React进阶之React焦点源码剖析(一)

打印 上一主题 下一主题

主题 824|帖子 824|积分 2472

cline + deeseek => AI工具,上手成本非常低,非常便宜
cursor 编辑器
  带着问题学源码

  • 为什么 react 会引入 fiber 架构
  • 简述 fiber 节点的布局和作用
  • fiber 架构 => 架构+流程
  • diff 算法
  • hooks 原理
学习方法论:由大到小 对比回答问题
react 特点


  • 单向数据流
    能够快速相应用户利用
    公式:ui = render(data)
什么缘故原由导致相应慢?
cpu卡顿,js执行导致画面卡顿
IO卡顿,网络问题等 延长
CPU卡顿

欣赏器一秒60hz,16.6ms革新一次,超过就会有掉帧征象
就比如这个例子
  1. // index.js
  2. import ReactDOM from "react-dom";
  3. import App from "./App";
  4. const rootElement = document.getElementById("root");
  5. // ReactDOM.render(<App />, rootElement);
  6. ReactDOM.createRoot(rootElement).render(<App />);
  7. // APP.js
  8. import "./styles.css";
  9. export default function App() {
  10.   const len = 3000;
  11.   return (
  12.     <ul>
  13.       {Array(len)
  14.         .fill(0)
  15.         .map((_, i) => (
  16.           <li>{i}</li>
  17.         ))}
  18.     </ul>
  19.   );
  20. }
复制代码
React是怎么解决的?
时间分片:把更新过程碎片化 优先级 应用更新 背景预渲染


  • 同步 阻塞 渲染
  • 异步 非阻塞 用户优先级渲染
16.8后提出的 concurrent mode
  1. ReactDOM.createRoot(rootEl).render(<App />)
复制代码
IO 卡顿

接口相应时间长,解决:


  • loading
  • suspence 兜底 fallback
  • error-boundary 错误兜底

新老 react 架构对比

v15 同步的,不可中断的
v16.8 异步,可中断
v18 ssr升级 流式 ssr stream,针对页面全部组件是分块,分局部输出,而不是等全部页面拼接组装好后一次性传输给客户端
页面上的差别接口,包裹成流式,然后传输给客户端,提升TTFB(首字节相应时间)
性能优化有需求,利用nextjs做提升
v15



  • Reconciler 协调器,diff 负责找出变化的组件

    • update 更新
    • component render jsx 渲染 => vdom
    • vdom diff
    • 找出变化的元素
    • 关照 renderer 渲染

通过递归的方式找出变化的组件
mount 阶段 => 调用 mountComponent
update 阶段 => 调用 updateComponent
递归更新子组件
=> 缺点:层级深,递归时间超过16ms


  • Renderer 渲染器,负责将变化的组件渲染到页面上

    • ReactDom.render
    • ReactNative.render

不可中断,中断则后续内容不执行
v16.8

多了一个 Scheduler 调度器


  • Scheduler 调度器 调度使命的优先级
  • Reconciler 协调器 负责找出变化的组件 递归=>可中断
  • Renderer 渲染器 是拿着Reconciler提供的标识 同步渲染
Scheduler 调度器

将大型使命分割成小使命,每一帧分配一定的时间执行小使命


  • 时间切片
  • 优先级调度

  • 每个工作单元,对应一个fiber节点
  • 时间分配
  • 调度循环 维护使命队列
  • 时间检查
  • 暂停与规复
  • 利用欣赏器API


  • requestAnimationFrame 用于在下一帧开始的时候执行回调函数
  • requestIdleCallBack 欣赏器空闲的时候执行回调函数 setTimeout 模拟了
Reconciler 协调器

  1. // 更新工作从递归变成了可以中断的循环过程。每次循环都会调用shouldYield判断当前是否有剩余时间。
  2. /** @noinline */
  3. function workLoopConcurrent() {
  4.   // Perform work until Scheduler asks us to yield
  5.   while (workInProgress !== null && !shouldYield()) {
  6.     workInProgress = performUnitOfWork(workInProgress);
  7.   }
  8. }
复制代码
React fiber

原理

