react18.2.0

在 18 之前,只有在react事件处理函数中,才会自动执行批处理,其它情况会多次更新
在 18,任何情况都会自动执行批处理,多次更新始终合并为一次
hydrate 变为hydrateRoot render改为createRoot().render()
flushSync 退出批量更新,但是内部仍然是批量更新
react17 和 react18的区别就是:从同步不可中断更新变成了异步可中断更新。
useInsertionEffect

在dom生成之后,useLayoutEffect之前,它的工作原理大致合useLayoutEffect相同,只是此时无法访问DOM节点的引用,一般用于提前注入脚本。

fiber架构的理解

15架构采用的是递归 遇到长任务会阻塞 造成用户卡顿
fiber是react现在的链式数组结构  执行异步的调度任务会在宏任务中执行,这样可以保证,不会让用户失去响应
解决的问题
  • 增量渲染。将渲染工作拆分成多个时间片段执行,使得每个时间片段都有机会插入其他优先级更高的任务,保证页面响应性的同时尽可能快地完成渲染工作。
  • 优先级调度。引入了任务优先级的概念,根据任务的紧急程度和重要性对任务进行优先级排序,确保优先级较高的任务能够尽早得到处理,提高用户交互的流畅度。
  • 可中断和恢复。支持任务的中断和恢复,允许在渲染过程中处理更高优先级的任务,以确保更及时地响应用户操作。
  • 更好的错误处理。每个Fiber都有自己的错误边界,可以捕获并处理组件树中发生的错误,并在不崩溃整个应用程序的情况下进行优雅降级。

useEffect 钩子的工作原理涉及到 React 的渲染流程和副作用的调度机制。以下是其工作原理的详细说明:
1、调度副作用:当你在组件内部调用 useEffect 时,你实际上是将一个副作用函数及其依赖项数组排队等待执行。这个函数并不会立即执行。
2、提交阶段(Commit Phase):React 渲染组件并且执行了所有的纯函数组件或类组件的渲染方法后,会进入所谓的提交阶段。在这个阶段,React 将计算出的新视图(新的 DOM 节点)更新到屏幕上。一旦这个更新完成,React 就知道现在可以安全地执行副作用函数了,因为这不会影响到正在屏幕上显示的界面。
3、副作用执行:提交阶段完成后,React 会处理所有排队的副作用。如果组件是首次渲染,所有的副作用都会执行。如果组件是重新渲染,React 会首先对比副作用的依赖项数组:如果依赖项未变,副作用则不会执行;如果依赖项有变化,或者没有提供依赖项数组,副作用会再次执行。
4、清理机制:如果副作用函数返回了一个函数,那么这个函数将被视为清理函数。在执行当前的副作用之前,以及组件卸载前,React 会先调用上一次渲染中的清理函数。这样确保了不会有内存泄漏,同时能撤销上一次副作用导致的改变。
5、延迟副作用:尽管 useEffect 会在渲染之后执行,但它是异步执行的,不会阻塞浏览器更新屏幕。这意味着 React 会等待浏览器完成绘制之后,再执行你的副作用函数,以此来确保副作用处理不会导致用户可见的延迟。
通过这种机制,useEffect 允许开发者以一种优化的方式来处理组件中可能存在的副作用,而不需要关心渲染的具体时机。退出清理功能确保了即使组件被多次快速创建和销毁,应用程序也能保持稳定和性能。

只要后续更新中不改变hooks的执行顺序,hooks可以写在if条件语句中

生命周期

static getDerivedStateFromProps(nextProps,prevState)

shouldComponentUpdate()

render()

componentDidMount()

getSnapshotBeforeUpdate(prevProps,prevState)

componentDidUpdate(prevProps,prevState,snapshot)

componentWillUnmount()

Static getDerivedStateFromError() 更改状态降级组件

componentDidCatch()

7个生命周期 2个静态方法

React事件和原生事件执行顺序

react原生事件

react合成事件

root上挂载事件

react事件是怎么注册的

React 代码执行时,顶层会自动执行事件的注册,初始化事件插件。
React 首次渲染时,会在根节点上绑定所有原生事件。支持冒泡的事件,
React 会同时绑定捕获阶段和冒泡阶段的事件;不支持冒泡的事件,会将事件绑定在具体 DOM 元素上。
事件触发前会从目标元素的 Fiber 节点向上收集同类型事件队列,构造合成对象,同类型的事件会复用同一个合成事件实例对象。
根据监听的事件阶段,决定顺序还是倒序遍历执行事件处理函数(模拟事件的冒泡捕获机制)。

React合成事件的优势:

抹平不同浏览器直接的差异,提供统一的API使用体验
通过事件委托的方式统一绑定和分发事件,有利于提升性能,减少内存消耗

react渲染流程:

Scheduler(调度器)根据优先级lanes区分不同的任务类型,其中同步任务立即同步执行在主线程执行,最快渲染出来
异步任务走Scheduler宏任务里执行的
通过 requestAnimationFrame + 超时时间 + messageChannel 控制那些任务优先进入 Reconciler
 
Reconciler(协调器)负责构建fibertree,找出变化的组件,标记组件的变化
react更新时会从fiberroot从上到下开始遍历,找到变化的节点,最终形成一颗fiberTree叫workInProgressTree
然后将fiberRootNode的current指向workInProgress 完成fiber tree的更新

lifecycle 生命周期阶段
调用生命周期方法

