简单手写实现React类组件的state批量更新

简介: 简单手写实现React类组件的state批量更新

简单手写实现React类组件的state批量更新


继续学习写源码系列,本系列是学习系列。

  1. 简单手写实现 React 的 createElement 和 render,里面有准备工作,不再赘述。
  2. 简单手写实现 react 的函数组件
  3. 简单手写实现 react 的类组件
  4. 简单手写实现 React 类组件的 state 更新

本文的目标是,手写实现React类组件的 state 批量更新。

TL;DR

  • 拆分setState逻辑,每一个类组件绑定一个Updater实例,Updater专门处理state的逻辑,以及什么时候更新
  • 增加pendingStates,这样state更新的时候,辅助批量更新
  • 增加updateQueue来控制批量更新,这里用了观察者模式,订阅更新
  • 处理事件逻辑,切片事件函数,执行前增加flag的开始,执行后开始更新和重置flag。

准备工作

先将index.js的点击事件略微修改

import React from './source/react';
import ReactDOM from './source/react-dom';
// import React from 'react';
// import ReactDOM from 'react-dom';
class Count extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 1 };
  }
  handleClick = () => {
    this.setState({ number: this.state.number + 1 });
    // {number:1}
    console.log('第一次setState', this.state);
    this.setState({ number: this.state.number + 1 });
    // {number:1}
    console.log('第二次setState', this.state);
    setTimeout(() => {
      this.setState({ number: this.state.number + 1 });
      // {number:3}
      console.log('setTimeout第一次setState', this.state);
      this.setState({ number: this.state.number + 1 });
      // {number:4}
      console.log('setTimeout第一次setState', this.state);
    });
  };
  render() {
    return (
      <div className="box">
        {this.state.number} <br />
        <button onClick={this.handleClick}>增加</button>
      </div>
    );
  }
}
const reactElement = <Count />;
ReactDOM.render(reactElement, document.getElementById('root'));

点击之后,看控制台:

网络异常,图片无法展示
|

state 怎么更新的?同步?异步?

state 更新一般发生的情况:

  1. 请求
  2. 交互事件

React17 之前,触发事件的时候,同一事件里面的 setState 会被攒在一起,然后更新。 而事件之外的,每次 setState,state 就会当场更新。

setTimeout 和请求 都是在事件之外的,所以会当场更新。

核心原理是,事件函数被处理了,同一事件函数,里面的 setState 被放到集合里,事件函数结束的时候一起更新。而其他的并未做处理。

分析 state 的批量更新

  1. setState 的时候,之前直接更新,设置一个 flag,才判断是是更新还是将修改状态暂存
  2. 事件触发,事件开始的时候,设置一个 flag,setState 都放进集合里;事件结束的时候 flag 重置,更新 state

实现

1.增加 Update 类,将 setState 的逻辑拆分

setState主要是增加状态,而状态是否真的增加、是否更新组件等需要额外的逻辑,考虑到逻辑略复杂,所以抽象到一个类Updater里,每个类组件关联一个Updater实例,用来管理state

class Updater{
  constructor(componentInstance){
    this.componentInstance = componentInstance
  }
  addState(partialState){
    // 刚开始是状态增加,就立马更新
    const {state} = this.componentInstance
    this.componentInstance.state = { ...state, ...partialState };
    this.componentInstance.updateDom()
  }
}
  setState(partialState) {
    // 每次setState之后,让updater去处理逻辑,这边只是单纯的加状态
    this.updater.addState(partialState)
  }

2.增加是否批量更新的逻辑

这里,为了后期方便,运用观察者模式,利用一个对象来控制批量更新的逻辑。 因为增加批量更新的逻辑,所以addState原逻辑简单拆分,更新逻辑分离出去,且因为 state 可能会多次累加,所以这边也增加一个getState方法,能计算到新的state

let updateQueue = {
  isBatchUpdating: false,
  updaters: new Set(),
  // 批量更新,目前是只有在事件结束的时候才会调用
  batchUpdate() {
    // 挨个更新
    for (let update of updateQueue.updaters) {
      update.updateComponent();
    }
    // 重置updaters
    updateQueue.updaters.clear();
    // 重置isBatchUpdating
    updateQueue.isBatchUpdating = false;
  },
};
class Updater {
  // ...
  addState(partialState) {
    this.pendingStates.push(partialState);
    // 是批量更新的话,将本实例加入到updateQueue
    if (updateQueue.isBatchUpdating) {
      updateQueue.updaters.add(this);
      return;
    }
    // 如果不是等着批量更新,那直接更新就好
    this.updateComponent();
  }
  updateComponent() {
    // 新状态的获取这边用一个方法计算出来,逻辑清晰
    this.componentInstance.state = this.getState();
    this.componentInstance.updateDom();
  }
  getState() {
    // 根据pendingStates得到新的state
    let { state } = this.componentInstance;
    const { pendingStates } = this;
    for (let i = 0; i < pendingStates.length; i++) {
      state = { ...state, ...pendingStates[i] };
    }
    pendingStates.length = 0;
    return state;
  }
}

