From 5c9f2b470f079a7f046907a8693ff0ddec9d1db5 Mon Sep 17 00:00:00 2001 From: future Date: Tue, 7 Apr 2020 00:09:42 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E7=8E=8B=E6=B5=B7=E5=85=B5=20#965#.=20?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "\347\216\213\346\265\267\345\205\265 #965#" | 232 +++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 "\347\216\213\346\265\267\345\205\265 #965#" diff --git "a/\347\216\213\346\265\267\345\205\265 #965#" "b/\347\216\213\346\265\267\345\205\265 #965#" new file mode 100644 index 0000000..a935317 --- /dev/null +++ "b/\347\216\213\346\265\267\345\205\265 #965#" @@ -0,0 +1,232 @@ +import { isValidElement, cloneAndReplaceKey } from 'react'; +import { REACT_ELEMENT_TYPE } from 'react'; +const POOL_SIZE = 10; +const traverseContextPool = []; +const SEPARATOR = '.'; +const SUBSEPARATOR = ':'; + + +const userProvidedKeyEscapeRegex = /\/+/g; +function escapeUserProvidedKey(text) { + return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/'); +} +/** + * 因为=和:有特殊含义,所以需要转义 + * =替换成=0 + * :替换成=2 因为:是分隔符 + * @param {*} key + */ +function escape(key) { + const escapeRegex = /[=:]/g; + const escaperLookup = { + '=': '=0', + ':': '=2', + }; + const escapedString = ('' + key).replace(escapeRegex, function (match) { + return escaperLookup[match]; + }); + // key前面要加$ + return '$' + escapedString; +} +function getPooledTraverseContext( + mapResult, + keyPrefix, + mapFunction, + mapContext, +) { + if (traverseContextPool.length) { + const traverseContext = traverseContextPool.pop(); + traverseContext.result = mapResult; + traverseContext.keyPrefix = keyPrefix; + traverseContext.func = mapFunction; + traverseContext.context = mapContext; + traverseContext.count = 0; + return traverseContext; + } else { + return { + result: mapResult, + keyPrefix: keyPrefix, + func: mapFunction, + context: mapContext, + count: 0,//计算 children 的个数,计算的是摊平后数组元素的个数 + }; + } +} +/** + * 映射子元素,一般是props.children + * 提供的函数将会被通过传递(child,key,index)来为每一个子节点调用 + * @param {*} children 要迭代的子元素数组 + * @param {*} func 映射函数 + * @param {*} context map函数的上下文对象 + * @returns 包含结果的数组 + */ +function mapChildren(children, func, context) { + debugger; + if (children == null) { + return children; + } + const result = [];// 遍历出来的元素会丢到result中最后返回出去 + mapIntoWithKeyPrefixInternal(children, result, null, func, context); + return result; +} +//将 children 完全遍历,遍历的节点最终全部存到 array 中,是 ReactElement 的节点会更改 key 之后再放到 array 中 +function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { + let escapedPrefix = ''; + if (prefix != null) { + escapedPrefix = escapeUserProvidedKey(prefix) + '/'; + } + const traverseContext = getPooledTraverseContext( + array, + escapedPrefix, + func, + context, + ); + traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); + releaseTraverseContext(traverseContext); +} +/** + * 将 child 推入 traverseContext 的 result 数组中,child 如果是 ReactElement,则更改 key 了再推入 + * 只有当传入的 child 是可渲染节点才会调用。如果执行了 mapFunc 返回的是一个数组,则会将数组放到 mapIntoWithKeyPrefixInternal 继续处理 + * @param {*} bookKeeping 就是我们从对象池子里取出来的东西,`traverseContext` + * @param {*} child 传入的节点,`children` + * @param {*} childKey 节点的 key,`nameSoFar` + */ +function mapSingleChildIntoContext(bookKeeping, child, childKey) { + const { result, keyPrefix, func, context } = bookKeeping; + // func 就是我们在 React.Children.map(this.props.children, c => c)中传入的第二个函数参数 + let mappedChild = func.call(context, child, bookKeeping.count++); + //判断函数返回值是否为数组 + if (Array.isArray(mappedChild)) { + mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); + } else if (mappedChild != null) { + if (isValidElement(mappedChild)) { + mappedChild = cloneAndReplaceKey( + mappedChild, + keyPrefix + + (mappedChild.key && (!child || child.key !== mappedChild.key) + ? escapeUserProvidedKey(mappedChild.key) + '/' + : '') + + childKey, + ); + } + result.push(mappedChild); + } +} + +function releaseTraverseContext(traverseContext) { + traverseContext.result = null; + traverseContext.keyPrefix = null; + traverseContext.func = null; + traverseContext.context = null; + traverseContext.count = 0; + if (traverseContextPool.length < POOL_SIZE) { + traverseContextPool.push(traverseContext); + } +} + +/** + * 遍历子节点,比如props.children + * traverseContext是一个可选参数,会被在整个遍历过程中被传递。它可能被用来进行仓库累加或者任何其它 + * 在回调中可以找到关联的地方 + * + * @param {?*} children 要遍历的子节点 + * @param {!function} callback 在每一个子节点上被调用 + * @param {?*} traverseContext 遍历时候的上下文 + * @return {!number} 在子树中的子节点的数量 + */ +function traverseAllChildren(children, callback, traverseContext) { + if (children == null) { + return 0; + } + return traverseAllChildrenImpl(children, '', callback, traverseContext); +} + +/** + * 获取组件的key + * @param {*} component React元素 + * @param {*} index 索引 + */ +function getComponentKey(component, index) { + if (//如果是对称并且有key的话就返回转义后的key + typeof component === 'object' && + component !== null && + component.key != null + ) { + return escape(component.key); + } + return index.toString(36); +} + +/** + * 如果children是可渲染节点,则调用 mapSingleChildIntoContext 把 children 推入 result 数组中 + * 如果children 是数组,则再次对数组中的每个元素调用 traverseAllChildrenImpl,传入的 key 是最新拼接好的 + * children 是对象,则通过 children[Symbol.iterator] 获取到对象的迭代器 iterator, 将迭代的结果放到 traverseAllChildrenImpl 处理 + * 函数核心作用就是通过把传入的 children 数组通过遍历摊平成单个节点,然后去执行 mapSingleChildIntoContext + * @param {?*} children 要循环的子节点 + * @param {!string} nameSoFar 父级 key,会一层一层拼接传递,用:分隔 + * @param {!function} callback 如果当前层级是可渲染节点,undefined、boolean 会变成 null,string、number、$$typeof 是 REACT_ELEMENT_TYPE 或者 REACT_PORTAL_TYPE,会调用 mapSingleChildIntoContext 处理 + * @param {?*} traverseContext 对象池中拿出来的一个对象,用来在遍历的过程中传递信息 + * @return {!number} 在这个子树中子节点的数量 + */ +function traverseAllChildrenImpl( + children, + nameSoFar, + callback, + traverseContext, +) { + const type = typeof children; + //所有的undefined或者boolean被处理为null + if (type === 'undefined' || type === 'boolean') { + children = null; + } + + let invokeCallback = false; + + if (children === null) { + invokeCallback = true; + } else { + switch (type) { + case 'string': + case 'number': + invokeCallback = true;//如果是字符串或者数字则可以直接回调 + break; + case 'object': + switch (children.$$typeof) {//如果是树象,并且是一个React元素节点可以直接回调 + case REACT_ELEMENT_TYPE: + invokeCallback = true; + } + } + } + if (invokeCallback) {//如果可以调用回调就则直接调用 + callback( + traverseContext, + children, + nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, + ); + return 1; + } + + let child; + let nextName; + let subtreeCount = 0;//在当前的子树中查找到的子节点的数量 + const nextNamePrefix = + nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;//如果nameSoFar为空就是.,否则就是nameSoFar: + + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + child = children[i]; + nextName = nextNamePrefix + getComponentKey(child, i);// 获得下一个名称 .0 或者.$key + subtreeCount += traverseAllChildrenImpl( + child, + nextName, + callback, + traverseContext, + ); + } + } + return subtreeCount; +} + +export { + mapChildren +} \ No newline at end of file -- Gitee