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;