动机

使用 hooks 的动机:

  • 在组件之间复用状态逻辑很难;

class 组件要实现复用逻辑,通常会以高阶组件的形式实现或render props,但它们需要重新组织你的组件结构并让组件嵌套变深,代码难以理解,形成“嵌套地狱”。

  • 复杂组件变得难以理解;

当一个简单的组件,由于需要的迭代,组件间的状态散布到生命周期的各个时期里,而状态之间既互相关联又互相不影响,导致代码拆分困难,组件的逻辑与代码越来越庞大,变得难以阅读理解。

  • class 让开发人员与计算机都难理解,而表现为函数式编程比 OOP 更加简单;

class 你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器,当然可以使用箭头函数解决。
class 不能很好的压缩,并且会使热重载出现不稳定的情况。

useReducer

useState 其实就是阉割版的 useReducer,但在实际开发中 useState 才是主要被使用的,而 useReducer 是 useState 的替代方案。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

比如直接在函数组件里调用请求之类的,需要等请求的数据回来后才进行某些操作,那么你就要维护一个 loading 与 data 的 state,而异步的 state 是 useState 后就立刻 render 一次,那么执行两次 useState,就会执行两次的 render,这就有可能出现一些执行顺序上的 BUG 问题,所以我们可以使用 useReducer 把它们维护到一个 state 上,并且 useReducer 里的 reducer 可以执行一些逻辑上的操作。
如:

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
47
import { useReducer, useEffect } from "react";

interface InitState {
data: any[];
page: { [key: string]: any };
loading: boolean;
}
const initState: InitState = {
data: [],
page: { total: 10, record: 10 },
loading: true,
};
function reducer(
state: InitState,
action: { type: string; payload: InitState }
) {
switch (action.type) {
case "a":
return { ...action.payload };
case "b":
return { ...action.payload };
default:
return state;
}
}

const useRequest = () => {
const [state, dispatch] = useReducer(reducer, initState);

useEffect(() => {
setTimeout(() => {
const data = {
type: "a",
payload: {
data: ["zhhh", "xxx", 3, 4],
page: { total: 51, record: 999 },
loading: false,
},
};
dispatch(data);
}, 1000);
}, []);

return state;
};

export default useRequest;

github 例子

官方 usereducer 说明

问题

hooks 接入 React-Redux 后 mapDispatchToProps 的第二参数是有问题的,如果使用了第二参数,那么每当 connected 的组件接收到新的 props 时,mapDispatchTopProps 都会被调用,这意味着组件会返回一个新的 props,导致使用 useEffect 监听 props 的属性的都会被触发掉而出现一些 BUG 问题,当然如果有正确判断规则,那么这个对于项目来说问题不大,就是多了一次 render 机会,不会严重影响程序与用户体验。

Redux with Hooks

源码浅析

  • React 通过单链表来管理 Hooks
  • 按 Hooks 的执行顺序依次将 Hook 节点添加到链表中
  • 每个 Hook 节点通过循环链表记住所有的更新操作
  • 在 update 阶段会依次执行 update 循环链表中的所有更新操作,最终拿到最新的 state 返回
  • FiberNdoe 节点中会又一个 updateQueue 链表来存放所有的本次渲染需要执行的 effect。
  • mountEffect 阶段和 updateEffect 阶段会把 effect 挂载到 updateQueue 上。
  • updateEffect 阶段,deps 没有改变的 effect 会被打上 NoHookEffect tag,commit 阶段会跳过该 Effect。

为什么只能在函数顶层使用 Hooks 而不能在条件语句等里面使用 Hooks?

其实 hooks 就是把挂载与更新分开来了,如果组件第一次加载那么就会走 mount 的函数,更新后走 update 的函数,由于函数组件其实就是 render 函数,所以每次的更新都会把整个函数都重新执行一遍,那么执行的函数里的钩子就要与第一次执行时是一致,这是为什么,你可以想象一下,数组,数组只有下标,而每次执行函数后函数里的数组都是重新 push 一遍内容的,那么如果组件第一次加载时保存一个全局钩子数组,与这次的钩子的 push 进去的不一致,那么全局钩子与更新的钩子匹配时整个更新就会错乱。

而像 preact 模拟 hooks 直接就使用数组,没有使用链表结构,然后每次获取当前 hooks 钩子都是 index 索引加一方式。

useState 的实现与 useReducer 是一样的,因为 useState 返回的就是一个 state 与 dispatch,只是它的 action 是内容实现的,action 就是更新的内容,不需要有 type 这样的字段。

资料

React Hooks 源码解析(3):useState
React Hooks 源码模拟与解读
React Hooks 源码解析,原来这么简单~