From d52b7aa5aaabf2c42a25fcc9819614f2f76e8476 Mon Sep 17 00:00:00 2001 From: ailin <5110360+fenglinlin@user.noreply.gitee.com> Date: Tue, 7 Apr 2020 18:05:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?update=20src/react/ReactChildren.js.=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0mapchildren=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/react/ReactChildren.js | 203 ++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) diff --git a/src/react/ReactChildren.js b/src/react/ReactChildren.js index bbc07d8..5de2f60 100644 --- a/src/react/ReactChildren.js +++ b/src/react/ReactChildren.js @@ -1,8 +1,209 @@ +//这个方法接受3个参数children、func和context。 +//children就是将要被遍历的子组件数组,func是对单个子组件需要执行的函数,context则是func执行时this指针所指向的对象。 function mapChildren(children, func, context) { //TODO实现此mapChildren方法 - return children; + if (children == null) { + return children; + } + const result = []; + mapIntoWithKeyPrefixInternal(children,result,null,func,context); + return result } +/** + * @param {*} props.children + * @param {Array} array 结果数组 + * @param {*} prefix null + * @param {function} func 每个孩子调用的 callback + * @param {obj} context func调用的上下文 + */ +function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { + // 和 prefix 前缀相关的可以暂时不用管 + /*let escapedPrefix = ''; + if (prefix != null) { + escapedPrefix = escapeUserProvidedKey(prefix) + '/'; + }*/ + // 从 存储 context 池子中拿取一个空对象来用 + const traverseContext = getPooledTraverseContext( + array, + escapedPrefix, + func, + context, + ); + traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); + releaseTraverseContext(traverseContext); + } + // context 池子的大小 +const POOL_SIZE = 10; +// context 池 +const traverseContextPool = []; +/** + * 从 context 池子中拿到一个空的 context 对象来用,然后将传进去的参数添加到 context 中, + * 并添加 count,初始值为 0,用来记录这个遍历过程中每一个被遍历到的 child 的顺序, 并被当做 mapfunction 的第三个 index 参数。然后返回 context + * @param {array} mapResult 结果数组 + * @param {string} keyPrefix 前缀 + * @param {funcgtion} mapFunction 每一个孩子调用的 callback + * @param {obj} mapContext mapFunction 调用的时候的上下文 + */ +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, + }; + } +} + +// 释放使用过的 context,将参数置为初始值,如果线程池没有满,那么就讲这个 +// 使用过的 context 添加进去。这样做的目的是为了防止频繁的分配内存,影响性能。 +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); + } +} +/** + * 遍历 children 实现 + * @param {?*} props.children + * @param {!string} nameSoFar Name of the key path so far. + * @param {!function} callback 对每个找到的 children 调用的方法,在它的内部会调用我们使用的时候传入的那个 mapFunction,然后把结果 push 到 result 数组中。 + * @param {?*} traverseContext 用于在遍历过程中传递信息。 + * @return {!number} 返回当前参数 children 下有多少个孩子 + */ +function traverseAllChildrenImpl( + children, + nameSoFar, + callback, + traverseContext, + ) { + // -------------------------- 首先处理 单个 children 的情况 + const type = typeof children; + + if (type === 'undefined' || type === 'boolean') { + children = null; + } + + let invokeCallback = false; + // 为 null 也会调用 + if (children === null) { + invokeCallback = true; + } else { + // 单个节点可能存在下面几种情况 + switch (type) { + case 'string': + case 'number': + invokeCallback = true; + break; + case 'object': + switch (children.$$typeof) { + case REACT_ELEMENT_TYPE: + case REACT_PORTAL_TYPE: + invokeCallback = true; + } + } + } + // 如果 children 是单个节点 + if (invokeCallback) { + callback( + traverseContext, + children, + nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, + ); + // 只有一个节点 那么 children 数量就是 1, 该函数返回 children 的数量,所以这里直接返回 1 + return 1; + } + // -------------------------- 处理children 是 Array 的情况 + let child; + let nextName; + let subtreeCount = 0; // 找到的 children 的数量 + // const nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; + + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + child = children[i]; + nextName = nextNamePrefix + getComponentKey(child, i); + subtreeCount += traverseAllChildrenImpl( + child, + nextName, + callback, + traverseContext, + ); + } + } else { + // 如果不是数组,但是有迭代器, 表示可遍历, + const iteratorFn = getIteratorFn(children); + if (typeof iteratorFn === 'function') { + const iterator = iteratorFn.call(children); + let step; + let ii = 0; + while (!(step = iterator.next()).done) { + child = step.value; + nextName = nextNamePrefix + getComponentKey(child, ii++); + // 依然是一样的逻辑,只是前面处理迭代的方式不同,是一个兼容处理 + subtreeCount += traverseAllChildrenImpl( + child, + nextName, + callback, + traverseContext, + ); + } + } + } + + return subtreeCount; + } + /** + * @param {obj} bookKeeping traverseContext 前面从 context 池子拿出来转换过的 context 携带着一些信息 + * @param {*} child props.children + * @param {*} childKey + */ +function mapSingleChildIntoContext(bookKeeping, child, childKey) { + const {result, keyPrefix, func, context} = bookKeeping; + // 调用我们最开始自定义的 mapFunction,并拿到返回结果, 这里用到了 count + let mappedChild = func.call(context, child, bookKeeping.count++); + + // 有可能我们自己返回的时候,返回的是数组,那么就继续回到 mapIntoWithKeyPrefixInternal 中 + if (Array.isArray(mappedChild)) { + mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); + } else if (mappedChild != null) { + // 如果是可用的 element, 那么 clone 一下,就像 Array.prototype.map 返回的是一个新的数组一样 + if (isValidElement(mappedChild)) { + mappedChild = cloneAndReplaceKey( + mappedChild, + // Keep both the (mapped) and old keys if they differ, just as + // traverseAllChildren used to do for objects as children + keyPrefix + + (mappedChild.key && (!child || child.key !== mappedChild.key) + ? escapeUserProvidedKey(mappedChild.key) + '/' + : '') + + childKey, + ); + } + // 将结果 push 到结果数组中去 + result.push(mappedChild); + } + } + export { mapChildren as map, }; \ No newline at end of file -- Gitee From 7ae1dcd94459ac77a262ba872273aa2ef852cca1 Mon Sep 17 00:00:00 2001 From: linlin <1259649692@qq.com> Date: Thu, 9 Apr 2020 10:15:45 +0800 Subject: [PATCH 2/2] handmapchildren --- src/react/ReactChildren.js | 246 ++++++++----------------------------- 1 file changed, 49 insertions(+), 197 deletions(-) diff --git a/src/react/ReactChildren.js b/src/react/ReactChildren.js index 5de2f60..7da511b 100644 --- a/src/react/ReactChildren.js +++ b/src/react/ReactChildren.js @@ -1,208 +1,60 @@ -//这个方法接受3个参数children、func和context。 -//children就是将要被遍历的子组件数组,func是对单个子组件需要执行的函数,context则是func执行时this指针所指向的对象。 -function mapChildren(children, func, context) { - //TODO实现此mapChildren方法 - if (children == null) { - return children; - } +//react.map.children +import { REACT_ELEMENT_TYPE } from '../shared/ReactSymbols'; +/* function mapChildren(children, mapFunction, context) {//children = A {$$typeof:REACT_ELEMENT_TYPE} + return children.flat(Infinity).map(mapFunction).flat(Infinity); +} */ +const SEPARATOR = '.';//分隔符 开头的分隔符 +const SUB_SEPARATOR = ':';//子分隔符 中间件分隔符 +function mapChildren(children, mapFunction, context) {//children = A {$$typeof:REACT_ELEMENT_TYPE} const result = []; - mapIntoWithKeyPrefixInternal(children,result,null,func,context); - return result -} - -/** - * @param {*} props.children - * @param {Array} array 结果数组 - * @param {*} prefix null - * @param {function} func 每个孩子调用的 callback - * @param {obj} context func调用的上下文 - */ -function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { - // 和 prefix 前缀相关的可以暂时不用管 - /*let escapedPrefix = ''; - if (prefix != null) { - escapedPrefix = escapeUserProvidedKey(prefix) + '/'; - }*/ - // 从 存储 context 池子中拿取一个空对象来用 - const traverseContext = getPooledTraverseContext( - array, - escapedPrefix, - func, - context, - ); - traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); - releaseTraverseContext(traverseContext); - } - // context 池子的大小 -const POOL_SIZE = 10; -// context 池 -const traverseContextPool = []; -/** - * 从 context 池子中拿到一个空的 context 对象来用,然后将传进去的参数添加到 context 中, - * 并添加 count,初始值为 0,用来记录这个遍历过程中每一个被遍历到的 child 的顺序, 并被当做 mapfunction 的第三个 index 参数。然后返回 context - * @param {array} mapResult 结果数组 - * @param {string} keyPrefix 前缀 - * @param {funcgtion} mapFunction 每一个孩子调用的 callback - * @param {obj} mapContext mapFunction 调用的时候的上下文 - */ -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, - }; - } -} - -// 释放使用过的 context,将参数置为初始值,如果线程池没有满,那么就讲这个 -// 使用过的 context 添加进去。这样做的目的是为了防止频繁的分配内存,影响性能。 -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); - } + mapIntoWithKeyPrefixInternal(children, result, null, mapFunction, context); + return result; } -/** - * 遍历 children 实现 - * @param {?*} props.children - * @param {!string} nameSoFar Name of the key path so far. - * @param {!function} callback 对每个找到的 children 调用的方法,在它的内部会调用我们使用的时候传入的那个 mapFunction,然后把结果 push 到 result 数组中。 - * @param {?*} traverseContext 用于在遍历过程中传递信息。 - * @return {!number} 返回当前参数 children 下有多少个孩子 - */ -function traverseAllChildrenImpl( - children, - nameSoFar, - callback, - traverseContext, - ) { - // -------------------------- 首先处理 单个 children 的情况 - const type = typeof children; - - if (type === 'undefined' || type === 'boolean') { - children = null; - } - - let invokeCallback = false; - // 为 null 也会调用 - if (children === null) { - invokeCallback = true; - } else { - // 单个节点可能存在下面几种情况 - switch (type) { - case 'string': - case 'number': - invokeCallback = true; - break; - case 'object': - switch (children.$$typeof) { - case REACT_ELEMENT_TYPE: - case REACT_PORTAL_TYPE: - invokeCallback = true; - } - } - } - // 如果 children 是单个节点 - if (invokeCallback) { - callback( - traverseContext, - children, - nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, - ); - // 只有一个节点 那么 children 数量就是 1, 该函数返回 children 的数量,所以这里直接返回 1 - return 1; +//prefix指的是渲染前的节点key 最终key的/前面那部分 +function mapIntoWithKeyPrefixInternal(children, result, prefix, mapFunction, context) { + //traverseContext 遍历的上下文 + if (prefix !== null) { + prefix = prefix + '/'; // .0:0 => .0:0/ } - // -------------------------- 处理children 是 Array 的情况 - let child; - let nextName; - let subtreeCount = 0; // 找到的 children 的数量 - // const nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; - - if (Array.isArray(children)) { - for (let i = 0; i < children.length; i++) { - child = children[i]; - nextName = nextNamePrefix + getComponentKey(child, i); - subtreeCount += traverseAllChildrenImpl( - child, - nextName, - callback, - traverseContext, + const traverseContext = { result, prefix, mapFunction, context }; + traverseAllChildren(children, '', mapSingleChildIntoContext, traverseContext); +} +//A .0:0 +function traverseAllChildren(children, nameSoFar, mapSingleChildIntoContext, traverseContext) { + let type = typeof children; + //如果type是字符串或者数字,或者type是一个对象,但是children.$$typeof是一个React元素,说明children是 一个可渲染的节点 + if (type === 'string' || type === 'number' || (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)) { + mapSingleChildIntoContext(traverseContext, children, + nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar ); - } - } else { - // 如果不是数组,但是有迭代器, 表示可遍历, - const iteratorFn = getIteratorFn(children); - if (typeof iteratorFn === 'function') { - const iterator = iteratorFn.call(children); - let step; - let ii = 0; - while (!(step = iterator.next()).done) { - child = step.value; - nextName = nextNamePrefix + getComponentKey(child, ii++); - // 依然是一样的逻辑,只是前面处理迭代的方式不同,是一个兼容处理 - subtreeCount += traverseAllChildrenImpl( - child, - nextName, - callback, - traverseContext, - ); + } else if (Array.isArray(children)) { + //如果传过来的nameSoFar是空的.前缀就是.,否则就是: + //第二次进来的时候 nameSoFar = .0 nextNamePrefix=: + let nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUB_SEPARATOR; + for (let i = 0; i < children.length; i++) { + let child = children[i];//[A, B] .0 + let nextName = nextNamePrefix + getComponentKey(child, i);// .0:0 + traverseAllChildren(child, nextName, mapSingleChildIntoContext, traverseContext); } - } } - - return subtreeCount; - } - /** - * @param {obj} bookKeeping traverseContext 前面从 context 池子拿出来转换过的 context 携带着一些信息 - * @param {*} child props.children - * @param {*} childKey - */ -function mapSingleChildIntoContext(bookKeeping, child, childKey) { - const {result, keyPrefix, func, context} = bookKeeping; - // 调用我们最开始自定义的 mapFunction,并拿到返回结果, 这里用到了 count - let mappedChild = func.call(context, child, bookKeeping.count++); - - // 有可能我们自己返回的时候,返回的是数组,那么就继续回到 mapIntoWithKeyPrefixInternal 中 +} +function getComponentKey(component, index) { + return component.key || index.toString(36);//如果说此节点有自己的key,就用自己的key,如果没有就用它的索引 +} +//如果执行到这个地方 child肯定是一个节点 child=A childKey =.0:0 +function mapSingleChildIntoContext(traverseContext, child, childKey) {// + let { result, prefix, mapFunction, context } = traverseContext; + //let mappedChild = mapFunction(child); + let mappedChild = mapFunction.call(context, child);//child = A {$$typeof:REACT_ELEMENT_TYPE} + //mappedChild===child + //mappedChild = [item, [item, [item, [item]]]]; 往result里面放的永远只能是一个对象,不能是数组 if (Array.isArray(mappedChild)) { - mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); - } else if (mappedChild != null) { - // 如果是可用的 element, 那么 clone 一下,就像 Array.prototype.map 返回的是一个新的数组一样 - if (isValidElement(mappedChild)) { - mappedChild = cloneAndReplaceKey( - mappedChild, - // Keep both the (mapped) and old keys if they differ, just as - // traverseAllChildren used to do for objects as children - keyPrefix + - (mappedChild.key && (!child || child.key !== mappedChild.key) - ? escapeUserProvidedKey(mappedChild.key) + '/' - : '') + - childKey, - ); - } - // 将结果 push 到结果数组中去 - result.push(mappedChild); + mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c, context); + } else { + //把这个对象展开,重写key prefix转换前的索引组成的key/childKey转换后的索引组件的key + result.push({ ...mappedChild, key: prefix + childKey }); // .0:0/ } - } +} export { mapChildren as map, -- Gitee