3.事件绑定的时候,加上批量更新的逻辑

绑定事件的时候,将原事件稍微切片下,前后方都加上批量更新的逻辑

  • 设置批量更新的 flag 是 true
  • 事件运行
  • 运行结束,执行批量更新
function updateProps(DOM, props) {
  // ...
  if (/on[A-Z]+/.test(key)) {
    const eventFn = props[key];
    DOM[key.toLowerCase()] = (...args) => {
      updateQueue.isBatchUpdating = true;
      eventFn(...args);
      // batchUpdate内部有重置updateQueue的逻辑
      updateQueue.batchUpdate();
    };
  }
  // ...
}

老规矩,index.js打开自己文件的路径

正常运行,✌️~~~

目录
相关文章
|
1天前
|
前端开发 JavaScript 开发者
React 按钮组件 Button
本文介绍了 React 中按钮组件的基础概念,包括基本的 `&lt;button&gt;` 元素和自定义组件。详细探讨了事件处理、参数传递、状态管理、样式设置和可访问性优化等常见问题及其解决方案,并提供了代码示例。帮助开发者避免易错点,提升按钮组件的使用体验。
99 77
|
2天前
|
前端开发 UED 开发者
React 对话框组件 Dialog
本文详细介绍了如何在 React 中实现一个功能完备的对话框组件(Dialog),包括基本用法、常见问题及其解决方案,并通过代码案例进行说明。从安装依赖到创建组件、添加样式,再到解决关闭按钮失效、背景点击无效、键盘导航等问题,最后还介绍了如何添加动画效果和处理异步关闭操作。希望本文能帮助你在实际开发中更高效地使用 React 对话框组件。
96 75
|
7天前
|
前端开发 Java API
React 进度条组件 ProgressBar 详解
本文介绍了如何在 React 中创建进度条组件,从基础实现到常见问题及解决方案,包括动态更新、状态管理、性能优化、高级动画效果和响应式设计等方面,帮助开发者构建高效且用户体验良好的进度条。
35 18
|
22天前
|
存储 前端开发 测试技术
React组件的最佳实践
React组件的最佳实践
|
20天前
|
前端开发 API 开发者
React 文件上传组件 File Upload
本文详细介绍了如何在 React 中实现文件上传组件,从基础的文件选择和上传到服务器,再到解决文件大小、类型限制、并发上传等问题,以及实现多文件上传、断点续传和文件预览等高级功能,帮助开发者高效构建可靠的应用。
48 12
|
15天前
|
存储 前端开发 JavaScript
React 表单输入组件 Input:常见问题、易错点及解决方案
本文介绍了在 React 中使用表单输入组件 `Input` 的基础概念,包括受控组件与非受控组件的区别及其优势。通过具体代码案例,详细探讨了创建受控组件、处理多个输入字段、输入验证和格式化的方法,并指出了常见易错点及避免方法,旨在提升表单的健壮性和用户体验。
27 4
|
22天前
|
前端开发 JavaScript API
React 文件下载组件 File Download
本文介绍了在React中实现文件下载组件的方法,包括使用`a`标签和JavaScript动态生成文件,解决了文件路径、文件类型、大文件下载及文件名乱码等问题,并展示了使用第三方库`file-saver`和生成CSV文件的高级用法。
35 6
|
19天前
|
前端开发 JavaScript API
React 文件下载组件:File Download
本文详细介绍了如何在React应用中实现文件下载组件,包括基本概念、实现步骤和代码示例。同时,探讨了常见问题如文件类型不匹配、文件名乱码等及其解决方法,旨在提升用户体验和代码可维护性。
39 2
|
23天前
|
存储 前端开发 JavaScript
React 文件上传组件 File Upload
本文介绍了如何在 React 中实现文件上传组件,包括基本的概念、实现步骤、常见问题及解决方案。通过 `&lt;input type=&quot;file&quot;&gt;` 元素选择文件,使用 `fetch` 发送请求,处理文件类型和大小限制,以及多文件上传和进度条显示等高级功能,帮助开发者构建高效、可靠的文件上传组件。
65 2
|
24天前
|
存储 前端开发
在React框架中,如何使用对象来管理组件的状态
在React中,组件状态通过`state`对象管理,利用`setState`方法更新状态。状态变化触发组件重新渲染,实现UI动态更新。对象结构清晰,便于复杂状态管理。