导航
[封装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] 执行上下文
[深入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
- 进程线程内存
- 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的执行
- 文档结构,节点样式,节点的结合关系,绘画顺序都知道了,最后我们需要绘制一个页面,将这些所有的信息转成像素,这个过程叫 ( 光栅化 )
(5) 浏览器的 16ms 渲染帧
- 一些概念
- 渲染帧
- 渲染帧是指浏览器的一次完整绘制过程
- 帧之间的时间间隔 16ms
- 帧之间的时间间隔,是 ( DOM视图更新的最小间隔 )
- 由于主流浏览器的刷新率是 (
60hz
),那么渲染一帧的时间就必须控制在 (16ms
) 内才能保证 (不掉帧
),也就是说每一次渲染都要保证在16ms内完成渲染在能保证页面流畅,才不会卡顿
- 由于主流浏览器的刷新率是 (
- 如果测量渲染间隔?
requestAnimationFrame
- window.requestAnimationFrame() 告诉浏览器你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
- 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
- 帧之间的时间间隔,是 ( DOM视图更新的最小间隔 )
- 浏览器每帧的生命周期 ( 即每帧需要完成的事情 )
- 1.处理用户的交互,即输入事件
- 阻塞输入事件:touch wheel ...
- 非阻塞输入事件:click keypress ...
- 2.js
- 定时器
- 3.开始帧 - begin frame
- window.resize
- scroll
- mediaquery changed
- 4.requestAnimationFrame
- 5.layout
- 6.paint
- 7.compisite
- 1.处理用户的交互,即输入事件
- 渲染帧
- 重要结论
一个渲染帧内的多次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执行完毕
(7) React15存在的问题
当存在大量的js计算时,当时间超过了16ms时页面没法得到及时更新,就会出现卡顿
- 当 (
setState
) 时,React会遍历应用的所有节点,找出差异,再更新页面,整个过程 (不能被打断
),如果元素过多,整个处理过程就可能超过16ms,造成掉帧,出现卡顿 - dom-diff过程
- dom-diff也是如此 ( 递归对比 )
- 节点树很庞大时,会导致 ( 递归调用 ) 的 ( 函数调用栈 ) 越来越深
- 整个diff过程不能被 ( 中断 ),页面会等待递归调用完成后才重新渲染
- dom-diff也是如此 ( 递归对比 )
(8) createElement 和 ReactElement 源码
- 文件位置:
package/react/src/ReactElement.js
- 在react写jsx语法,是不能直接被浏览器解析的,需要经过babel编译成js,编译后会转成React.createElement()这样的形式
- jsx => 编译成函数式语法 - 如下图
(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阶段
- 会分别为节点执行
beginWork
和completeWork
- 计算
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阶段
(2.0.1) scheduler 调度器
- 核心作用
- 为了达到使 fiber-reconciler 每执行一段时间就把控制权交回给浏览器,我们就需要一个 (
调度器Scheduler
) 来进行 (任务分配
),(排序优先级,让优先级更高的任务先进行reconciler
) - react15没有scheduler,任务都没有优先级,也不能被中断,不能排序,只能一次性同步执行
- 为了达到使 fiber-reconciler 每执行一段时间就把控制权交回给浏览器,我们就需要一个 (
- 优先级通过什么来表示?
- 在 (
scheduler
) 中每个任务的 (优先级
) 都是通过 (过期时间
) 来表示的 - ( 过期时间 ) 距离 ( 现在的时间 ) 越近,表示 ( 优先级越高 )
- 在 (
- timerQueue 和 taskQueue
timerQueue
- 存放 ( 没有过期的任务 )
taskQueue
- 存放 ( 已经过期的任务 )
- 小顶堆
- 小顶堆也叫小根堆
- 小顶堆:即在 ( 完全二叉树 ) 中 ( 每个节点的值都小于或等于其左右孩子的值 )
- timerQueue和taskQueue都是 ( 小顶堆 ),所以取出来的任务都是距离现在时间的过期时间最短的任务,即 ( 取出的任务都是优先级最高的任务 ),然后优先执行它
- 优先级高的任务 (比如:键盘输入) 可以打断优先级低的任务 (比如:diff)
-
- ( 输入事件 ) - 阻塞输入事件touch,wheel;非阻塞输入事件click,keypress
-
- ( 定时器 ),检查定时器是否到点,到点则执行回调函数
-
- ( 开始帧 ) Begin Frame,即每一帧的事件,包括 ( window.resize ) ( scroll ) ( media )
-
- ( 请求动画帧 ) requestAnimationFrame,即在每次绘制前,会执行requestAnimationFrame的回调
-
- ( layout )
-
- ( paint )
-
- ( 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;
(2.0.2) reconciler 调和器
- 核心作用
- 找出哪些节点发生了改变,并打上不同的tag
- 在render/reconciliation阶段过程中,让优先级更高的任务先执行,该过程可以被打断
- 在commit阶段一次性的批量更新,该过程不能被打断
- reconciler执行时机
- reconciler发生在 ( render ) 阶段
- ( render阶段 ) 再次划分为两个阶段
- beginWork
- completeWork
- reconciler
- 旧版中的reconciler =>
stack reconciler
------------ 递归 - 新版中的reconciler =>
fiber reconciler
------------ 循环
- 旧版中的reconciler =>
- stack reconciler
- stack reconciler 不能被打断
- fiber reconciler
- fiber reconciler 每执行一段时间都会将控制权交回给浏览器
- fiber reconciler 在执行过程中可分为 ( 两个阶段 )
- 阶段一 ( render/reconciliation )
生成fiber树,得出需要更新的节点信息,可以被打断,让优先级高的任务先执行
- 通过 scheduler 执行器来完成
- 阶段二 ( commit )
将需要更新的节点一次批量更新,不能被打断
- 阶段一 ( render/reconciliation )
- fiber reconciler 在执行过程中可分为 ( 两个阶段 )
- fiber reconciler 每执行一段时间都会将控制权交回给浏览器
(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的备用节点,反之亦然
(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信息
- 通过fiber对象的 (
- 遍历
- 遍历时,
深度优先原则,先children,再sibling,再找叔叔
- 遍历时,
- child sibling return
(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)
-
- ( 输入事件 ) - 阻塞输入事件touch,wheel;非阻塞输入事件click,keypress
-
- ( 定时器 ),检查定时器是否到点,到点则执行回调函数
-
- ( 开始帧 ) Begin Frame,即每一帧的事件,包括 ( window.resize ) ( scroll ) ( media )
-
- ( 请求动画帧 ) requestAnimationFrame,即在每次绘制前,会执行requestAnimationFrame的回调
-
- ( layout )
-
- ( paint )
-
- ( 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 树进行修改
-
案例:阶段一(render/reconciliation阶段)
function App() {
return (
<div>
xiao
<p>chen</p>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
- 上图-图示说明
- 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
- fiberRootNode 和 rootFiber
(2) 阶段二 ------------ commit阶段
- 将阶段一中结算出来的 ( 需要更新的节点 ) ( 一次性的批量更新 ),该过程 ( 不能被打断 )
参考资料
-
16ms渲染帧 getpocket.com/read/237111…
-
加载css会造成DOM树的解析和渲染吗 segmentfault.com/a/119000001…
-
React-biber的原理 juejin.cn/post/684490…
-
走进fiber的世界 juejin.cn/post/693756…
-
fiber源码解析 segmentfault.com/a/119000002…
-
react源码解析 www.jianshu.com/p/d56b907c0…
-
人人都能读懂的react源码 xiaochen1024.com/article_ite…
-
手写render zhuanlan.zhihu.com/p/355884903