因为最近业务较忙,2020 年搞懂 React 原理系列文章最终篇直到现在才在业余时间抽空实现。之前在公司外部已有过一次 PPT 模式的分享,但通过一段时间对 hooks 的深度应用,对其又有了更深一些理解,故本次加上新内容并以文章模式再分享一次。
持续一年浏览 React 源码和总结其外围原理,缓缓也有了一些心得:
读懂源码只是第一步,弄懂其性能的代码实现形式。而再进一步是彻底搞懂其实现原理、思维,它通过什么形式实现了什么性能,带来了什么价值。
不论它的底层代码如何改写,最终的目标都是为了实现某个性能。只有咱们把其性能实现原理把握,便可活学活用,联合业务让业务开发效率更高,或围绕业务做一些生产力工具。
React 应用以后最新版本:
17.0.1
往年写了一个“搞懂 React 源码系列”,把 React 最外围的内容用最易懂的形式讲清楚。2020 年搞懂 React 源码系列:
- React Diff 原理
- React 调度原理
- 搭建浏览 React 源码环境 - 反对 React 所有版本断点调试细分文件
- (以后)React Hooks 原理
少了 React Fiber 更新原理?那是因为国外大佬的一篇文章写得太好,没有必要再反复写一次。或者明年能够找个工夫写个扼要概要的 React 所有原理汇总文章。
本文将重点讲 useMemo
、useEffect
和useState
3 个 api,因为它们在日常开发中最罕用。前面讲其余几个 api。本次次要形容每个 hook 的性能和原理。
基础知识
任何一个 hook 的调用形式都是:
输入 = hook 函数(输出)
肯定会有输出和 hook 函数和输入。
而被调用的过程个别是 2 种:组件初始化和组件更新。
UseMemo 实现原理
useMemo 的性能是记忆某个后果,只有依赖项产生扭转时才更新输入后果。
输入后果 = useMemo(计算函数,依赖项)
下方展现其在不同过程中 useMemo 外部实现原理。
输出 | hook 函数 | 输入 |
---|---|---|
计算函数,依赖项 | useMemo | 计算结果 |
组件初始化:
- 执行计算函数,获取计算结果
- 缓存后果后果和依赖项
- 返回计算结果
组件更新:
if (依赖项和已缓存依赖项雷同) {返回已缓存计算结果} else {
执行计算函数,获取新计算结果
缓存新计算结果和新依赖项
返回新计算结果
}
其中一个问题值得注意,依赖项是如何比拟的?深比拟或浅比拟?因为依赖项个别是一个数组,而数组中的每个元素是具体的依赖变量,那么 React 是如何比拟的?
翻看源码,发现若两个依赖项都是数组,则 React 会应用 Object.is 对其每一个元素进行强比拟。
Object.is('foo', 'foo'); // true
Object.is(window, window); // true
Object.is('foo', 'bar'); // false
Object.is([], []); // false
var foo = {a: 1};
var bar = {a: 1};
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(null, null); // true
// Special Cases
Object.is(0, -0); // false
Object.is(-0, -0); // true
Object.is(NaN, 0/0); // true
转念一想,其实就应这样比拟。
UseEffect 实现原理
useEffect(创立函数,依赖项)
useEffect 的次要性能是:
组件加载后执行创立函数,创立函数执行后会返回一个销毁函数,在组件销毁前执行。
若依赖项为数组且不为空,则依赖项扭转时,会执行上一个销毁函数和从新创立函数。
输出 | hook 函数 |
---|---|
创立函数,依赖项 | useEffect |
useEffect 间接被调用的过程是组件初始化和组件更新,其销毁函数被调用的过程是组件销毁。
组件初始化:
- 生成一个 effect 对象,蕴含创立函数
- 缓存 effect 和依赖项
- 当 React 进入提交阶段,执行 effect 中的创立函数,获取销毁函数。若销毁函数不为空,则将其放入 effect。
组件更新:
- 生成一个 effect 对象, 蕴含创立函数
- 查看已缓存 effect 中是否有销毁函数,有的话则放入新 effect 对象
- 缓存 effect
- 若依赖项和已缓存依赖项不同,则将 hasEffect 标记增加到 effect,并缓存新依赖项
- 当 React 进入提交阶段:
if (effect 有 hasEffect 标记) {
若 effect 中有销毁函数,则先执行销毁函数
执行 effect 中的创立函数,获取销毁函数。若销毁函数不为空,则将其放入 effect
}
组件销毁:
- 若 effect 中有销毁函数,则执行。
UseState 实现原理
useState 的性能是设置一个状态的初始值,并返回以后状态和设置状态的函数。
[状态,设置状态函数] = useState(初始状态)
输出 | hook 函数 | 输入 |
---|---|---|
初始状态 | useState | 状态,设置状态函数 |
useState 间接被调用的过程也是组件初始化和组件更新,其还有一个调用设置状态函数的过程。
组件初始化:
- 若初始状态为函数,则将函数执行后果设为以后状态。否则将初始状态设为以后状态。
- 生成设置状态函数
- 缓存以后状态和设置状态函数
- 返回以后状态
组件更新:
- 读取缓存状态和设置状态函数
- 返回缓存状态
执行设置状态函数:
- 更新缓存状态
- 触发 React 组件树更新
- 在下一次组件更新时,将返回已被更新的缓存状态
useReducer
useReducer 的性能和原理与 useState 统一,区别在于 useReducer 应用函数治理状态,应用派发动作指令函数作为设置状态函数。Reducer 概念可参看 redux。
[状态,派发动作指令函数]=useReducer(reducer 函数,初始状态)
UseCallback 实现原理
已缓存函数 = useCallback(待缓存函数,依赖项)
useCallback 的性能就是 useMemo 记忆函数一个封装,相比 useMemo 只是少套了一层函数:
已缓存函数 = useMemo(() => 代缓存函数, 依赖项)
不过 React 外部并没有用 useMemo 间接实现 useCallback,而是用一套相似 useMemo 的代码实现。
UseRef 实现原理
{current: 以后值} = useRef(初始以后值)
useRef 的性能是生成一个对象,构造是:{current: 以后值}
, 对象一旦初始化,不会因为组件更新而扭转。
尽管对象初始化后不会因组件更新扭转,但咱们能够通过更改其 current 属性,以后值就相当于一个组件的变量,相似 class 组件的实例属性。
useRef 最罕用的场景莫过于设置组件的 ref。
const container = useRef(null)
return <div ref={container}></div>
其实此处官网也有特地讲,div 上的 ref 属性将触发设置 container.current
为 dom 对象。
但咱们也能够把 useRef 作为生成组件变量的办法灵便利用。
输出 | hook 函数 | 输入 |
---|---|---|
初始以后值 | useRef | {current: 以后值} |
组件初始化:
- 生成对象: {current: 初始以后值}
- 缓存对象
- 返回缓存对象
组件更新:
- 获取缓存对象
- 返回缓存对象
UseImperativeHandle
useImperativeHandle 的性能被子组件应用,实现父组件调用子组件外部办法,个别与 forwardRef 一起应用。
UseImperativeHandle 实现原理与 useEffect 相似。
UseLayoutEffect
useLayoutEffect 和 useLayout 的性能区别:
useLayoutEffect | useLayout |
---|---|
渲染到屏幕前执行 | 渲染到屏幕后执行 |
useLayoutEffect(() => {
// 组件初始化或更新在渲染到屏幕前执行
return () => {// 1. 组件卸载前执行 2. 执行下一个 effect 前执行}
}, )
结尾
在跨年前实现 2020 搞懂 React 原理系列文章最初一篇,也是为了迎接行将到来的 2021。
在 2021 年,新的系列行将启航。不过在写新系列前,下一篇将先写微前端框架实现原理。
祝大家新年快乐!