React 源码学习01 ---- React.Children.map 的实现与应用

embedded/2024/11/14 4:16:48/

1. 使用方法

React.Children.map(children, function[(thisArg)])

2. 方法解释

在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。

3. 注意

如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。

4. React.Children.map 源码

4.1 mapChildren 源码
\packages\react\src\ReactChildren.jsfunction mapChildren(children: ?ReactNodeList,func: MapFunc,context: mixed,
): ?Array<React$Node> {// 判断传入的子元素列表是否是 null 或 undefined,条件成立,直接返回 childrenif (children == null) {// $FlowFixMe limitation refining abstract types in Flowreturn children;}// 存储处理后的 child 列表const result: Array<React$Node> = [];// 每一个 child 的索引let count = 0;// 将 children 中的每个元素映射到数组 result 中,并在映射过程中应用 func 函数mapIntoArray(children, result, '', '', function (child) {// 调用映射函数处理 child,并返回处理结果return func.call(context, child, count++);});return result;
}
4.2 mapChildren 函数解释
  1. children 是任意类型的子元素列表。
  2. func 操作子元素的映射函数,接受两个参数:子元素和索引,并返回一个映射后的结果。
  3. context 执行 func 映射函数的上下文。
  4. 检查 children 是否为 null 或 undefined,如果是,则直接返回 children。
  5. 创建一个名为 result 的空数组,用于存储处理后的子元素。
  6. 初始化一个计数器 count,用于记录当前处理的子元素的索引。
  7. 使用 mapIntoArray 函数遍历 children,并将每个子元素转换为数组形式存储在 result 中。mapIntoArray 函数还接受一个回调函数,该回调函数会调用 func 函数,并传入当前子元素和计数器的值。
  8. 返回处理后的 result 数组。
4.3 mapIntoArray 函数的实现
function mapIntoArray(children: ?ReactNodeList,array: Array<React$Node>,escapedPrefix: string,nameSoFar: string,callback: (?React$Node) => ?ReactNodeList,
): number {// 使用 typeof 获取 children 的类型const type = typeof children;// 判断 type 是否是 undefined 或者 boolean,跳转成立,将 children 赋值 nullif (type === 'undefined' || type === 'boolean') {// All of the above are perceived as null.children = null;}// 是否调用回调函数标记let invokeCallback = false;// 如果 children 是 null,则调用回调if (children === null) {invokeCallback = true;} else {// 判断 type 是 bigint、string、number 直接调用回调switch (type) {case 'bigint':case 'string':case 'number':invokeCallback = true;break;case 'object':// 如果 type 是 object,继续判断 $$typeof 的值switch ((children: any).$$typeof) {// 如果 $$typeof 是 REACT_ELEMENT_TYPE、REACT_PORTAL_TYPE直接进入回调case REACT_ELEMENT_TYPE:case REACT_PORTAL_TYPE:invokeCallback = true;break;// 如果 $$typeof 是 REACT_LAZY_TYPE 进行递归调用case REACT_LAZY_TYPE:const payload = (children: any)._payload;const init = (children: any)._init;return mapIntoArray(init(payload),array,escapedPrefix,nameSoFar,callback,);}}}// 如果 invokeCallback 是 true,直接调用回调if (invokeCallback) {const child = children;// 使用回调函数处理 child,然后赋值给 mappedChildlet mappedChild = callback(child);// If it's the only child, treat the name as if it was wrapped in an array// so that it's consistent if the number of children grows:// 获取子元素的key,如果 nameSoFar 是空,直接使用 SEPARATOR + getElementKey(child, 0) 否则使用 nameSoFarconst childKey =nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;// 判断处理后的子元素是否是数组【Array.isArray】 if (isArray(mappedChild)) {let escapedChildKey = '';// 如果 childKey 不为 null,则将 childKey 的单/替换为//,结果赋值给 escapedChildKeyif (childKey != null) {escapedChildKey = escapeUserProvidedKey(childKey) + '/';}// 如果是数组,递归调用 mapIntoArraymapIntoArray(mappedChild, array, escapedChildKey, '', c => c);} else if (mappedChild != null) {// 如果是 react 元素if (isValidElement(mappedChild)) {// 复制一份新的子元素const newChild = cloneAndReplaceKey(mappedChild,// Keep both the (mapped) and old keys if they differ, just as// traverseAllChildren used to do for objects as childrenescapedPrefix +// $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key(mappedChild.key != null &&(!child || child.key !== mappedChild.key)? escapeUserProvidedKey(// $FlowFixMe[unsafe-addition]'' + mappedChild.key, // eslint-disable-line react-internal/safe-string-coercion) + '/': '') +childKey,);mappedChild = newChild;}// 将子元素放入存储的数组中array.push(mappedChild);}return 1;}let child;let nextName;let subtreeCount = 0; // Count of children found in the current subtree.const nextNamePrefix =nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;// 如果 invokeCallback 为 false,// children 是否为数组if (isArray(children)) {// 遍历 children 中每个元素,调用 mapIntoArray 函数,并将返回的子树计数累加到 subtreeCount 中for (let i = 0; i < children.length; i++) {child = children[i];nextName = nextNamePrefix + getElementKey(child, i);subtreeCount += mapIntoArray(child,array,escapedPrefix,nextName,callback,);}} else {// children 是一个可迭代对象,则使用 iteratorFn 函数获取迭代器,并遍历每个元素,调用 mapIntoArray 函数,并将返回的子树计数累加到 subtreeCount 中const iteratorFn = getIteratorFn(children);if (typeof iteratorFn === 'function') {const iterableChildren: Iterable<React$Node> & {entries: any,} = (children: any);const iterator = iteratorFn.call(iterableChildren);let step;let ii = 0;// $FlowFixMe[incompatible-use] `iteratorFn` might return null according to typing.while (!(step = iterator.next()).done) {child = step.value;nextName = nextNamePrefix + getElementKey(child, ii++);subtreeCount += mapIntoArray(child,array,escapedPrefix,nextName,callback,);}} else if (type === 'object') {// children 是一个对象,则检查其是否有 then 方法,如果有,则将其视为一个 Promise,并递归调用 mapIntoArray 函数if (typeof (children: any).then === 'function') {return mapIntoArray(resolveThenable((children: any)),array,escapedPrefix,nameSoFar,callback,);}// children 是一个对象,但没有 then 方法,则将其转换为字符串,并抛出一个错误,因为对象不能作为 React 子元素// eslint-disable-next-line react-internal/safe-string-coercionconst childrenString = String((children: any));throw new Error(`Objects are not valid as a React child (found: ${childrenString === '[object Object]'? 'object with keys {' +Object.keys((children: any)).join(', ') +'}': childrenString}). ` +'If you meant to render a collection of children, use an array ' +'instead.',);}}// 返回 subtreeCount,表示处理的子元素总数return subtreeCount;
}
4.4 总结

