# 手摸手实现 react-hooks
# 前言
本文不会描述太多 react-hooks
的基本用法,直接参考源码实现一款简易版的 react-hooks
, 大家可以自己debugger一下源码,这样的话更容易理解。
# useState 的基本应用和实现
# 基本用法
我们先来看下 useState
的基本用法
TIP
上述代码中将 render 逻辑抽离出来,是为了自己实现对应hooks的时候有个方法可以触发页面的重新渲染
上述代码使用hooks实现了一个简单的点击按钮修改 number
和 name
的例子, useState
函数接收一个初始值(也可以接收一个函数,下文中不涉及传入函数的处理), 然后返回一个[state, setState]
, 分别对应着当前的 state
值以及修改这个值方法。
那我们试下模仿源码实现一个简易版的 useState
, 如果对链表不是很熟悉的话,可能有点绕~~
# 实现 useState
我们先看下下面代码
只是看代码不是很好理解, 我们画图一步一步解析这个流程
我们看下 firstWorkInProgressHook
和 workInProgressHook
到底是什么
第一第二行代码,声明了一个对象节点, 我们也可以理解为第一个初始化节点, 对象里面分别是 memoizedState
和 next
, 意思是记忆的数据和下一个指向, 然后 firstWorkInProgressHook
和 workInProgressHook
我们可以理解为它们分别是两个指针,指向第一个初始化节点。
然后我们第一次调用 useState(0)
的时候, 我们发现 workInProgressHook.next
的值(也就是初始化节点的next属性)为空, 所有我们创建了一个新的节点 currentHook
, 意义是当前的节点,并且 memoizedState
和 next
为 0(传入的初始值) 和 null, 也就是对应这下图
再然后我们继续往下走,因为 workInProgressHook.next
的值为 null, 所以我们走 else 逻辑, 在else 逻辑中, 我们将 currentHook
赋值给 workInProgressHook.next
, 意味着我们将第一次初始化节点通过 next
和当前的节点连接在了一起。然后将currentHook
的值赋值给 workInProgressHook
, 意思就是将workInProgressHook
指针指向了当前节点,如下图
同理当我们第二次调用useState('cy')
的时候, 还是走相同的逻辑,然后得到的结构是这样的
到现在我们已经完成了第一次渲染,也就是根据useState
初始化我们的数据链表, 现在我们完善一下我们的代码,增加两行关键性代码
当我们点击按钮add number
的时候, 我们不难发现实际上就是给第二个节点赋值, 我们把思路集中在链表回退这个逻辑。
在setState
函数中,我们手动调用了 render()
方法,在 render()
方法里面我们将workInProgressHook
指向了第一个初始化节点firstWorkInProgressHook
, 如下图,在我们每一次调用setState
的时候, 也就是重新渲染页面的时候会出现这种效果
因为页面重新渲染了, 我们就会再一次走useState
的逻辑,不同的是,这个时候 workInProgressHook.next
已经有值了,走的是 if 逻辑, workInProgressHook = workInProgressHook.next
, 保证不管页面重新渲染多少次,我们通过[state, setState]中state(currentHook.memoizedState)获取到的都是同一个值。
TIP
现在我们知道为什么只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用了,因为 useState的存储数据结构和存储的顺序是密切相关的,如果我们在条件判断等里面调用 useState 会导致数据对不上,导致出错
# useReducer 的基本实现
# 基本用法
我们先来看下 useReducer
的基本用法
useReducer
接收3个参数,第一个是reducer
, 第二个参数是state的初始值,第三个参数是一个函数,如果这个函数存在,则将useReducer
的第二个参数作为这个函数的参数,执行后的结果作为初始值。
知道思路之后其实就不难了,我们来实现它
# useEffect 的基本实现
# 基本用法
我们知道 useEffect
给函数组件添加了操作副作用的能力,例如修改DOM、定时器这些.
还有最重要一点是 useEffect
可以替代类组件中的didmount
、 didupdate
、 willunmount
我们来实现一个具有打印功能的计时器例子,来了解 useEffect
的基本用法吧
我们可以发现,只有当每次name发生改变之后,才会执行回调函数(其他基础用法这里就不赘述了), 这也是我们实现的时候最需要注意的一点, 当我们传入的依赖项和上一次保存的依赖项相同的话,回调函数不执行.
其实我们只需要理解这行代码就足够了, 他是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