react 内部实现的数据布局,支持状态更新,可中断可规复,规复后可以复用之前的中间状态


  • 架构:v15 stack reconciler,v16 fiber reconciler
  • 数据布局:每个 fiber 节点对应 react element,多个组件类型,dom节点各种属性数据
  • 动态的工作单元,改变的状态,要执行的工作
  1. function FiberNode(
  2.   tag: WorkTag,
  3.   pendingProps: mixed,
  4.   key: null | string,
  5.   mode: TypeOfMode,
  6. ) {
  7.   // Instance,静态节点的数据结构属性
  8.   this.tag = tag;
  9.   this.key = key;
  10.   this.elementType = null;
  11.   this.type = null;
  12.   this.stateNode = null;
  13.   // Fiber,用来链接其他fiber节点形成的fiber树
  14.   this.return = null;
  15.   this.child = null;
  16.   this.sibling = null;
  17.   this.index = 0;
  18.   this.ref = null;
  19.   // 作为动态的工作单元的属性
  20.   this.pendingProps = pendingProps; //即将应用到组件上新属性 相当于是新值newValue
  21.   this.memoizedProps = null; // 上次渲染时使用的属性值 相当于是旧值oldValue
  22.   this.updateQueue = null;
  23.   this.memoizedState = null;
  24.   this.dependencies = null;
  25.   this.mode = mode;
  26.   // 记录变化的节点 effectlist=>render
  27.   this.effectTag = NoEffect;
  28.   this.subtreeTag = NoSubtreeEffect;
  29.   this.deletions = null;
  30.   this.nextEffect = null;
  31.   // effectslist链 = firstEffect -> nextEffect -> nextEffect -> lastEffect => 交给render => 渲染
  32.   this.firstEffect = null;
  33.   this.lastEffect = null;
  34.   // 作为调度优先级的属性
  35.   this.lanes = NoLanes;
  36.   this.childLanes = NoLanes;
  37.   // 指向该fiber在另一次更新时对应的fiber
  38.   this.alternate = null;
  39.   if (enableProfilerTimer) {
  40.     // Note: The following is done to avoid a v8 performance cliff.
  41.     //
  42.     // Initializing the fields below to smis and later updating them with
  43.     // double values will cause Fibers to end up having separate shapes.
  44.     // This behavior/bug has something to do with Object.preventExtension().
  45.     // Fortunately this only impacts DEV builds.
  46.     // Unfortunately it makes React unusably slow for some applications.
  47.     // To work around this, initialize the fields below with doubles.
  48.     //
  49.     // Learn more about this here:
  50.     // https://github.com/facebook/react/issues/14365
  51.     // https://bugs.chromium.org/p/v8/issues/detail?id=8538
  52.     this.actualDuration = Number.NaN;
  53.     this.actualStartTime = Number.NaN;
  54.     this.selfBaseDuration = Number.NaN;
  55.     this.treeBaseDuration = Number.NaN;
  56.     // It's okay to replace the initial doubles with smis after initialization.
  57.     // This won't trigger the performance cliff mentioned above,
  58.     // and it simplifies other profiler code (including DevTools).
  59.     this.actualDuration = 0;
  60.     this.actualStartTime = -1;
  61.     this.selfBaseDuration = 0;
  62.     this.treeBaseDuration = 0;
  63.   }
  64.   if (__DEV__) {
  65.     // This isn't directly used but is handy for debugging internals:
  66.     this._debugID = debugCounter++;
  67.     this._debugSource = null;
  68.     this._debugOwner = null;
  69.     this._debugNeedsRemount = false;
  70.     this._debugHookTypes = null;
  71.     if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
  72.       Object.preventExtensions(this);
  73.     }
  74.   }
  75. }
复制代码

举个例子:
  1. import React, { Component } from 'react';
  2. class Header extends Component {
  3.     render() {
  4.         return <h1>{this.props.title}</h1>;
  5.     }
  6. }
  7. function Content(props) {
  8.     return (
  9.         <div>
  10.             <p>{props.text}</p>
  11.             <Footer />
  12.         </div>
  13.     );
  14. }
  15. class Footer extends Component {
  16.     render() {
  17.         return <footer>Footer Content</footer>;
  18.     }
  19. }
  20. class App extends Component {
  21.     render() {
  22.         return (
  23.             <div>
  24.                 <Header title="Welcome to My App" />
  25.                 <Content text="This is some example content." />
  26.             </div>
  27.         );
  28.     }
  29. }
  30. export default App;
复制代码
Fiber布局:
Root Fiber Node
└── Type: “div” (elementType: “div”)
├── Child Fiber Node
│ ├── Type: “Header” (elementType: Header)
│ │ └── State Node: Header instance
│ │ └── Props: { title: “Welcome to My App” }
│ └── Sibling: Content Fiber Node
└── Child Fiber Node
├── Type: “Content” (elementType: Content)
│ └── State Node: null (函数组件没有状态节点)
│ └── Props: { text: “This is some example content.” }
│ └── Child Fiber Node
│ ├── Type: “Footer” (elementType: Footer)
│ │ └── State Node: Footer instance
│ │ └── Props: {}
│ └── Sibling: null
更新dom