render渲染阶段
React会根据组件的状态变化、props的更新或者⽗组件的重新渲染等触发条件,重新执⾏组件的函数体(函数组件)或者render⽅法(类组件)。
当React执⾏函数组件或render⽅法时,它会检测组件中是否包含了Hooks,如果包含了Hooks,那么React会根据Hooks的顺序依次调⽤它们

commit 阶段
将render阶段生成的更新应用到真实的dom上,完成界面的渲染

useTranstion和useDeferredValue异同:

相同点: useDeferredValue本质上和内部实现与useTranstion一样都是标记成了过度更新任务。

不同点:useTranstion是把startTranstion内部的更新任务变成了过度任务transtion,而useDeferredValue是把原值通过过度任务得到新的值,这个值作为延时状态,一个是处理逻辑,一个是生产一个新的状态。

 

React Diff 会预设几个规则:

  1. 只对同级节点,进行比较
  2. 节点变化,直接删除,然后重建
  3. 存在key值,对比节点的key值

其中单节点Diff相对简单,包含以下流程:

  1. 首先会判断老的Fiber树上有没有对应的Fiber节点,若没有则说明是新增操作,直接在老Fiber树上新增节点并更新DOM
  2. 若老Fiber节点也存在,则判断节点上的key值是否相同,若不同则删除老节点并新增新节点
  3. key值相同,则判断节点的type是否相同,若不同则删除老节点并新增节点
  4. type值也相同,则认为是一个可复用的节点,直接返回老节点就行

多节点的Diff操作主要用于map返回多个相同节点的情况下,可以分为三种情况:新增节点、删除节点以及节点移动,React采用双重遍历的方式来进行三种情况的判断,流程如下:

  1. 第一轮遍历会依次将 children[i] 和 currentFiber 以及 children[i++] 和 currentFiber.sibling 进行对比,当发现节点不可复用时提前结束遍历
  2. 当第一轮遍历无提前结束时,说明所有节点都可以复用,直接返回老节点
  3. 若children遍历完成,currentFiber未完成,则说明是删除操作,需要对未完成的 currentFiber 兄弟节点标记删除
  4. 若children遍历未完成,currentFiber完成,则说明是新增操作,需要生成新的workInProgressFiber节点
  5. 若children和currentFiber都未完成,则说明是节点位置发送了变更,那就对剩余的currentFiber进行遍历,并通过key值找到每一个节点在children中对应的老节点,并将老节点中的位置替换为新节点的
整体流程分为四部:
1.将current 中所有同级fiber保存在map 中
2.遍历newChild数组,每个遍历到的element 如果map中存在对应的current fiber可以复用
3.判断是插入还是移动
4.最后map中剩下的都标记删除

第一层

React 15的渲染过程是用递归函数同步遍历整个虚拟DOM树,一旦开始就无法停止。
假设组件树中有1000个节点,递归函数需要一次性执行完所有节点的创建和更新。
如果总耗时是100毫秒,浏览器主线程被连续占用100毫秒,这期间无法响应用户的点击、键盘输入、动画帧。
用户就会感觉页面卡顿。核心问题是递归调用栈无法拆分和暂停,必须从头执行到尾

第二层

Fiber架构把树形结构转换成了链表结构,每个Fiber节点包含三个关键指针:
return指向父节点,child指向第一个子节点,sibling指向下一个兄弟节点
遍历时从根节点开始,优先向下找child,没有child就找sibling,没有sibling就通过return回到父节点再找父节点的sibling。
这种遍历方式每次只处理一个节点,处理完一个节点就可以检查时间是否用完,用完了就中断并保存当前节点位置,下次恢复时从中断点继续。
每个Fiber节点还存储了pendingProps、memoizedProps、memoizedState等字段,用于对比更新前后的变化。

第三层

React设计了三个层次的事件优先级。
最高优先级是离散事件,比如点击、输入框变化,需要立即响应。
中间优先级是连续事件,比如滚动、拖拽。最低优先级是空闲任务,比如网络请求回来的数据更新。
Scheduler调度器维护了一个任务队列,每次执行任务前检查当前时间是否还有剩余时间片,如果没有就交出主线程。
高优先级任务到达时,会打断正在执行的低优先级任务,低优先级任务被取消,然后重新开始执行高优先级任务。
这意味着低优先级任务已经构建的workInProgress树会被丢弃,等所有高优先级任务处理完后再重新构建。
为了避免用户感知到内容跳变,React需要确保commit阶段的内容是最终确定的。

第四层

current树代表当前屏幕上已经渲染的内容,workInProgress树是在内存中构建的新树。
首次渲染时,createRoot创建根节点,初始化current树。
调度更新时,复制current树的根节点创建workInProgress树,然后遍历workInProgress树处理更新。
所有更新计算完成后进入commit阶段。commit阶段分三步。
第一步before mutation,调用类组件的getSnapshotBeforeUpdate。
第二步mutation,执行DOM的增删改。第三步layout,调用useLayoutEffect和componentDidMount。
这三步不能中断,因为每一步都依赖前一步的执行结果。

commit完成后,WworkInPrpgress树成为新的current树。 第五层

Fiber架构引入后,渲染时会出现任务中断和恢复。
如果一个组件的生命周期方法里执行了同步代码,比如while循环或者巨大的数组遍历,仍然会阻塞渲染,因为任务拆分的粒度是组件级别,不是代码行级别。
正确的做法是把重计算拆分成多个小任务或用useDeferredValue延迟非紧急更新。
另外useLayoutEffect和componentDidMount在commit阶段同步执行,不能做耗时操作否则会抵消Fiber带来的性能优势

  

 

posted @ 2023-01-13 16:48  国服第一李师师  阅读(195)  评论(0)    收藏  举报