![c32894d0ef2becc69aaed4d44b48d3c9.png](https://i-blog.csdnimg.cn/blog_migrate/4d5ab41dba858a669e0c6ba7ac28988f.jpeg)
前言
本文不会描述太多 react-hooks
的基本用法,直接参考源码实现一款简易版的 react-hooks
, 大家可以自己debugger一下源码,这样的话更容易理解。
更多学习笔记可以关注
公众号:若成诗
博客:博客
useState 的基本应用和实现
基本用法
我们先来看下 useState
的基本用法
![aead152b886298d94bb4c32df788c4d3.png](https://i-blog.csdnimg.cn/blog_migrate/3bab24114c7adc90e6efa54f233953de.jpeg)
上述代码中将 render 逻辑抽离出来,是为了自己实现对应hooks的时候有个方法可以触发页面的重新渲染
现在我们使用hooks实现了一个简单的点击按钮修改 number
和 name
的例子, useState
函数接收一个初始值(也可以接收一个函数,下文中不涉及传入函数的处理), 然后返回一个[state, setState]
, 分别对应着当前的 state
值以及修改这个值方法。
那我们试下模仿源码实现一个简易版的 useState
, 如果对链表不是很熟悉的话,可能有点绕~~
实现 useState
我们先看下下面代码
![059f539a54e3856be016c60dfbbd3fc3.png](https://i-blog.csdnimg.cn/blog_migrate/0b8ec0c2f3fb4f1bc5b1776fafe573d1.jpeg)
只是看代码不是很好理解, 我们画图一步一步解析这个流程
我们看下 firstWorkInProgressHook
和 workInProgressHook
到底是什么
![72db123305c0f2b148e9dcfd9a040c0c.png](https://i-blog.csdnimg.cn/blog_migrate/6693a57045843e77ed55270fba99ae80.jpeg)
第一第二行代码,声明了一个对象节点, 我们也可以理解为第一个初始化节点, 对象里面分别是 memoizedState
和 next
, 意思是记忆的数据和下一个指向, 然后 firstWorkInProgressHook
和 workInProgressHook
我们可以理解为它们分别是两个指针,指向第一个初始化节点。
然后我们第一次调用 useState(0)
的时候, 我们发现 workInProgressHook.next
的值(也就是初始化节点的next属性)为空, 所有我们创建了一个新的节点 currentHook
, 意义是当前的节点,并且 memoizedState
和 next
为 0(传入的初始值) 和 null, 也就是对应这下图
![58b0e1e9aab06b6b497c606a85f52392.png](https://i-blog.csdnimg.cn/blog_migrate/9467d8377ba0bc2f24052f2455430509.jpeg)
再然后我们继续往下走,因为workInProgressHook.next
的值为 null, 所以我们走 else 逻辑, 在else 逻辑中, 我们将currentHook
赋值给workInProgressHook.next
, 意味着我们将第一次初始化节点通过next
和当前的节点连接在了一起。然后将currentHook
的值赋值给workInProgressHook
, 意思就是将workInProgressHook
指针指向了当前节点,如下图
![90dea41ae458d5bd474a2e26f6db3c75.png](https://i-blog.csdnimg.cn/blog_migrate/3645f0598ffd15fb81834a98ff8a3ab4.jpeg)
同理当我们第二次调用useState('cy')
的时候, 还是走相同的逻辑,然后得到的结构是这样的
![928719743024ee13635996b10b8024e8.png](https://i-blog.csdnimg.cn/blog_migrate/4aa30c9cfae90fb53e32613acca3c8cb.jpeg)
到现在我们已经完成了第一次渲染,也就是根据useState
初始化我们的数据链表, 现在我们完善一下我们的代码,增加两行关键性代码
![1ef0ca5367724e18a46e8dfde2024d5d.png](https://i-blog.csdnimg.cn/blog_migrate/9c5f8ae22ec0fe7b41452f3fa253f40e.jpeg)
当我们点击按钮add number
的时候, 我们不难发现实际上就是给第二个节点赋值, 我们把思路集中在链表回退这个逻辑。
在setState
函数中,我们手动调用了 render()
方法,在 render()
方法里面我们将workInProgressHook
指向了第一个初始化节点firstWorkInProgressHook
, 如下图,在我们每一次调用setState
的时候, 也就是重新渲染页面的时候会出现这种效果
![b692af5fdf0b059f5c5170819328a6db.gif](https://i-blog.csdnimg.cn/blog_migrate/0bd4e3c4868a5f6c9aa810cc6846255a.gif)
因为页面重新渲染了, 我们就会再一次走useState
的逻辑,不同的是,这个时候workInProgressHook.next
已经有值了,走的是 if 逻辑,workInProgressHook = workInProgressHook.next
, 保证不管页面重新渲染多少次,我们通过[state, setState]中state(currentHook.memoizedState)获取到的都是同一个值。
![e2c3b7fe24ceb5553e9c54ffbf0c3e14.gif](https://i-blog.csdnimg.cn/blog_migrate/6ba557044a9d42fe0077e2b1884e8067.gif)
现在我们知道为什么只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用了,因为 useState的存储数据结构和存储的顺序是密切相关的,如果我们在条件判断等里面调用 useState 会导致数据对不上,导致出错
useReducer 的基本实现
基本用法
我们先来看下 useReducer
的基本用法
![7726364cc8b43ca4df3e960fb3954fb3.png](https://i-blog.csdnimg.cn/blog_migrate/ff02e8d434e8d7aee25dd42bd013f51d.jpeg)
useReducer
接收3个参数,第一个是reducer
, 第二个参数是state的初始值,第三个参数是一个函数,如果这个函数存在,则将useReducer
的第二个参数作为这个函数的参数,执行后的结果作为初始值。
知道思路之后其实就不难了,我们来实现它
![3e49cdc4b7b8897126b87966e8621a9d.png](https://i-blog.csdnimg.cn/blog_migrate/8f3a64ce49c86b60d8ae28d36130bdc0.jpeg)
useEffect 的基本实现
基本用法
我们知道 useEffect
给函数组件添加了操作副作用的能力,例如修改DOM、定时器这些.
还有最重要一点是 useEffect
可以替代类组件中的didmount
、 didupdate
、 willunmount
我们来实现一个具有打印功能的计时器例子,来了解 useEffect
的基本用法吧
![d98070260cdeaf92e9c4b02f3704407d.png](https://i-blog.csdnimg.cn/blog_migrate/c0dd02bc9db2797668ce1913789a83c4.jpeg)
我们可以发现,只有当每次name发生改变之后,才会执行回调函数(其他基础用法这里就不赘述了), 这也是我们实现的时候最需要注意的一点, 当我们传入的依赖项和上一次保存的依赖项相同的话,回调函数不执行.
![e46cb88a8fdf6e02170386bf5639f6d0.png](https://i-blog.csdnimg.cn/blog_migrate/7a628cf4292ca7cf4f50293fee82eb70.jpeg)
其实我们只需要理解这行代码就足够了, 他是useEffect
的核心
let changed = lastDependencies ? !dependencies.every((item, index) => item === lastDependencies[index]) : true
当我们第一次执行useEffect
的时候,lastDependencies
为undefined, 所以第一次的时候changed
为真,所以执行回调。第二次执行useEffect
的时候,遍历依赖项和保存的最后一次修改的依赖项,如果两者相等,则返回false,不执行回调函数。
总结
还有其他的一些知识点这里就不赘述了,因为你会发现,除了useState
使用的链表存储的数据结构难理解一点,剩余的hooks大多数都是依靠一个全局的变量来存储值,而且需要注意的是,useState
内部其实是用useReducer
来实现的,有时间的话一定要试一下自己用useReducer
来实现一款链表的useState