React源码解析18(11)------ 实现多次setState的批处理

这篇具有很好参考价值的文章主要介绍了React源码解析18(11)------ 实现多次setState的批处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

摘要

在React中,如果涉及到了多次setState,组件render几次。setState是同步的还是异步的。这是一个很常见的面试题。

而本篇文章,就是主要实现React中,对于这部分的性能优化,我们称之为批处理。例如当我有下面的JSX。

const root = document.querySelector('#root');

function App() {
  const [num, setNum] = useState(0)
  const click1 = () => {
    setNum(num + 1)
    setNum(num + 2)
    setNum(num + 3)
  }
  return jsx("div", {
    onClick: click1,
    children: num
  });
}

ReactDOM.createRoot(root).render(<App />)

对于当前的点击事件来说,只有最后的setNum(num + 3)是有效的。

但是在我们之前的实现中,对于这种连续三次setState,我们的代码就要处理三次,就要经过三次beginWork,completeWork,commitWork。

那我们能不能实现出一种方式,对于这种多次setState,最终之做最后一次处理。

1.修改update相关逻辑

目前,在我们的代码里,能够让组件更新的方式,就是通过setState,触发更新。而updateQueue就是用来保存更新的内容。

function enqueueUpdate(updateQueue, update) {
  updateQueue.shared.pending = update
}

之前,在enqueueUpdate方法里,我们是直接进行赋值的。这没什么问题,因为之前每次赋值之后都会更新一下。

但是现在我们希望,最后只更新一次的话。我们就需要一个数据结构,可以保存多次更新的内容。这里使用的是链表结构,但在真正的React源码中,使用的是环形链表。

function enqueueUpdate(updateQueue, update) {
  // updateQueue.shared.pending = update

  let pending = updateQueue.shared.pending;
  if(pending === null) {
    updateQueue.shared.pending = update
  }else{
    while(pending.next != null) {
      pending = pending.next;
    }
    pending.next = update
    pending = pending.next;
  }
}

OK,修改完之后,我们调用了三次setState,那么updateQueue中保存的应该是一个链表了。

在之前的processUpdateQueue方法里,也是直接更新就完了。但是现在updateQueue的结构发生了变化,所以对于processUpdateQueue,更新逻辑也要改变。

function processUpdateQueue(baseState, pendingUpdate) {
  const result = {
    memoizedState: baseState
  }
  if(pendingUpdate.next === undefined) {
    const action = pendingUpdate.action;
    //setState(() => {}) 传入方法
    if(typeof action === 'function'){
      result.memoizedState = action(baseState);
    }else {
      //setState()
      result.memoizedState = action;
    }
    return result
  }
  while(pendingUpdate != null) {
    const action = pendingUpdate.action;
    //setState(() => {}) 传入方法
    if(typeof action === 'function'){
      baseState = action(baseState);
    }else {
      //setState()
      baseState = action;
    }
    pendingUpdate = pendingUpdate.next;
  }
  result.memoizedState = baseState;
  return result;
}

我们需要遍历pending,将所有的更新内容返回。

2.修改workLoop循环

我们想一下,之前触发更新后,执行的机制是什么样子的。

  1. 更新updateQueue
  2. 拿到更新的updateQueue,挂载Hook上
  3. 执行workLoop

也就是说,每次workLoop都只能拿到当前更新的内容。
如果我希望在第一次workLoop就可以拿到所有的更新内容,并且取消后面的workLoop。

有什么方法呢?微任务!!!!

我们可以将执行workLoop的过程放在微任务里,这样执行workLoop的时候,updateQueue的链表已经生成。

同时我们用一个标志位,取消后面的workLoop执行。
当workLoop执行完成后,再将标志位置反。

let isFinished = false;
export function syncWorkLoop(root,hostRootFilber) {
  if(isFinished) {
    return;
  }
  Promise.resolve(null).then(() => {
    wookLoop(root,hostRootFilber);
  })
  isFinished = true;`在这里插入代码片`
}
const wookLoop = (root,hostRootFilber) => {
  //其他代码。。。。。
  isFinished = false;
}

3.修改filberHook

最后,我们只要在filberHook中触发的逻辑里,替换workLoop即可:

