文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

useState执行流程使怎样的

2023-06-27 11:10

关注

这篇文章主要介绍“useState执行流程使怎样的”,在日常操作中,相信很多人在useState执行流程使怎样的问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”useState执行流程使怎样的”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

作为 React 开发者,你能答上如下两个问题么:

  1. 对于如下函数组件:

function App() {  const [num, updateNum] = useState(0);  window.updateNum = updateNum;  return num;}

调用window.updateNum(1)可以将视图中的0更新为1么?

  1. 对于如下函数组件:

function App() {  const [num, updateNum] = useState(0);    function increment() {    setTimeout(() => {      updateNum(num + 1);    }, 1000);  }    return <p onClick={increment}>{num}</p>;}

在1秒内快速点击p5次,视图上显示为几?

1. 可以2. 显示为1

其实,这两个问题本质上是在问:

本文会结合源码,讲透如上两个问题。

这些,就是你需要了解的关于useState的一切。

hook如何保存数据

FunctionComponentrender本身只是函数调用。

那么在render内部调用的hook是如何获取到对应数据呢?

比如:

答案是:

每个组件有个对应的fiber节点(可以理解为虚拟DOM),用于保存组件相关信息。

每次FunctionComponent render时,全局变量currentlyRenderingFiber都会被赋值为该FunctionComponent对应的fiber节点

所以,hook内部其实是从currentlyRenderingFiber中获取状态信息的。

多个hook如何获取数据

我们知道,一个FunctionComponent中可能存在多个hook,比如:

function App() {  // hookA  const [a, updateA] = useState(0);  // hookB  const [b, updateB] = useState(0);  // hookC  const ref = useRef(0);    return <p></p>;}

那么多个hook如何获取自己的数据呢?

答案是:

currentlyRenderingFiber.memoizedState中保存一条hook对应数据的单向链表。

对于如上例子,可以理解为:

const hookA = {  // hook保存的数据  memoizedState: null,  // 指向下一个hook  next: hookB  // ...省略其他字段};hookB.next = hookC;currentlyRenderingFiber.memoizedState = hookA;

FunctionComponent render时,每执行到一个hook,都会将指向currentlyRenderingFiber.memoizedState链表的指针向后移动一次,指向当前hook对应数据。

这也是为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。

useState执行流程

我们知道,useState返回值数组第二个参数为改变state的方法

在源码中,他被称为dispatchAction

每当调用dispatchAction,都会创建一个代表一次更新的对象update

const update = {  // 更新的数据  action: action,  // 指向下一个更新  next: null};

对于如下例子

function App() {  const [num, updateNum] = useState(0);    function increment() {    updateNum(num + 1);  }    return <p onClick={increment}>{num}</p>;}

调用updateNum(num + 1),会创建:

const update = {  // 更新的数据  action: 1,  // 指向下一个更新  next: null  // ...省略其他字段};

如果是多次调用dispatchAction,例如:

function increment() {  // 产生update1  updateNum(num + 1);  // 产生update2  updateNum(num + 2);  // 产生update3  updateNum(num + 3);}

那么,update会形成一条环状链表。

update3 --next--> update1  ^                 |  |               update2  |______next_______|

这条链表保存在哪里呢?

既然这条update链表是由某个useStatedispatchAction产生,那么这条链表显然属于该useState hook

我们继续补充hook的数据结构。

const hook = {  // hook保存的数据  memoizedState: null,  // 指向下一个hook  next: hookForB  // 本次更新以baseState为基础计算新的state  baseState: null,  // 本次更新开始时已有的update队列  baseQueue: null,  // 本次更新需要增加的update队列  queue: null,};

其中,queue中保存了本次更新update的链表。

在计算state时,会将queue的环状链表剪开挂载在baseQueue最后面,baseQueue基于baseState计算新的state

在计算state完成后,新的state会成为memoizedState

为什么更新不基于memoizedState而是baseState,是因为state的计算过程需要考虑优先级,可能有些update优先级不够被跳过。所以memoizedState并不一定和baseState相同。

回到我们开篇第一个问题:

function App() {  const [num, updateNum] = useState(0);  window.updateNum = updateNum;  return num;}

调用window.updateNum(1)可以将视图中的0更新为1么?

我们需要看看这里的updateNum方法的具体实现:

updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);

可见,updateNum方法即绑定了currentlyRenderingFiberqueue(即hook.queue)的dispatchAction

上文已经介绍,调用dispatchAction的目的是生成update,并插入到hook.queue链表中。

既然queue作为预置参数已经绑定给dispatchAction,那么调用dispatchAction就步仅局限在FunctionComponent内部了。

update的action

第二个问题

function App() {  const [num, updateNum] = useState(0);    function increment() {    setTimeout(() => {      updateNum(num + 1);    }, 1000);  }    return <p onClick={increment}>{num}</p>;}

在1秒内快速点击p5次,视图上显示为几?

我们知道,调用updateNum会产生update,其中传参会成为update.action

在1秒内点击5次。在点击第五次时,第一次点击创建的update还没进入更新流程,所以hook.baseState还未改变。

那么这5次点击产生的update都是基于同一个baseState计算新的state,并且num变量也还未变化(即5次update.action(即num + 1)为同一个值)。

所以,最终渲染的结果为1。

useState与useReducer

那么,如何5次点击让视图从1逐步变为5呢?

由以上知识我们知道,需要改变baseState或者action

其中baseState由 React 的更新流程决定,我们无法控制。

但是我们可以控制action

action不仅可以传,也可以传函数

// action为值updateNum(num + 1);// action为函数updateNum(num => num + 1);

在基于baseStateupdate链表生成新state的过程中:

let newState = baseState;let firstUpdate = hook.baseQueue.next;let update = firstUpdate;// 遍历baseQueue中的每一个updatedo {  if (typeof update.action === 'function') {    newState = update.action(newState);  } else {    newState = action;  }} while (update !== firstUpdate)

可见,当传时,由于我们5次action为同一个值,所以最终计算的newState也为同一个值。

而传函数时,newState基于action函数计算5次,则最终得到累加的结果。

如果这个例子中,我们使用useReducer而不是useState,由于useReduceraction始终为函数,所以不会遇到我们例子中的问题。

事实上,useState本身就是预置了如下reduceruseReducer

function basicStateReducer(state, action) {  return typeof action === 'function' ? action(state) : action;}

到此,关于“useState执行流程使怎样的”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