[深入24] Fiber01

985 阅读21分钟

浏览器.png

Fiber.png

 React.createElement.png

导航

[react] Hooks

[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber

[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon

(一) 前置知识

(1) 一些单词

thread 线程
process 进程
composite 合成

优先级 highest高 medium中 low低

heap 堆
stack 栈 // heap stack 的区别
phase 阶段


reconciliaction 调和
reconciler 调和器
// 旧版本的叫法:stack reconciler
// 新版本的叫法:fiber reconciler

scheduler 调度器
synchronous 同时 同步

obtain 获得
intellisense 智能感知
framework 架构
try it out 试试看
reserve 保留的 // reserved_props
enumerable 可枚举
descriptor 描述符
therefore 因此 所以
alternate 候补者,交替 // fiber.alternate
draft 草稿

priority 优先级
concurrent 并发
immediate 立即的

(2) 进程和线程

  • 进程
    • 正在执行的应用程序
  • 线程
    • 应用程序中的 ( 代码执行器 )
  • 进程和线程的关系
    • 线程跑在进程中,一个进程可能有多个线程,而一个线程只能属于一个进程
  • 进程和进程的关系
    • 进程和进程之间,因为内存空间不一样,所以相互独立,互不影响
    • 一个进程挂掉,其他进程不会受到影响
  • 浏览器中的进程和线程
    • 进程线程内存
      • 浏览器是多进程的 ( 可以理解为一个标签页就是一个进程,具有多个进程 ),进程启动后,( cpu ) 就会给 ( 该进程 ) 分配 ( 内存空间 ) ,当进程得到内存空间后,就可以使用 ( 线程 ) 进行 ( 资源调度 ),进而完成功能
    • 进程和内存
      • 浏览器每 ( 新建一个进程 ),cpu都会给该进程分配一个 ( 新的独立的内存空间 ),( 不会与原来的进程共用一个内存空间 ),那么就会有一个问题,进程之间是需要通信的,从而才能传递数据,那么进程之间是如何通信的呢?
      • 进程和进程之间,因为内存空间不一样,所以相互独立,互不影响
    • 进程之间的通信
      • ( 进程 ) 之间需要 ( 通信 ),可以通过 ( IPC ) 机制来进行
      • ipc: inter process communication image.png
  • Chrome浏览器中的进程
    • 浏览器是多进程的应用程序
    • chrome中的主要进程
      • 浏览器进程
        • 主要就是浏览器本身的功能:负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作
      • 渲染进程
        • 负责一个tab标签页面的相关工作,也称渲染引擎
      • 插件进程
        • 浏览器插件功能
      • GPU进程
        • 负责视频等GPU任务
    • 进程之间的关系
      • 浏览器进程 => 渲染进程 => 插件进程 => GPU进程
      • 当在输入url返回资源后,浏览器进程会通知渲染进程进行解析,解析完成后得到图像帧 => 渲染进程会通知GPU进程将其转化为图像,并在屏幕上显示
    • chrome为啥要采用多线程
      • 一个tab挂掉,其他的tab不受影响,即一个进程挂掉不会影响其他进程
      • 浏览器针对不同进程做了不同的权限,从而保证安全性和沙河性
      • 更快的响应速度,不会像单进程那样抢夺cpu资源

(3) 浏览器的渲染进程

导航过程完成后,浏览器拿到响应数据后,( 浏览器进程 ) 会把 ( 数据 ) 交给 ( 渲染进程 ),渲染进程负责 ( tab内的所有事情 ),主要任务就是将 ( html/css/js) 转化成 web页面

  • 渲染进程中包含的线程
    • 一个主线程 ------------------ main thread
    • 一个合成线程 ---------------- compositor thread
    • 多个工作线程 ---------------- work thread
    • 多个光栅线程 ---------------- raster thread
    • 不同线程有不同的工作职责

(4) 浏览器的渲染过程!!!!!!!!!!!!!!!!!!!!!!!!!!!

  • 总顺序

      Parse HTML => Parse Stylesheet => Evaluate Script => Layout => Paint => Composite
    
  • (1) 浏览器根据 ( 响应类型 ) 来解析文件 - html解析 - DOM构建 -------------- Parse HTML

    • 如果响应类型是 ( Content-Type: text/html ) 的话,浏览器会用 ( html解析器将html解析成DOM树 )
    • html => html parser => dom tree
    • ( dom tree ) 是典型的 ( 栈结构 )
  • (2) css的解析 - 样式计算Style calculation - Parse Stylesheet -------- Parse StyleSheet

    • 主线程在解析页面时,遇到 ( style link ) 标签就会调用css解析器,将css解析成cssom tree,生成样式
    • 如果页面没有设置过样式,也会提供默认样式
  • (3) js的解析 --------------------------------------------------------------- Evaluate Script

    • 遇到 ( script ) 标签,浏览器会调用js解析器解析js
    • js的 ( 加载和执行 )都会阻塞DOM解析,因为js代码可能改变DOM,如果在script中添加了 (async defer) 就会是异步加载,即加载都不会阻塞DOM,但是执行都会阻碍DOM
    • defer异步加载,dom渲染完后才会执行,保证顺序
    • async异步加载,加载完后就会立即执行,不能保证顺序
  • (4) 布局Layout ----------------------------------------------------------- Layout

    • DOM树和样式计算完成后,还需要知道每个节点的位置,布局就是找到位置的过程
    • 主线程会根据 ( DOM tree ) 和 ( cssom tree ) 合成 ( render tree ),render tree包含了节点的 ( 坐标信息 ) 和 ( 盒子模型 ),最终生成 ( layout tree )
      • 遍历过程会跳过隐藏的元素display: none
      • 伪元素虽然不在dom-tree中,但是在render-tree中
    • layout分为 ( 全局的 ) 和 ( 局部的 )
      • 全局
        • 对整棵树进行布局
        • 比如修改浏览器尺寸,或者修改跟元素的大小,位置,字体等
      • 局部
        • 对部分进行重新布局
  • (5) 绘制Paint ---------------------------------------------------------- Paint

    • layout布局之后,我们知道了元素的 ( 结构,样式,几何关系 ),需要绘制一个页面就要知道每个 ( 节点的绘制先后顺序 )
    • 在Paint阶段,主线程会便利layout tree,生成一系列的绘画记录Paint records
    • 分层
      • 为了保证重绘的速度比初始绘制的速度快,屏幕的绘图通常分解成数层 ( 分层 ),如果发生这种情况,通常需要进行composite合成
    • Layer tree
      • 绘制可以将布局中的元素分解为多层,为了确定哪些元素要放在同一层,需要遍历render-tree来创建一个新的 ( Layer tree )
      • 添加了 will-change 的css属性的元素,会单独作为一层
      • 没有添加 will-change的css属性的元素,浏览器会根据情况决定是否把该元素放在单独的层
    • 光栅化
      • 一旦layer-tree被创建,( 渲染顺序被确定 ),主线程会把这些信息通知给 ( 合成器线程 ),合成器线程就会对每一层进行 ( 光栅化 )
  • (6) 合成Compositing -------------------------------------------------- Composite Layout

    • 当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容
    • 合成是在 ( 合成线程 ) 中进行的,不涉及 ( 主线程 ),因此合成线程不需要等待css和js的执行
    • 文档结构,节点样式,节点的结合关系,绘画顺序都知道了,最后我们需要绘制一个页面,将这些所有的信息转成像素,这个过程叫 ( 光栅化 )

image.png

(5) 浏览器的 16ms 渲染帧

  • 一些概念
    • 渲染帧
      • 渲染帧是指浏览器的一次完整绘制过程
    • 帧之间的时间间隔 16ms
      • 帧之间的时间间隔,是 ( DOM视图更新的最小间隔 )
        • 由于主流浏览器的刷新率是 ( 60hz ),那么渲染一帧的时间就必须控制在 ( 16ms ) 内才能保证 ( 不掉帧 ),也就是说每一次渲染都要保证在16ms内完成渲染在能保证页面流畅,才不会卡顿
      • 如果测量渲染间隔?
        • requestAnimationFrame
          • window.requestAnimationFrame() 告诉浏览器你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
          • 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
    • 浏览器每帧的生命周期 ( 即每帧需要完成的事情 )
      • 1.处理用户的交互,即输入事件
        • 阻塞输入事件:touch wheel ...
        • 非阻塞输入事件:click keypress ...
      • 2.js
        • 定时器
      • 3.开始帧 - begin frame
        • window.resize
        • scroll
        • mediaquery changed
      • 4.requestAnimationFrame
      • 5.layout
      • 6.paint
      • 7.compisite image.png
  • 重要结论
    • 一个渲染帧内的多次commit的DOM改动会被合并渲染
    • 耗时js会造成丢帧
    • 避免交错读写样式可以提高渲染效率

(6) 加载css会造成阻塞DOM树的解析和渲染吗?

  • css不会阻塞DOM树的解析
  • css会阻塞DOM树的渲染
  • css会阻塞js的执行,因为js要等待css加载完毕才能保证js可以操作样式
  • 总结
    • css不会阻塞DOM-tree的解析,但是css会阻塞DOM-tree的渲染,css还会阻塞js执行,因为js需要等到css解析完成后,js才能操作样式,本质上因为html,css,js都是在主线程中执行的,三者是互斥关系
    • dom渲染依赖于 => js执行完毕 => js执行依赖于 => css执行完毕 image.png

(7) React15存在的问题

当存在大量的js计算时,当时间超过了16ms时页面没法得到及时更新,就会出现卡顿

  • 当 ( setState ) 时,React会遍历应用的所有节点,找出差异,再更新页面,整个过程 ( 不能被打断 ),如果元素过多,整个处理过程就可能超过16ms,造成掉帧,出现卡顿
  • dom-diff过程
    • dom-diff也是如此 ( 递归对比 )
      • 节点树很庞大时,会导致 ( 递归调用 ) 的 ( 函数调用栈 ) 越来越深
      • 整个diff过程不能被 ( 中断 ),页面会等待递归调用完成后才重新渲染

(8) createElement 和 ReactElement 源码

 React.createElement.png

  • 文件位置:package/react/src/ReactElement.js
  • 在react写jsx语法,是不能直接被浏览器解析的,需要经过babel编译成js,编译后会转成React.createElement()这样的形式
    • jsx => 编译成函数式语法 - 如下图 image.png

(8.1) createElement

  • createElement源码分析 - 仓库地址
  • createElement的执行顺序
    • 从里到外
    • 从上到下
    • 最终到根时就创建一个ReactElement tree
    • 案例:比如function a(){return <div><p></p></div>}
    • 结果:是先创建p的ReactElement,在创建div的ReactElement
  • 注意点
    • 如果type是一个组件的时,首字母一定要大写,不大写type会被当成一个字符串,而不会当成class或function来处理
createElement
-------
export function createElement(type, config, children) {
   ...
   return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props,
   );
}
// React.createElement() 返回一个ReactElement对象,即特定格式的js对象

(8.2) ReactElement

ReactElement
------- 
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // $$typeof 这个标签允许我们唯一地将其标识为React元素
    // const symbolFor = Symbol.for;
    // REACT_ELEMENT_TYPE = symbolFor('react.element');
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    // element的内置属性
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    // 记录负责创建此元素的组件
    _owner: owner,
  };
  ...
  return element;
}

