因为最近业务较忙,2020年搞懂React原理系列文章最终篇直到现在才在业余时间抽空实现。之前在公司外部已有过一次PPT模式的分享,但通过一段时间对hooks的深度应用,对其又有了更深一些理解,故本次加上新内容并以文章模式再分享一次。

持续一年浏览React源码和总结其外围原理,缓缓也有了一些心得:

读懂源码只是第一步,弄懂其性能的代码实现形式。而再进一步是彻底搞懂其实现原理、思维,它通过什么形式实现了什么性能,带来了什么价值。

不论它的底层代码如何改写,最终的目标都是为了实现某个性能。只有咱们把其性能实现原理把握,便可活学活用,联合业务让业务开发效率更高,或围绕业务做一些生产力工具。

React应用以后最新版本:17.0.1

往年写了一个“搞懂React源码系列”,把React最外围的内容用最易懂的形式讲清楚。2020年搞懂React源码系列:

  • React Diff原理
  • React 调度原理
  • 搭建浏览React源码环境-反对React所有版本断点调试细分文件
  • (以后)React Hooks原理

少了React Fiber更新原理?那是因为国外大佬的一篇文章写得太好,没有必要再反复写一次。或者明年能够找个工夫写个扼要概要的React所有原理汇总文章。

本文将重点讲useMemouseEffectuseState3个api,因为它们在日常开发中最罕用。前面讲其余几个api。本次次要形容每个hook的性能和原理。

基础知识

任何一个hook的调用形式都是:

输入 = hook函数(输出)

肯定会有输出和hook函数和输入。
而被调用的过程个别是2种:组件初始化和组件更新。

UseMemo实现原理

useMemo的性能是记忆某个后果,只有依赖项产生扭转时才更新输入后果。

输入后果 = useMemo(计算函数,依赖项)

下方展现其在不同过程中useMemo外部实现原理。

输出hook函数输入
计算函数,依赖项useMemo计算结果

组件初始化

  1. 执行计算函数,获取计算结果
  2. 缓存后果后果和依赖项
  3. 返回计算结果

组件更新:

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间接被调用的过程是组件初始化和组件更新,其销毁函数被调用的过程是组件销毁。

组件初始化

  1. 生成一个effect对象,蕴含创立函数
  2. 缓存effect和依赖项
  3. 当React进入提交阶段,执行effect中的创立函数,获取销毁函数。若销毁函数不为空,则将其放入effect。

组件更新

  1. 生成一个effect对象, 蕴含创立函数
  2. 查看已缓存effect中是否有销毁函数,有的话则放入新effect对象
  3. 缓存effect
  4. 若依赖项和已缓存依赖项不同,则将hasEffect标记增加到effect,并缓存新依赖项
  5. 当React进入提交阶段:
if (effect有hasEffect标记) {    若effect中有销毁函数,则先执行销毁函数    执行effect中的创立函数,获取销毁函数。若销毁函数不为空,则将其放入effect} 

组件销毁

  1. 若effect中有销毁函数,则执行。

UseState实现原理

useState的性能是设置一个状态的初始值,并返回以后状态和设置状态的函数。

[状态,设置状态函数] = useState(初始状态)
输出hook函数输入
初始状态useState状态,设置状态函数

useState间接被调用的过程也是组件初始化和组件更新,其还有一个调用设置状态函数的过程。

组件初始化

  1. 若初始状态为函数,则将函数执行后果设为以后状态。否则将初始状态设为以后状态。
  2. 生成设置状态函数
  3. 缓存以后状态和设置状态函数
  4. 返回以后状态

组件更新

  1. 读取缓存状态和设置状态函数
  2. 返回缓存状态

执行设置状态函数

  1. 更新缓存状态
  2. 触发React组件树更新
  3. 在下一次组件更新时,将返回已被更新的缓存状态

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: 以后值}

组件初始化

  1. 生成对象: { current: 初始以后值 }
  2. 缓存对象
  3. 返回缓存对象

组件更新

  1. 获取缓存对象
  2. 返回缓存对象

UseImperativeHandle

useImperativeHandle的性能被子组件应用,实现父组件调用子组件外部办法,个别与forwardRef一起应用。

UseImperativeHandle实现原理与useEffect相似。

UseLayoutEffect

useLayoutEffect和useLayout的性能区别:

useLayoutEffectuseLayout
渲染到屏幕前执行渲染到屏幕后执行
useLayoutEffect(() => {   // 组件初始化或更新在渲染到屏幕前执行   return () => {     // 1. 组件卸载前执行 2. 执行下一个effect前执行   } }, )

结尾

在跨年前实现2020搞懂React原理系列文章最初一篇,也是为了迎接行将到来的2021。

在2021年,新的系列行将启航。不过在写新系列前,下一篇将先写微前端框架实现原理。

祝大家新年快乐!