双缓存机制
内存中绘制当前的 fiber dom,绘制完后直接替换上一帧的fiber dom,如许省去两帧之间替换的计算时间,就不会存在白屏的情况,因此就有两棵fiber树


  • current fiber 屏幕上正在显示的内容
  • workingprogress fiber 内存中正在构建的树 简称 wip fiber
alternate 毗连


mount 构建过程



  • 应用级别的节点 ReactDom.render 创建 fiberRootNode
  • rootFiber 组件树的根节点

render阶段 — scheduler reconciler

通过遍历 找到全部的fiber布局 实现可中断的异步递归

  • 递 => 生成树
    创建节点,形成节点之间的关系
    vdom
从 rootfiber 深度优先遍历 fiber 调用 beginwork


  • 根据传入的 fiber 节点创建子 fiber 节点,毗连两个 fiber 节点
  • 遍历叶子节点,进入归的阶段


  • 调用completework


  • 创建真实的dom节点
  • 将当前节点下的子节点挂载到当前节点上
  • 收集当前节点的effectlist
递 归 交错执行,直到归到rootFiber

react源码剖析

react github
react-dom


react-dom/src/client/ReactDOMRoot.js



最终返回ReactDOMRoot这个实例
ReactDOMRoot 和 ReactDOMHydrationRoot 上面都挂载 render 方法
吸收children
调用updateContainer方法,传入children,updateContainer来举行递归的这个阶段,创建当前节点

后面还挂载了unmount方法

react-reconciler

react-reconciler/src/ReactFiberReconciler.js

这里着重是 Scheduler
创建FiberRootNode节点绑定root
render渲染方法传入children(App),调用updateContainer方法,举行大使命拆分小使命,优先级调度

react-reconciler/src/ReactFiberWorkLoop.js

着重先容循环创建fiber树的方法
焦点方法:performUnitOfWork,workLoopConcurrent
workLoopSync 同步方法,不会判断 shouldYield,这是和workLoopConcurrent方法的区别

workLoopConcurrent方法是异步模式,都是调用performUnitOfWork构造fiber树

这里是Scheduler调度器将渲染使命拆分成差别的使命单元去创建对应的fiber,fiber通过performUnitOfWork去完成fiber单元的创建,然后通过shouldYield判断是否执行如许的使命
workInProgress是全局的变量,存储在全局
performUnitOfWork中记录当前“递”和“归”的一个过程,判断当前满足条件,进入beginWork
Reconciler 协调器阶段,创建fiber树,递归遍历,diff算法比力差别
performUnitOfWork中判断是否是开发情况,开发情况开启性能调优则计算执行时间
调用beginWork举行节点的递阶段,拆解组件内容,并且返回下一个组件
当深度遍历到最底层的时候,开始举行归的阶段
则next为空,调用completeUnitOfWork开始”归“的阶段,归回父节点,更新父节点状态
"递"和"归"阶段是交错执行的,直到回到rootFiber为止

react-reconciler/src/ReactFiberBeginWork.js


  • beginWork

    if 阶段:后续进入到diff的过程,非首次渲染
    否则为else阶段
后面:


  • updateHostComponent

  • reconcileChildren
    mount组件:创建新的子Fiber节点

react-reconciler/src/ReactChildFiber.js





通过调用useFiber创建fiber节点

react-reconciler/src/ReactFiber.js

找到workInProgress,为null则创建Fiber节点



不论是哪个方法,最终返回的都是 workInProgress.child下一个节点
react-reconciler/src/ReactFiberWorkLoop.js

performUnitOfWork中
上述 beginWork返回的是workInProgress.child下一个节点,因此next就会发生变化
next为null时候,则叶子节点为空,调用completeUnitOfWork

next不为空,则将next指针赋值给workInProgress,修改workInProgress指向,重新执行beginWork
在 completeUnitOfWork 中,创建对应的dom元素,假如sibling不为null,然后创建对应的指针

commit阶段 同步阶段

effectlist


  • before mutation 阶段,执行 dom利用前
  • mutation 阶段,执行dom利用阶段,遍历effectlist,执行mutation
  • layout 阶段,执行dom利用后,绘制
可以自己写一个实例,然后打断点看利用数据,利用结果

一句话来总结commit阶段所作的事变:
基于链表的方式存储副作用,并根据优先级执行这些更新,直至全部的更新完成。

  • React遍历fiber树并将必要执行副作用的节点以链表的情势收集起来
  • 根据优先级存储更新
  • 递归执行更新

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表