(9) React.Children

  • 文件位置:package/react/src/React.js
const Children = {
  map, // 其实就是 mapChildren() 函数 => 在children里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg,直接子节点是null和undefined直接返回null和undefined,否则返回一个数组
  forEach,
  count, // 返回 children 中组件的总数量,等同于 map 和 forEach 循环的次数
  toArray, // 将 children 这个复杂的数据结构以 ( 数组 ) 的方式 ( 扁平展开并返回 ),并为每个子节点分配一个 ( key ) => 可以为自节点排序等
  only, // 验证 children是否只有一个子节点,true则返回这个节点,false则报错
};

(二) Fiber

(2.0) Fiber 中的一些概念

(2.0.0) react应用执行的整体过程

  • render阶段
    • 会分别为节点执行 beginWorkcompleteWork
    • 计算 state,对比节点差异,为节点赋值相应的 effectTag ( 即DOM节点的增删改查 )
    • 在render阶段的结尾,会形成 effectList 链表,该链表中的所有fiber节点都是带有副作用的
    • ----------- reconciler调和器 - 在render阶段执行 -------------
  • commit阶段
    • 遍历 effect-list ,执行对应的DOM操作,或执行部分生命周期钩子函数
    • ----------- renderer渲染器 - 在commit阶段执行 ----------------
  • 执行过程总结:
    • 1.不同的任务在 ( scheduler调度器中进行优先级排序 ) --------------------- render阶段
    • 2.优先级更高的任务,会优先进入 ( reconciler调和器中 ) ------------------- render阶段
    • 3.reconciler处理完后的任务,会进入 ( renderer渲染器中渲染成真实的ui ) -- commit阶段 image.png

