react源码记录

#createElement

18版本的createElement可通过配置enableRefAsProp,使得ref通过属性传递。此外不再暴露source、self的值

#cloneElement

可通过cloneElement为元素额外传递属性和children。

使用assign克隆props,如果值是引用类型,assign是浅拷贝;如果值是简单类型则是深拷贝。

#createRoot

react18使用createRoot挂在节点,将挂在元素暴露给用户,用户通过挂在节点调用render方法进行渲染。

const root = React.createRoot(document.getElementById('root'));

root.render(<App />);

优势:

  • 使用createRoot可以开启并行渲染
  • 如果多个组件挂载在同一个节点,可以使得挂在节点不用重复渲染
fiberRoot & rootFiber

fiberRoot只有一个,即挂载节点,是页面的入口,最顶层元素。

rootFiber是各种子组件,因此有多个。此外,一些组件可以使用render方法单独挂载渲染,也会导致fiber有多个。多个fiber使得组件的粒度更细,可以根据优先级进行渲染,提高性能。

useRef & createRef

useRef是Hook,只在初始化渲染的时候创建,后续不会重新创建。

createRef一般用于类组件。每次渲染都会重新创建。因此如果是函数组件,会导致每次重复创建

react执行过程

createFiberRoot -> createContainer -> root.render -> updateContainer -> createUpdate, requestLane ->scheduleUpdateOnFiber(创建workInProgress) -> performUnitOfWork -> didReceiveUpdate -> UpdateClassComponent(oldState, newState, props,isShouldUpdate), UpdateHookComponent...... -> beginWork (reconcileChildren) -> completeWork -> commitRoot

diff过程

diff的时候,会根据diff的类型是Element,Portal还是Array调用不同的方法。

删除:

新节点是单节点:首先会通过key判断是否是同一个节点,如果不是,则删除当前节点及其兄弟节点;是则判断type是否是同一个类型。如果相同,则删除其兄弟节点(不复用兄弟节点),然后复用当前的节点fiber,并赋值新的props;如果不同,则删除当前节点及其兄弟节点。删除的元素放在workInProgressFiber的deletions属性中。

如果当前child为空,则直接创建新的fiber节点并返回。

对于变动的节点,打上placement替换的标签

新节点是多节点数组:分为2次遍历。

第一次是遍历公共序列,即key相同的节点。对于key相同的节点,执行和单节点遍历时类似的操作,如果elementType不同,则删除;相同则使用新的props复用当前fiber。

第二次遍历是遍历key不同的序列。如果新节点为null了,则删除current剩余的节点。如果不为null。如果current和新节点都有剩余,则执行替换逻辑。

替换过程。使用lastPlacedIndex标志当前更新的最大位置。if 当前遍历的新节点在旧序列的Index  < lastPlacedIndex,则发生移动,并将该节点标记为placement,lastPlacedIndex的值不变。如果oldIndex >= lastPlacedIndex,则 lastPlacedIndex = oldIndex。

如果旧序列为null,则打标插入。

建立当前节点的fiber之后,再遍历其兄弟节点,重复上述操作。

completeWork阶段主要是做以下工作:

根据不同的组件类型调用不同的方法处理组件。如果是挂载阶段,创建组件实例,并挂载到根节点上,如果是更新阶段,则设置props,将props放在updateQueue中。最后设置subTreeFlags标记是否更新,并冒泡到父级root.finishWork.subTreeFlags,在commit阶段会根据该参数或flags判断是否有需要处理的副作用。

commitRoot阶段

在diff完了且对变动节点打标之后,需要将fiber节点更新到真是DOM中。该阶段一共分为2个部分,一个是commit之前的准备工作,一个是commit部分。

在执行commit之前,会先执行flushPassiveEffect将上次未处理完的useEffect副作用处理了。然后重置root状态。如果存在subTreeFlags或flags表明有更新,则建立scheduleCallBack宏任务异步调度flushPassiveEffect方法

commit分为beforeMutation,mutation,layout三个阶段:

beforeMutation: 主要是获取getSnapshotUpdate的快照,并清空root.containerInfo的值,为下次赋值做准备。

mutation: 对fiber节点进行增删改操作,插入到真正的dom中。执行unmount卸载逻辑

layout:执行useLayout方法,以及componentDIdMount等钩子函数,以及执行updateQueue中的逻辑,更新state

更新中断后如何恢复

fiber中有callBackNode属性,如果中断了,下次会从callBackNode取上次的回调方法继续执行

setState是宏任务还是微任务?

既不是宏任务也不是微任务,就是同步任务。之所以console.log时会表现出异步任务的状态,是因为setState会加入到更新队列中,在渲染完之后,批量执行更新。

react18的批处理特性

react18之前,只有react合成事件会批处理,将多次setState合并成一次更新。react18开始,所有事件均可以批处理,因为18的并发特性。通过createRoot创建的渲染fiber,可以对渲染进行暂停。

react17是使用batchUpdate函数,将isBatchingUpdate置为true,使得合成事件可以批处理。而对于setTimeout或dom原生事件等,根据事件循环机制,是在下一个循环中执行,因此react无法控制这些事件,无法标志为批处理。

Hook的setState会合并处理,只触发一次渲染。

useEffect是在渲染之后执行,因此不会导致重复渲染,除非useEffect中触发某些操作或事件才会导致重新渲染。

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值