乐趣区

关于前端:轻松掌握React-Hooks底层实现原理

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

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

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

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

React 应用以后最新版本:17.0.1

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

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

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

本文将重点讲 useMemouseEffectuseState 3 个 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');     // 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 间接被调用的过程是组件初始化和组件更新,其销毁函数被调用的过程是组件销毁。

组件初始化

  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 的性能区别:

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

结尾

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

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

祝大家新年快乐!

退出移动版