(2.0.1) scheduler 调度器

  • 核心作用
    • 为了达到使 fiber-reconciler 每执行一段时间就把控制权交回给浏览器,我们就需要一个 ( 调度器Scheduler ) 来进行 ( 任务分配 ),( 排序优先级,让优先级更高的任务先进行reconciler )
    • react15没有scheduler,任务都没有优先级,也不能被中断,不能排序,只能一次性同步执行
  • 优先级通过什么来表示?
    • 在 ( scheduler ) 中每个任务的 ( 优先级 ) 都是通过 ( 过期时间 ) 来表示的
    • ( 过期时间 ) 距离 ( 现在的时间 ) 越近,表示 ( 优先级越高 )
  • timerQueue 和 taskQueue
    • timerQueue
      • 存放 ( 没有过期的任务 )
    • taskQueue
      • 存放 ( 已经过期的任务 )
    • 小顶堆
      • 小顶堆也叫小根堆
      • 小顶堆:即在 ( 完全二叉树 ) 中 ( 每个节点的值都小于或等于其左右孩子的值 )
      • timerQueue和taskQueue都是 ( 小顶堆 ),所以取出来的任务都是距离现在时间的过期时间最短的任务,即 ( 取出的任务都是优先级最高的任务 ),然后优先执行它
  • 优先级高的任务 (比如:键盘输入) 可以打断优先级低的任务 (比如:diff)
      1. ( 输入事件 ) - 阻塞输入事件touch,wheel;非阻塞输入事件click,keypress
      1. ( 定时器 ),检查定时器是否到点,到点则执行回调函数
      1. ( 开始帧 ) Begin Frame,即每一帧的事件,包括 ( window.resize ) ( scroll ) ( media )
      1. ( 请求动画帧 ) requestAnimationFrame,即在每次绘制前,会执行requestAnimationFrame的回调
      1. ( layout )
      1. ( paint )
      1. ( composite )
