【React】常见疑问的整理

1. React渲染一个列表,需要为每一项指定key?

列表的key主要是为了react渲染阶段更好的协调(reconciliation),diff时更好的识别react元素,减少dom的创建和操作,提升效率。

设定key值方式:

  • 来自数据库的数据,最好是主键(Primary Key)
  • 如果数据是本地产生,可以生成唯一的ID作为key

key需满足条件:

  • key值在兄弟节点之间必须唯一
  • key值不能改变
2. 为什么一定要在函数组件(FC)顶层调用Hook函数?

以useState举例来说,state和修改state的操作队列一起存在Fiber节点的hooks数组中,形式类似。

javascript 复制代码
fiber.hooks = [
    {
        state: '', // 第0个hook的当前状态值
        queue: [], // 第0个hook待执行的操作队列
    },
    {
        state: '', // 第1个hook的当前状态值
        queue: [], // 第1个hook待执行的操作队列
    },
    {
        memorizedState: [...,...]
    }
    ...
];

渲染时,函数组件作为函数执行时,函数内每执行一个hook函数,就按顺序从fiber.hooks取出hook的对应状态和操作队列。如果hook函数不是在函数组件顶层,可能会打乱获取hook对应状态和操作队列的顺序。

3.React运行过程是怎样的?
3.1 render阶段

一、基本过程

  • render
  • performSyncWorkOnRoot 同步(performConcurrentWorkOnRoot 异步)
  • wookLoopSync(workLoopCurrent)
  • performUnitOfWork:创建下一个Fiber节点,并赋值给workInProcess,将workInProcess与已创建的FIber节点连接起来,构成Fiber树
  • beginWork(递):根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来
  • completeWork(归):当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(fiber.sibling !== null),会进入其兄弟Fiber的'递'阶段;如果不存在兄弟Fiber,会进入父级Fiber的'归'阶段。
  • '递'和'归'阶段交错执行直到'归'到rootFiber,至此渲染工作结束。
  • 深度优先:child -> sibling -> return(parent),一直child到叶节点,再到sibling兄弟节点,处理完了再到return父节点

二、beginWork(current | null, workInProgress, renderLanes)

  • update:current.memorizedProps(oldProps)和workInProgress.pendingProps(newProps)
  • mount:根据Fiber.tag不同,进入不同类型的Fiber的创建逻辑

最终,都会进入reconcileChildren方法

  • update:reconcileChildren会将当前组件与该组件上次更新对应的Fiber节点比较(diff算法),将比较的结果生成新Fiber节点
  • mount:reconcileChildren会创建新的子Fiber节点

最后会执行moutChildFibers或reconcileChildFibers,workInProgress.child = moutChildFibers(...)或reconcileChildFibers(...),其中reconcileChildFibers会为生成的Fiber节点带上effectTag属性,moutChildFibers则不会。

fiber.stateNode会在completeWork中创建,mount时只有rootFiber会赋值Placement effectTag,在commit阶段直接一次插入操作。

三、completeWork(current | null, workInProgress, renderLanes)

类似beginWork,completeWork也是针对不同fiber.tag调用不同的处理逻辑

  • update:Fiber节点已存储对应DMO节点,不需要再生成DOM节点
  1. onClick、onChange等回调函数的注册
  2. 处理style prop
  3. 处理DANGEROUSLY_SET_INNER_HTML prop
  4. 处理children prop

fiber.tag为HostComponent时,最主要的逻辑是调用updateHostComponent,在updateHostComponent内部,被处理完的props会赋值给workInProgress.updateQueue,并最终会在commit阶段渲染到页面上。

  • mount
  1. 为Fiber节点生成对应DOM节点
  2. 将子孙DOM节点插入刚生成的DOM节点中
  3. 与update逻辑中的updateHostComponent类似的处理props的过程

mount时,只会在rootFiber存在Placement effectTag,那么commit阶段是如何通过一次插入DOM操作的?原因是completeWorkd中appendAllChildren方法,由completeWork属于'归'阶段调用函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下,'归'到rootFiber时,已经构建好了离屏DOM树。

在completeWork的上层函数completeUnitOfWork中,每执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。

在'归'阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表。rootFiber.firstEffect.nextEffect.nextEffect....

结尾:render全部工作完成,在performSyncWorkOnRoot函数中,fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。

3.2 commit阶段

commitRoot(root) commit阶段的起点。

一、commit阶段主要工作

  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)

before mutation阶段之前 和 layout阶段之后,还有一些额外工作:useEffect的触发、优先级相关的重置、ref的绑定/解绑

before mutation阶段之前:主要做一些变量赋值,状态重置的工作

layout阶段之后:

  • useEffect相关的处理。
  • 性能追踪相关。
  • 在commit阶段会触发一些生命周期钩子(如 componentDidXXX)和hook(如useLayoutEffect、useEffect)

各阶段的工作

  • before mutation阶段

整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理,

  1. 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑
  2. 调用getSnapshotBeforeUpdate生命周期钩子
  3. 调度useEffect
  • mutation阶段

mutation阶段也是遍历effectList,执行函数。这里执行的是commitMutationEffects

  1. 根据ContentReset effectTag重置文字节点
  2. 更新ref
  3. 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating)
  • layout阶段

与前两个阶段类似,layout阶段也是遍历effectList,执行函数。 具体执行的函数是commitLayoutEffects

  1. commitLayoutEffectOnFiber(调用生命周期钩子和hook相关操作):componentDidMount、componentDidUpdate、useEffect的销毁和回调(先高度,layout结束后再异步执行)、useLayoutEffect回调(mutation阶段调用useLayoutEffect的销毁函数、layout阶段同步执行回调)
  2. commitAttachRef(赋值 ref)

结束:root.current = finishedWork;

3.3 Diff算法
  • 只对同级元素进行Diff,如果一个DOM节点在前后两次更新跨越层级,React则不利用它。
  • 两个不同类型的元素产生不同的树,如果元素由p变成p,React会销毁div及其子孙节点,并新建p及其子孙节点
  • 开发者可通过key prop来暗示哪些子元素在不同的渲染下能保持稳定。

reconcileChildFibers

  • 当newChild类型为object、number、string,代表同级只有一个节点 - reconcileSingleElement
  • 当newChild类型为Array,同级有多个节点 - reconcileChildFibers

参见文-章:

Build your own React (pomb.us)

React技术揭秘 (iamkasong.com)

注:以上,如有不合理之处,还请帮忙指出,大家一起交流学习~

相关推荐
bloxed23 分钟前
前端文件下载多方式集合
前端·filedownload
余生H29 分钟前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC37 分钟前
CSS(四)display和float
前端·css
cwtlw41 分钟前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
谢道韫6661 小时前
今日总结 2024-12-24
javascript·vue.js·elementui
一朵好运莲1 小时前
React引入Echart水球图
开发语言·javascript·ecmascript
米奇妙妙wuu1 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖1 小时前
React 生命周期完整指南
前端·react.js
梦境之冢2 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http