validateDOMNesting模块用于校验父子节点设置及同名节点嵌套结构设定是否有误。
'use strict';
var _assign = require('object-assign');
var emptyFunction = require('fbjs/lib/emptyFunction');
var warning = require('fbjs/lib/warning');
var validateDOMNesting = emptyFunction;
if (process.env.NODE_ENV !== 'production') {
var specialTags = ['address', 'applet', 'area', 'article', 'aside', 'base', 'basefont', 'bgsound',
'blockquote', 'body', 'br', 'button', 'caption', 'center', 'col', 'colgroup', 'dd', 'details',
'dir', 'div', 'dl', 'dt', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'frame',
'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'iframe',
'img', 'input', 'isindex', 'li', 'link', 'listing', 'main', 'marquee', 'menu', 'menuitem', 'meta',
'nav', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'p', 'param', 'plaintext', 'pre',
'script', 'section', 'select', 'source', 'style', 'summary', 'table', 'tbody', 'td', 'template',
'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul', 'wbr', 'xmp'];
var inScopeTags = ['applet', 'caption', 'html', 'table', 'td', 'th', 'marquee', 'object', 'template',
'foreignObject', 'desc', 'title'];
var buttonScopeTags = inScopeTags.concat(['button']);
var impliedEndTags = ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt'];
var emptyAncestorInfo = {
current: null,
formTag: null,
aTagInScope: null,
buttonTagInScope: null,
nobrTagInScope: null,
pTagInButtonScope: null,
listItemTagAutoclosing: null,
dlItemTagAutoclosing: null
};
// 记录自顶层非react绘制的容器节点到当前react绘制节点,节点类型出现情况,用于校验嵌套元素的合法性
// ancestorInfo.current记录当前节点
// ancestorInfo.listItemTagAutoclosing为真时,意为li节点已出现,且其下是'address'、'div'、'p'节点
// 校验时"li"不允许再次绘制,否则可允许嵌套"li"节点
var updatedAncestorInfo = function (oldInfo, tag, instance) {
var ancestorInfo = _assign({}, oldInfo || emptyAncestorInfo);
var info = { tag: tag, instance: instance };
if (inScopeTags.indexOf(tag) !== -1) {
ancestorInfo.aTagInScope = null;
ancestorInfo.buttonTagInScope = null;
ancestorInfo.nobrTagInScope = null;
}
if (buttonScopeTags.indexOf(tag) !== -1) {
ancestorInfo.pTagInButtonScope = null;
}
// li节点下不是'address'、'div'、'p',可允许嵌套绘制"li"节点;否则不允许嵌套绘制"li"节点
if (specialTags.indexOf(tag) !== -1 && tag !== 'address' && tag !== 'div' && tag !== 'p') {
ancestorInfo.listItemTagAutoclosing = null;
ancestorInfo.dlItemTagAutoclosing = null;
}
ancestorInfo.current = info;
if (tag === 'form') {
ancestorInfo.formTag = info;
}
if (tag === 'a') {
ancestorInfo.aTagInScope = info;
}
if (tag === 'button') {
ancestorInfo.buttonTagInScope = info;
}
if (tag === 'nobr') {
ancestorInfo.nobrTagInScope = info;
}
if (tag === 'p') {
ancestorInfo.pTagInButtonScope = info;
}
if (tag === 'li') {
ancestorInfo.listItemTagAutoclosing = info;
}
if (tag === 'dd' || tag === 'dt') {
ancestorInfo.dlItemTagAutoclosing = info;
}
return ancestorInfo;
};
// 校验子节点是否为父节点下可接受的类型;以及父节点是否是子节点上可接受的类型
var isTagValidWithParent = function (tag, parentTag) {
switch (parentTag) {
case 'select':
return tag === 'option' || tag === 'optgroup' || tag === '#text';
case 'optgroup':
return tag === 'option' || tag === '#text';
case 'option':
return tag === '#text';
case 'tr':
return tag === 'th' || tag === 'td' || tag === 'style' || tag === 'script' ||
tag === 'template';
case 'tbody':
case 'thead':
case 'tfoot':
return tag === 'tr' || tag === 'style' || tag === 'script' || tag === 'template';
case 'colgroup':
return tag === 'col' || tag === 'template';
case 'table':
return tag === 'caption' || tag === 'colgroup' || tag === 'tbody' || tag === 'tfoot' ||
tag === 'thead' || tag === 'style' || tag === 'script' || tag === 'template';
case 'head':
return tag === 'base' || tag === 'basefont' || tag === 'bgsound' || tag === 'link' ||
tag === 'meta' || tag === 'title' || tag === 'noscript' || tag === 'noframes' ||
tag === 'style' || tag === 'script' || tag === 'template';
case 'html':
return tag === 'head' || tag === 'body';
case '#document':
return tag === 'html';
}
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
return parentTag !== 'h1' && parentTag !== 'h2' && parentTag !== 'h3' && parentTag !== 'h4'
&& parentTag !== 'h5' && parentTag !== 'h6';
case 'rp':
case 'rt':
return impliedEndTags.indexOf(parentTag) === -1;
case 'body':
case 'caption':
case 'col':
case 'colgroup':
case 'frame':
case 'head':
case 'html':
case 'tbody':
case 'td':
case 'tfoot':
case 'th':
case 'thead':
case 'tr':
return parentTag == null;
}
return true;
};
// 不允许嵌套绘制节点的情形发生时,获取嵌套外层的li、p、dd、dt、form、a、button、nobr节点标签名及相关react组件实例
var findInvalidAncestorForTag = function (tag, ancestorInfo) {
switch (tag) {
case 'address':
case 'article':
case 'aside':
case 'blockquote':
case 'center':
case 'details':
case 'dialog':
case 'dir':
case 'div':
case 'dl':
case 'fieldset':
case 'figcaption':
case 'figure':
case 'footer':
case 'header':
case 'hgroup':
case 'main':
case 'menu':
case 'nav':
case 'ol':
case 'p':
case 'section':
case 'summary':
case 'ul':
case 'pre':
case 'listing':
case 'table':
case 'hr':
case 'xmp':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
return ancestorInfo.pTagInButtonScope;
case 'form':
return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope;
case 'li':
return ancestorInfo.listItemTagAutoclosing;
case 'dd':
case 'dt':
return ancestorInfo.dlItemTagAutoclosing;
case 'button':
return ancestorInfo.buttonTagInScope;
case 'a':
return ancestorInfo.aTagInScope;
case 'nobr':
return ancestorInfo.nobrTagInScope;
}
return null;
};
// 获取祖先组件实例到当前组件实例的集合
var findOwnerStack = function (instance) {
if (!instance) {
return [];
}
var stack = [];
do {
stack.push(instance);
} while (instance = instance._currentElement._owner);
stack.reverse();
return stack;
};
var didWarn = {};
// 校验节点书写(同名节点嵌套、父子节点设定)是否符合html规范,有节点堆栈提示
validateDOMNesting = function (childTag, childText, childInstance, ancestorInfo) {
ancestorInfo = ancestorInfo || emptyAncestorInfo;
var parentInfo = ancestorInfo.current;
var parentTag = parentInfo && parentInfo.tag;
// 参数childText有值时,参数childTag须为null
if (childText != null) {
process.env.NODE_ENV !== 'production' ?
warning(childTag == null, 'validateDOMNesting: when childText is passed, '
+ 'childTag should be null')
: void 0;
childTag = '#text';
}
// isTagValidWithParent函数校验子节点是否为父节点下可接受的类型;以及父节点是否是子节点上可接受的类型
var invalidParent = isTagValidWithParent(childTag, parentTag) ? null : parentInfo;
// findInvalidAncestorForTag函数,当不允许嵌套绘制节点的情形发生时
// 获取嵌套外层的li、p、dd、dt、form、a、button、nobr节点标签名及相关react组件实例
var invalidAncestor = invalidParent ? null : findInvalidAncestorForTag(childTag, ancestorInfo);
var problematic = invalidParent || invalidAncestor;
if (problematic) {
var ancestorTag = problematic.tag;
var ancestorInstance = problematic.instance;
var childOwner = childInstance && childInstance._currentElement._owner;
var ancestorOwner = ancestorInstance && ancestorInstance._currentElement._owner;
// findOwnerStack函数用于获取祖先组件实例到当前组件实例的集合
var childOwners = findOwnerStack(childOwner);
var ancestorOwners = findOwnerStack(ancestorOwner);
var minStackLen = Math.min(childOwners.length, ancestorOwners.length);
var i;
// childInstance还可能跟ancestorInstance含有不同祖先组件实例吗???
var deepestCommon = -1;
for (i = 0; i < minStackLen; i++) {
if (childOwners[i] === ancestorOwners[i]) {
deepestCommon = i;
} else {
break;
}
}
var UNKNOWN = '(unknown)';
var childOwnerNames = childOwners.slice(deepestCommon + 1).map(function (inst) {
return inst.getName() || UNKNOWN;
});
var ancestorOwnerNames = ancestorOwners.slice(deepestCommon + 1).map(function (inst) {
return inst.getName() || UNKNOWN;
});
// 获取节点的堆栈信息,作为错误提示用;节点嵌套书写有误,提示含"...";父子节点书写有误,不含"..."
var ownerInfo = [].concat(
deepestCommon !== -1 ? childOwners[deepestCommon].getName() || UNKNOWN : [], ancestorOwnerNames, ancestorTag,
invalidAncestor ? ['...'] : [], childOwnerNames, childTag).join(' > ');
var warnKey = !!invalidParent + '|' + childTag + '|' + ancestorTag + '|' + ownerInfo;
if (didWarn[warnKey]) {
return;
}
didWarn[warnKey] = true;
var tagDisplayName = childTag;
var whitespaceInfo = '';
if (childTag === '#text') {
if (/\S/.test(childText)) {
tagDisplayName = 'Text nodes';
} else {
tagDisplayName = 'Whitespace text nodes';
whitespaceInfo = ' Make sure you don\'t have any extra whitespace between tags on ' + 'each line of your source code.';
}
} else {
tagDisplayName = '<' + childTag + '>';
}
if (invalidParent) {
var info = '';
if (ancestorTag === 'table' && childTag === 'tr') {
info += ' Add a <tbody> to your code to match the DOM tree generated by ' + 'the browser.';
}
process.env.NODE_ENV !== 'production' ?
warning(false, 'validateDOMNesting(...): %s cannot appear as a child of <%s>.%s '
+ 'See %s.%s', tagDisplayName, ancestorTag, whitespaceInfo, ownerInfo, info)
: void 0;
} else {
process.env.NODE_ENV !== 'production' ?
warning(false, 'validateDOMNesting(...): %s cannot appear as a descendant of '
+ '<%s>. See %s.', tagDisplayName, ancestorTag, ownerInfo)
: void 0;
}
}
};
// 用于更新ReactDomComponent组件实例的this._ancestorInfo,以校验嵌套节点书写是否符合html规范
validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
// 校验节点书写(同名节点嵌套、父子节点设定)是否符合html规范,无提示
validateDOMNesting.isTagValidInContext = function (tag, ancestorInfo) {
ancestorInfo = ancestorInfo || emptyAncestorInfo;
var parentInfo = ancestorInfo.current;
var parentTag = parentInfo && parentInfo.tag;
return isTagValidWithParent(tag, parentTag) && !findInvalidAncestorForTag(tag, ancestorInfo);
};
}
module.exports = validateDOMNesting;
本文介绍了一个用于验证DOM节点嵌套合法性的模块,确保HTML结构遵循标准规范。该模块能够检查父子节点间的合规性,并警告不当的节点嵌套。

1920

被折叠的 条评论
为什么被折叠?