// 源码位置:packages/react-reconciler/ReactFiberLane.new.js

// 优先级越高,位数就越少
// 优先级越低,位数就越高
// 可以用赛道比喻,如果一直占用内道,那就越快
// 这里的所有任务 ( 从上往下 ) 优先级越低,比如 SyncLane > InputContinuousHydrationLane

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000000100;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000000010000;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;

image.png

(2.0.2) reconciler 调和器

  • 核心作用
    • 找出哪些节点发生了改变,并打上不同的tag
    • 在render/reconciliation阶段过程中,让优先级更高的任务先执行,该过程可以被打断
    • 在commit阶段一次性的批量更新,该过程不能被打断
  • reconciler执行时机
    • reconciler发生在 ( render ) 阶段
    • ( render阶段 ) 再次划分为两个阶段
      • beginWork
      • completeWork
  • reconciler
    • 旧版中的reconciler => stack reconciler ------------ 递归
    • 新版中的reconciler => fiber reconciler ------------ 循环
  • stack reconciler
    • stack reconciler 不能被打断
  • fiber reconciler
    • fiber reconciler 每执行一段时间都会将控制权交回给浏览器
      • fiber reconciler 在执行过程中可分为 ( 两个阶段 )
        • 阶段一 ( render/reconciliation )
          • 生成fiber树,得出需要更新的节点信息,可以被打断,让优先级高的任务先执行
          • 通过 scheduler 执行器来完成
        • 阶段二 ( commit )
          • 将需要更新的节点一次批量更新,不能被打断

