react 等待every 执行完_手摸手实现 react-hooks

本文深入解析React Hooks的useState、useReducer和useEffect的基本实现,通过源码分析和简易版实现,帮助理解其工作原理。文章详细介绍了useState的链表数据结构和useEffect的依赖项比较逻辑。
摘要由CSDN通过智能技术生成

c32894d0ef2becc69aaed4d44b48d3c9.png

前言

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

更多学习笔记可以关注
公众号:若成诗
博客:博客

useState 的基本应用和实现

基本用法

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

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

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

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

实现 useState

我们先看下下面代码

059f539a54e3856be016c60dfbbd3fc3.png

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

我们看下 firstWorkInProgressHookworkInProgressHook到底是什么

72db123305c0f2b148e9dcfd9a040c0c.png

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

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

58b0e1e9aab06b6b497c606a85f52392.png

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

90dea41ae458d5bd474a2e26f6db3c75.png

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

928719743024ee13635996b10b8024e8.png

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

1ef0ca5367724e18a46e8dfde2024d5d.png

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

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

b692af5fdf0b059f5c5170819328a6db.gif

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

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

useReducer 的基本实现

基本用法

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

7726364cc8b43ca4df3e960fb3954fb3.png

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

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

3e49cdc4b7b8897126b87966e8621a9d.png

useEffect 的基本实现

基本用法

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

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

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

d98070260cdeaf92e9c4b02f3704407d.png

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

e46cb88a8fdf6e02170386bf5639f6d0.png

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

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

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

总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值