# 手摸手实现 react-hooks

# 前言

本文不会描述太多 react-hooks 的基本用法,直接参考源码实现一款简易版的 react-hooks, 大家可以自己debugger一下源码,这样的话更容易理解。

# useState 的基本应用和实现

# 基本用法

我们先来看下 useState的基本用法

An image

TIP

上述代码中将 render 逻辑抽离出来,是为了自己实现对应hooks的时候有个方法可以触发页面的重新渲染

上述代码使用hooks实现了一个简单的点击按钮修改 numbername的例子, useState函数接收一个初始值(也可以接收一个函数,下文中不涉及传入函数的处理), 然后返回一个[state, setState], 分别对应着当前的 state 值以及修改这个值方法。

那我们试下模仿源码实现一个简易版的 useState, 如果对链表不是很熟悉的话,可能有点绕~~

# 实现 useState

我们先看下下面代码

An image

只是看代码不是很好理解, 我们画图一步一步解析这个流程

我们看下 firstWorkInProgressHookworkInProgressHook到底是什么

An image

第一第二行代码,声明了一个对象节点, 我们也可以理解为第一个初始化节点, 对象里面分别是 memoizedStatenext, 意思是记忆的数据和下一个指向, 然后 firstWorkInProgressHookworkInProgressHook 我们可以理解为它们分别是两个指针,指向第一个初始化节点。

然后我们第一次调用 useState(0)的时候, 我们发现 workInProgressHook.next的值(也就是初始化节点的next属性)为空, 所有我们创建了一个新的节点 currentHook, 意义是当前的节点,并且 memoizedStatenext 为 0(传入的初始值) 和 null, 也就是对应这下图

An image

再然后我们继续往下走,因为 workInProgressHook.next的值为 null, 所以我们走 else 逻辑, 在else 逻辑中, 我们将 currentHook 赋值给 workInProgressHook.next, 意味着我们将第一次初始化节点通过 next 和当前的节点连接在了一起。然后将currentHook的值赋值给 workInProgressHook, 意思就是将workInProgressHook指针指向了当前节点,如下图

An image

同理当我们第二次调用useState('cy')的时候, 还是走相同的逻辑,然后得到的结构是这样的

An image

到现在我们已经完成了第一次渲染,也就是根据useState初始化我们的数据链表, 现在我们完善一下我们的代码,增加两行关键性代码

An image

当我们点击按钮add number的时候, 我们不难发现实际上就是给第二个节点赋值, 我们把思路集中在链表回退这个逻辑。

setState函数中,我们手动调用了 render() 方法,在 render() 方法里面我们将workInProgressHook 指向了第一个初始化节点firstWorkInProgressHook, 如下图,在我们每一次调用setState的时候, 也就是重新渲染页面的时候会出现这种效果

An image

因为页面重新渲染了, 我们就会再一次走useState的逻辑,不同的是,这个时候 workInProgressHook.next已经有值了,走的是 if 逻辑, workInProgressHook = workInProgressHook.next , 保证不管页面重新渲染多少次,我们通过[state, setState]中state(currentHook.memoizedState)获取到的都是同一个值。

An image

TIP

现在我们知道为什么只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用了,因为 useState的存储数据结构和存储的顺序是密切相关的,如果我们在条件判断等里面调用 useState 会导致数据对不上,导致出错

# useReducer 的基本实现

# 基本用法

我们先来看下 useReducer的基本用法

An image

useReducer接收3个参数,第一个是reducer, 第二个参数是state的初始值,第三个参数是一个函数,如果这个函数存在,则将useReducer的第二个参数作为这个函数的参数,执行后的结果作为初始值。

知道思路之后其实就不难了,我们来实现它

An image

# useEffect 的基本实现

# 基本用法

我们知道 useEffect 给函数组件添加了操作副作用的能力,例如修改DOM、定时器这些.

还有最重要一点是 useEffect 可以替代类组件中的didmountdidupdatewillunmount

我们来实现一个具有打印功能的计时器例子,来了解 useEffect 的基本用法吧

An image

我们可以发现,只有当每次name发生改变之后,才会执行回调函数(其他基础用法这里就不赘述了), 这也是我们实现的时候最需要注意的一点, 当我们传入的依赖项和上一次保存的依赖项相同的话,回调函数不执行.

An image

其实我们只需要理解这行代码就足够了, 他是useEffect的核心



let changed = lastDependencies ?  !dependencies.every((item, index) => item === lastDependencies[index]) : true

当我们第一次执行useEffect的时候, lastDependencies为undefined, 所以第一次的时候changed为真,所以执行回调。第二次执行useEffect的时候,遍历依赖项和保存的最后一次修改的依赖项,如果两者相等,则返回false,不执行回调函数。

# 总结

还有其他的一些hooks这里就不赘述了,因为你会发现,除了useState使用的链表存储的数据结构难理解一点,剩余的hooks大多数都是依靠一个全局的变量来存储值,而且需要注意的是,useState内部其实是用useReducer来实现的,有时间的话一定要试一下自己用useReducer来实现一款链表的useState