(2.0.3) renderer 渲染器

  • 核心作用
    • 将reconciler中打好标签的节点,渲染到视图上
  • renderer渲染器执行时机
    • renderer是commit阶段执行的

(2.0.4) currentTree 和 workInProgressTree

  • currentTree
    • 第一次渲染后,react最终得到一个fiber-tree,它反映了用于渲染的ui的应用程序的 ( 状态 ),这颗树叫 ( current-tree )
  • workInProgressTree
    • react开始处理更新时,他会根据fiber-current-tree和virtual-tree生成 ( workInProgressTree ),它 反映了要刷新到屏幕的 ( 未来状态 )
    • 所有任务都在fiber-workInProgressTree的fiber上执行,当react遍历current-tree时,对于每个现有fiber节点,他会使用render方法返回react元素中的数据,创建一个备用节点(fiber=alternate),这些节点用于构成 workInProgressTree(备用 tree),处理完更新并完成相关工作后,react将备用tree刷新到屏幕,一旦这个workInProgressTree在屏幕上呈现,就会变成current-tree
    • 每个 ( fiber ) 节点,都会通过 ( alternate ) 属性保持对另一个树对应节点的引用
      • currentTree中的节点指向workInProgressTree的备用节点,反之亦然 image.png

(2.0.5) FiberRootNode 和 rootFiber

  • fiberRoot
    • fiberRoot指整个应用的根节点,有且只有一个
    • fiberRootNode的current指针指向完成了的workInProgressTree,current指向workInProgressTree后就变成了currentTree
  • rootFiber
    • React.render() 创建出来的节点,可以有多个
  • 两者的关系
    • fiberRootNode --> ( current ) --> rootFiber
    • rootFiber --> ( stateNode ) --> fiberRootNode

(2.0.6) fiber对象

  • 文件位置:packages/react-reconciler/src/ReactFiber.new.js
  • 每一个react元素,对应一个fiber对象,一个fiber对象通常表示一个work的基本单元,fiber对象有几个属性,这些属性指向其他的fiber对象
    • child sibling return
      • child指向子节点
      • sibling指向兄弟节点
      • return指向父节点
    • fibers可以理解为
      • ( 一个包含react元素上下文信息的 - 数据域节点)
      • ( 以及由child,sibling,return等 指针域构成的 - 链表结构 )
    • fiber可以保存真实的DOM
      • 通过fiber对象的 ( stateNode ) 属性保存着真实的DOM信息
    • 遍历
      • 遍历时,深度优先原则,先children,再sibling,再找叔叔 image.png

(2.1) Fiber的特点

  • 可以 ( 暂停 ) 工作,并在之后 ( 返回 ) 再次开始
  • 可以为不同类型的工作,设置 ( 优先级 )
  • ( 复用 ) 之前已经完成的工作
  • ( 终止 ) 已经不再需要的工作

(2.2) Fiber reconciler 执行的阶段