目前是第一次看这个函数的实现,知道他大概干了什么,也大概明白每一步要干什么,但是目前还不清除他为什么要这么干。为什么要来看他的实现呢?一个是只有知道他是怎么实现的,才能知道在调用这个方法时,那些情况会报错,为什么报错,能够快速处理。使用这个方法开发功能的时候能够更加得心应手。

5. React.Children.map 应用

  1. swiper 组件的开发,或者说类 swiper 组件的开发,都需要在 swiper 组件内部获取他的子元素,然后再再子元素的外层添加一个盒子,然后在盒子上实现我们需要的动画效果,比如渐入渐出、放大缩小,滑动等动画效果;
  2. 目前想到可以使用的就是瀑布流展示效果,动态获取每一列的高度,然后在动态的分配下一个子元素在那一列展示;
  3. 其他的应用应该还有很多,只是我在开发中遇到的比较少,以后有新的想法,再来补充!

http://www.ppmy.cn/embedded/136945.html

相关文章

【Linux】常用命令(2.6万字汇总)

文章目录 Linux常用命令汇总1. 基础知识1.1. Linux系统命令行的含义1.2. 命令的组成 2. 基础知识2.1. 关闭系统2.2. 关闭重启2.3. 帮助命令&#xff08;help&#xff09;2.4. 命令说明书&#xff08;man&#xff09;2.5. 切换用户&#xff08;su&#xff09;2.6.历史指令 3.目录…

【LeetCode】【算法】33. 搜索旋转排序数组

LeetCode 33. 搜索旋转排序数组 题目描述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k…

【测试】【Debug】pytest运行后print没有输出

import pytest def test_good():for i in range(1000):print(i)def test_bad():print(this should fail!)assert False比如上述程序&#xff0c;运行之后只能看到输出了’this should fail!&#xff1b;但是debug版的测试运行后又能看到test_good函数中的输出。 这是为什么呢&a…

【初阶数据结构与算法】线性表之链表的分类以及双链表的定义与实现

文章目录 一、链表的分类二、双链表的实现1.双链表结构的定义2.双链表的初始化和销毁初始化函数1初始化函数2销毁函数 3.双链表的打印以及节点的申请打印函数节点的申请 4.双链表的头插和尾插头插函数尾插函数 5.双链表的查找和判空查找函数判空函数 6.双链表的头删和尾删头删函…

第26天 安全开发-PHP应用模板引用Smarty渲染MVC模型数据联动RCE安全

时间轴&#xff1a; 演示案例 新闻列表&模板引用-代码RCE安全 知识点 1、PHP 新闻显示-数据库操作读取显示 2、PHP 模版引用-自写模版&Smarty 渲染 3、PHP 模版安全-RCE 代码执行&三方漏洞 新闻列表 1.数据库创建新闻存储 2.代码连接数据库读取 3.页面进行自定…

vue2 自动化部署 shell 脚本

需求场景&#xff1a;在云平台中进行开发时&#xff0c;由于无法连接外网&#xff0c;在部署前端项目时&#xff0c;是通过本地打包再上传到服务器的方式进行部署的。基于这种部署场景&#xff0c;通过 shell 脚本进行部署流程优化&#xff0c;具体如下&#xff1a; 1、服务器…

Solon MVC 的 @Mapping 用法说明

在 Solon Mvc 里&#xff0c;Mapping 注解一般是配合 Controller 和 Remoting&#xff0c;作请求路径映射用的。且&#xff0c;只支持加在 public 函数 或 类上。 1、注解属性 属性说明备注value路径与 path 互为别名path路径与 value 互为别名method请求方式限定(defall)可用…

【C++ 算法进阶】算法提升十四

目录 括号匹配问题 &#xff08;动态规划&#xff09;题目题目分析代码 子数组最接近某个数 &#xff08;动态规划&#xff09;题目题目分析代码 求出数组中缺失的最小正整数 &#xff08;贪心&#xff09;题目题目分析代码 恢复二叉搜索树 &#xff08;二叉树的性质&#xff0…