因为最近业务较忙,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'); // trueObject.is(window, window); // trueObject.is('foo', 'bar'); // falseObject.is([], []); // falsevar foo = { a: 1 };var bar = { a: 1 };Object.is(foo, foo); // trueObject.is(foo, bar); // falseObject.is(null, null); // true// Special CasesObject.is(0, -0); // falseObject.is(-0, -0); // trueObject.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年,新的系列行将启航。不过在写新系列前,下一篇将先写微前端框架实现原理。
祝大家新年快乐!