(1) 阶段一 ------------ reconciliation阶段 ( 调和render )

  • 进行diff运算,生成fiber树,可以被打断

  • ( fiber树 ) 是在 ( virtualDOM树 ) 的基础上添加 ( 额外信息 ) 生成,本质上是一个 ( 链表 )

  • fiber树

    • ( current树 ) 和 ( workInProgress树 ) 被称为 ( fiber双缓存 )
    • current树
      • fiber树的生成 ( 当前树 )
      • fiber树在首次渲染时会一次性生成
    • workInProgress树
      • fiber树的新生成 ( 工作进程树 )
      • 在fiber树在首次渲染一次性生成后,在后续需要 ( diff ) 的时候,遍历fiber-current树时,会为每一个存在的fiber节点创建一个 ( 替代节点 ),这些替代节点节点组成的树就是 ( workInProgressTree )
      • 这颗新树,每生成一个新的节点,都会把控制权交回给浏览器的主线程,去检查有没有优先级更高的任务需要执行
        • 如果没有---优先级更高的任务,则继续构建
        • 如果有-----优先级更高的任务,fiber reconciler 会丢弃正在生成的树,在空闲时再从新生成一遍
      • 在构建fiber树的过程中,( fiber-reconciler ) 会将 ( 需要更新的节点信息 ) 保存在 ( effect list ) 中,在 ( 阶段二 ) 执行的时候,( 批量的更新 ) 相应的节点
    • currentTree和workInProgressTree如何关联?
      • ( current树 ) 和 ( workInProgree树 ) 通过 ( alternate ) 相连
    • 为什么要生成currentTree和workInProgressTree??
      • 主要原因是为了避免 ( 更新丢失 )
  • scheduler

    • 如何才能使 fiber-reconciler 每执行一段时间就把控制权交回给浏览器?
    • 需要一个 ( scheduler ) 调度器,来完成 ( 任务分配 ),可以简单的理解为 ( 在什么时候确定执行work的过程 )
    • 优先级高的任务 (比如:键盘输入) 可以打断优先级低的任务 (比如:diff)
        1. ( 输入事件 ) - 阻塞输入事件touch,wheel;非阻塞输入事件click,keypress
        1. ( 定时器 ),检查定时器是否到点,到点则执行回调函数
        1. ( 开始帧 ) Begin Frame,即每一帧的事件,包括 ( window.resize ) ( scroll ) ( media )
        1. ( 请求动画帧 ) requestAnimationFrame,即在每次绘制前,会执行requestAnimationFrame的回调
        1. ( layout )
        1. ( paint )
        1. ( composite )
  • Effect list

    • effect list 可以理解为是一个 ( 存储effectTag副作用的列表容器 )
    • 它是由 ( fiber节点 ) 和 ( nextEffect指针 ) 构成的 ( 单向链表 ),还包括第一个节点的 ( firstEfflect ) 和 最后一个节点的 ( lastEfflect )
    • react采用 ( 深度优先 ) 搜索算法,在render阶段遍历fiber树时,把 ( 每个有副作用的fiber筛选出来 ), ( 有副作用的意思是比如:有更新有改动 ),即把有变化的节点筛选出来,最后构建生成一个 ( 只带副作用的effect list 链表 )
    • 在commit阶段,react拿到effect list节点后,遍历 effect list 节点,并且根据每一个 effect 节点的 ( EffectTag类型 ),从而对相应的 dom 树进行修改 image.png image.png
  • 案例:阶段一(render/reconciliation阶段)

function App() {
  return (
    <div>
      xiao
      <p>chen</p>
    </div>
  )
}
ReactDOM.render(<App />, document.getElementById("root"));

image.png

  • 上图-图示说明
    • fiberRootNode 和 rootFiber
      • fiberRoot只有一个,表示整个应用的根节点
      • rootFiber可以有多个,通过React.render()生成
      • rootFiber就是一个fiber对象,其中的 stateNode 属性指向的是真实的DOM,即FiberRootNode
      • FiberRootNode是一个真实的DOM节点,通过 current 属性指向 rootFiber
    • currentTree 和 workInProgressTree
      • 两者通过 alternate 相连
      • fiber节点的 child指向子节点,sibling指向兄弟节点,return指向父节点
      • currentTree和workInProgressTree称为 ( fiber双缓存 )
    • fiberRootNode的current指针的指向
      • 正在创建的fiber树,我们叫做workInProgressTree
      • 当workInProgressTree创建完成后,fiberRootNode的current指针会从currentTree指向workInProgressTree,那么现在 workInProgressTree 就成了新的 currentTree

(2) 阶段二 ------------ commit阶段

  • 将阶段一中结算出来的 ( 需要更新的节点 ) ( 一次性的批量更新 ),该过程 ( 不能被打断 )

image.png

参考资料