chst365's blog chst365's blog
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

chst365

DIV工程师
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 浏览器

  • webpack

  • TypeScript

  • 性能

  • 工程化

  • React

  • JavaScript

  • 编程题

  • React技术揭秘

    • React理念
    • React新老架构
    • Fiber
    • 前置知识
    • render阶段
    • commit阶段
    • Diff算法
    • 状态更新
    • Hooks
      • Hooks 理念
      • 极简Hooks实现
        • 工作原理
        • 更新是什么
        • update数据结构
      • useEffect
        • flushPassiveEffectsImpl
        • 阶段一:销毁函数的执行
      • useRef
      • useMemo与useCallback
        • mount
        • update
  • 算法

  • 前端
  • React技术揭秘
chst365
2022-09-28
目录

Hooks

# Hooks 理念

React 的LOGO图案代表原子的符号。世间万物由原子组成,原子的类型与属性决定了事物的外观与表现。 在React中,可将UI拆分为很多独立的单元,每个单元叫Component。Component的属性与类型决定了UI的外观与表现。 然而,原子中还有更小的粒子--电子(electron)。电子可解释原子是如何工作的。 在React中,ClassComponent是一类原子,Hooks是贴近事物运行规律的电子。 React的架构遵循schedule - render - commit的运行流程。 ClassComponent作为React世界的原子,他的生命周期(componentWillXX/componentDidXX)是为了介入React的运行流程而实现的更上层抽象。 Hooks则更贴近React内部运行的各种概念(state|context|life-cycle)。 作为React技术栈的开发者,初次学习Hooks时,会拿ClassComponent的生命周期来类比Hooks API的执行时机。 但当我们熟练使用Hooks时发现,这两者的概念有很多割裂感,并不是同一抽象层次可互相替代的概念。 如:替代componentWillReceiveProps的Hooks是什么呢? 有的说是useEffect

 useEffect( () => {
    console.log('something updated');
  }, [props.something])
1
2
3

但是componentWillReceiveProps是在render阶段执行,而useEffect是在commit阶段完成渲染后异步执行。

Concurrent Mode是React未来的发展方向,而Hooks是能够最大限度发挥Concurrent Mode潜力的Component构建方式。

# 极简Hooks实现

为更好理解Hooks原理,遵循React的运行流程,实现一个极简useState Hook 完整代码如下:

function useState(initialState) {
  let hook;

  if (isMount) {
    hook = {
      queue: {
        pending: null
      },
      memoizedState: initialState,
      next: null
    }
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook;
    } else {
      workInProgressHook.next = hook;
    }
    workInProgressHook = hook;
  } else {
    hook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;
  }

  let baseState = hook.memoizedState;
  if (hook.queue.pending) {
    let firstUpdate = hook.queue.pending.next;

    do {
      const action = firstUpdate.action;
      baseState = action(baseState);
      firstUpdate = firstUpdate.next;
    } while (firstUpdate !== hook.queue.pending.next)

    hook.queue.pending = null;
  }
  hook.memoizedState = baseState;

  return [baseState, dispatchAction.bind(null, hook.queue)];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 工作原理

对于useState Hook,考虑如下例子:

function App() {
  const [num, updateNum] = useState(0);

  return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;
}
1
2
3
4
5

可将工作分两部分:

  1. 通过一些途径产生更新,更新会造成组件render
  2. 组件render时useState返回的num为更新后的结果 其中,步骤1的更新可分mount和update
  3. 调用ReactDOM.render会产生mount的更新,更新内容为useState的initialValue(即0)
  4. 点击p标签触发updateNum会产生一次update的更新,更新内容num=>num+1

# 更新是什么

在我们的极简例子中,更新是如下数据结构:

const update = {
  // 更新执行的函数
  action,
  // 与同一个Hook的其他更新形成链表
  next: null
}
1
2
3
4
5
6

对应App来说,点击p标签产生的update的action为num => num + 1 若改写App的onClick:

// 之前
return <p onClick={() => updateNum(num => num + 1)}>{num}</p>;

// 之后
return <p onClick={() => {
  updateNum(num => num + 1);
  updateNum(num => num + 1);
  updateNum(num => num + 1);
}}>{num}</p>;
1
2
3
4
5
6
7
8
9

那么点击p标签会产生三个update

# update数据结构

这些update是如何组合在一起呢? 答案是 他们会形成环状单向链表。 调用updateNum实际调用的是dispatchAction.bind(null, hook.queue)

function dispatchAction(queue, action) {
  // 创建update
  const update = {
    action,
    next: null
  }

  // 环状单向链表操作
  if (queue.pending === null) {
    update.next = update;
  } else {
    update.next = queue.pending.next;
    queue.pending.next = update;
  }
  queue.pending = update;

  // 模拟React开始调度更新
  schedule();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# useEffect

在flushPassiveEffects方法内部会从全局变量rootWithPendingPassiveEffects获取effectList

# flushPassiveEffectsImpl

flushPassiveEffects (opens new window)内部会设置优先级,并执行flushPassiveEffectsImpl。 flushPassiveEffectsImpl主要做三件事:

  • 调用该useEffect在上一次render时的销毁函数
  • 调用该useEffect在本次render时的回调函数
  • 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他

# 阶段一:销毁函数的执行

# useRef

# useMemo与useCallback

# mount

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 创建并返回当前hook
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 计算value
  const nextValue = nextCreate();
  // 将value与deps保存在hook.memoizedState
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 创建并返回当前hook
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 将value与deps保存在hook.memoizedState
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可看到,与mountCallback这两个唯一的区别是

  • mountMemo会将回调函数(nextCreate)的执行结果作为value保存
  • mountCallback会将回调函数作为value保存

# update

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 返回当前hook
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 判断update前后value是否变化
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 未变化
        return prevState[0];
      }
    }
  }
  // 变化,重新计算value
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 返回当前hook
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 判断update前后value是否变化
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 未变化
        return prevState[0];
      }
    }
  }

  // 变化,将新的callback作为value
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

可见,对于update,这两个hook的唯一区别也是回调函数本身还是回调函数的执行结果作为value。

#前端#React技术揭秘
上次更新: 2022/09/28, 16:33:11
状态更新
斐波那契数

← 状态更新 斐波那契数→

最近更新
01
面试官
03-27
02
this&指针&作用域&闭包
03-27
03
前端
03-27
更多文章>
Theme by Vdoing | Copyright © 2019-2025 chst365 | 豫ICP备17031889号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式