function disaptchState(filber, hook, action) {
  const update = createUpdate(action);
  enqueueUpdate(hook.updateQueue, update);
  workUpdateHook = hook;
  syncWorkLoop(filber.return.stateNode);
}

通过这种方式,当我执行多次setState,最终只会render一次。文章来源地址https://www.toymoban.com/news/detail-666589.html

到了这里,关于React源码解析18(11)------ 实现多次setState的批处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • React源码解析18(5)------ 实现函数组件【修改beginWork和completeWork】

    经过之前的几篇文章,我们实现了基本的jsx,在页面渲染的过程。但是如果是通过函数组件写出来的组件,还是不能渲染到页面上的。 所以这一篇,主要是对之前写得方法进行修改,从而能够显示函数组件,所以现在我们在index.js文件中,修改一下jsx的写法。修改成函数组件

    2024年02月13日
    浏览(25)
  • React源码解析18(1)------ React.createElement 和 jsx

    我们知道在React17版本之前,我们在项目中是一定需要引入react的。 import React from “react” 即便我们有时候没有使用到React,也需要引入。原因是什么呢? 在React项目中,如果我们使用了模板语法JSX,我们知道它要先经过babel的转译。那babel会将JSX转换成什么样子的格式呢? 可

    2024年02月13日
    浏览(26)
  • React中的setState使用细节和原理解析

    前面我们有使用过setState的基本使用, 接下来我们对setState使用进行详细的介绍 使用setState的原因 开发中我们并不能直接通过修改state的值来让界面发生更新 : 因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是 this.state 这种方式的修改React并不知道数据发

    2024年02月03日
    浏览(29)
  • React源码解析18(2)------ FilberNode,FilberRootNode结构关系

    在上一篇,我们实现了通过JSX转换为ReactElement的方法,也看到了转换后React元素的结构。但是这个React元素,并不能很清楚的表达组件之间的关系,以及属性的处理。 所以在React内部,会将所有的React元素转换为Filber树。而这一章节,主要就是简单描述一下FilberNode的结构。 首

    2024年02月13日
    浏览(21)
  • React源码解析18(3)------ beginWork的工作流程【mount】

    OK,经过上一篇文章。我们调用了: 生成了FilberRootNode和HostRootFilber。 并且二者之间的对应关系也已经确定。 而下一步我们就需要调用render方法来讲react元素挂载在root上: 所以我们现在要实现ReactDOM.createRoot中返回的render方法。 首先我们思考一下,在React中,除了通过上面的

    2024年02月13日
    浏览(33)
  • React源码解析18(4)------ completeWork的工作流程【mount】

    经过上一章,我们得到的FilberNode已经具有了child和return属性。一颗Filber树的结构已经展现出来了。 那我们最终是想在页面渲染真实的DOM。所以我们现在要在completeWork里,构建出一颗离屏的DOM树。 之前在说FilberNode的属性时,我们提到过一个属性stateNode。它就是用来保存每个

    2024年02月13日
    浏览(27)
  • 前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?

    在React中, setState 方法是用于更新组件状态的重要方法。当 setState 被调用时,React会对组件进行重新渲染,以反映状态的变化。 具体的执行过程如下: 调用 setState 入口函数 :当你在组件中调用 setState 方法时,实际上是调用了React组件的 setState 方法。这个方法在内部充当一

    2024年04月17日
    浏览(33)
  • react中的setState是同步还是异步

    setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。 合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件 原生事件:比如通过addeventListener添加的,dom中的原生事件 setState的“异步”并不是说内部由异步代码实现,其实本身

    2024年02月04日
    浏览(24)
  • 【React】组件生命周期、组件通信、setState

    ◼ 组件化思想的应用: ​  有了组件化的思想,我们在之后的开发中就要充分的利用它。 ​  尽可能的将页面拆分成一个个小的、可复用的组件。 ​  这样让我们的代码更加方便组织和管理,并且扩展性也更强。 ◼ React的组件相对于Vue更加的灵活和多样,按照不同的

    2024年01月20日
    浏览(29)
  • React中setState是同步还是异步的

    setState()同步、异步总结 异步的情况: 由React控制的事件处理函数,以及生命周期函数调用setState时表现为异步 。 大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等(合成事件中),这些事件处理函数中的setState都是异步处理的。 注:上面的事件都是

    2024年02月11日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包