简单手写实现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打开自己文件的路径

正常运行,✌️~~~

目录
相关文章
|
16天前
|
前端开发 UED 索引
React 图片灯箱组件 Image Lightbox
图片灯箱组件是一种常见的Web交互模式,用户点击缩略图后弹出全屏窗口展示大图,并提供导航功能。本文介绍了基于React框架的图片灯箱组件开发,涵盖初始化状态管理、图片加载与预加载、键盘和鼠标事件处理等常见问题及解决方案。通过`useState`和`useEffect`钩子管理状态,使用懒加载和预加载优化性能,确保流畅的用户体验。代码案例展示了组件的基本功能实现,包括打开/关闭灯箱、切换图片及键盘操作。
114 80
|
15天前
|
移动开发 前端开发 JavaScript
React 视频播放器组件:Video Player
本文介绍了如何使用 React 和 HTML5 `&lt;video&gt;` 标签构建自定义视频播放器组件。首先,通过创建基础的 React 项目和 VideoPlayer 组件,实现了基本的播放、暂停功能。接着,探讨了常见问题如视频加载失败、控制条样式不一致、性能优化不足及状态管理混乱,并提供了相应的解决方案。最后,总结了构建高效视频播放器的关键要点,帮助开发者应对实际开发中的挑战。
65 27
|
18天前
|
前端开发 JavaScript API
React 图片放大组件 Image Zoom
本文介绍如何使用React创建图片放大组件(Image Zoom),提升用户体验。组件通过鼠标悬停或点击触发放大效果,利用`useState`管理状态,CSS实现视觉效果。常见问题包括图片失真、性能下降和移动端支持,分别可通过高质量图片源、优化事件处理和添加触摸事件解决。易错点涉及状态管理混乱、样式冲突和过多事件绑定,建议使用上下文API、CSS模块及优化事件绑定逻辑。高级功能扩展如多张图片支持和自定义放大区域进一步丰富了组件的实用性。
50 25
|
14天前
|
存储 编解码 前端开发
React 视频上传组件 Video Upload
随着互联网的发展,视频内容在网站和应用中愈发重要。本文探讨如何使用React构建高效、可靠的视频上传组件,涵盖基础概念、常见问题及解决方案。通过React的虚拟DOM和组件化开发模式,实现文件选择、进度显示、格式验证等功能,并解决跨域请求、并发上传等易错点。提供完整代码案例,确保用户能顺畅上传视频。
118 92
|
19天前
|
移动开发 前端开发 JavaScript
React 图片裁剪组件 Image Cropper
本文介绍了在React中实现图片裁剪功能的方法,涵盖基础知识、常见问题及解决方案。首先,通过第三方库如`react-image-crop`或`cropperjs-react`可轻松实现图片裁剪。接着,针对性能和兼容性问题,提供了优化图片加载、处理裁剪区域响应慢、解决浏览器差异等方案。最后,通过代码案例详细解释了如何创建一个基本的图片裁剪组件,并提出了优化建议,如使用`React.memo`、添加样式支持及处理大图片预览,帮助开发者避免常见错误并提升用户体验。
104 67
|
24天前
|
前端开发 JavaScript UED
React 拖拽排序组件 Draggable List
在现代Web应用中,拖拽排序功能显著提升用户体验。使用React结合`react-dnd`库,可以轻松创建高效且易于维护的拖拽排序组件。通过简单的拖拽操作,用户能直观调整列表项顺序,适用于任务管理、看板工具等场景。实现步骤包括项目初始化、安装依赖、创建基础组件、添加拖拽功能及管理状态和事件。常见问题如拖拽效果不流畅、顺序未更新等可通过性能优化、正确处理索引交换等方式解决。移动端支持也需考虑,确保跨平台的良好体验。
89 25
|
17天前
|
存储 前端开发 索引
React 图片轮播组件 Image Carousel
本文介绍了如何使用React创建图片轮播组件。首先,解释了图片轮播的基本概念和组件结构,包括图片容器、导航按钮、指示器和自动播放功能。接着,通过代码示例详细说明了创建基本组件、添加自动播放、处理边界情况的步骤。还探讨了常见问题如状态更新不及时、内存泄漏和样式问题,并提供了解决方案。最后,介绍了进阶优化,如添加过渡效果、支持触摸事件和动态加载图片,帮助读者构建更完善的轮播组件。
36 16
|
26天前
|
前端开发 JavaScript UED
React 轮播图组件 Carousel
本文介绍了如何在 React 中实现和优化轮播图组件,涵盖自动播放、手动切换、循环播放和响应式设计等核心功能。通过 `useState` 和 `useEffect` 钩子管理状态和副作用,添加左右箭头和指示器增强交互性。同时,探讨了常见问题如自动播放与手动切换冲突、指示器样式不一致、响应式设计及性能优化,并提供解决方案和代码示例。帮助开发者提升轮播图组件的用户体验。
74 26
|
21天前
|
移动开发 前端开发 数据可视化
React 拖拽布局组件 Drag & Drop Layout
本文介绍了如何在React中构建拖拽布局组件,涵盖基础知识、常见问题及解决方案。首先解释了拖拽操作的三个阶段:开始、过程中和结束。接着推荐了几个常用的拖拽库,如`react-beautiful-dnd`,并详细展示了如何使用该库创建基础拖拽组件,包括安装依赖、初始化容器和处理拖拽结束事件。文章还探讨了常见问题,如拖拽不生效、性能优化、嵌套拖拽和跨浏览器兼容性,并提供了进阶技巧,如自定义样式、多列布局和集成其他UI组件。通过这些内容,读者可以掌握构建高效拖拽布局的方法。
51 16
|
22天前
|
前端开发 安全 UED
React 文件预览组件:File Preview
在现代Web应用中,文件上传和预览功能至关重要。本文基于React库,详细介绍如何构建文件预览组件,涵盖文件选择器、图片预览、文件大小限制及多种文件类型支持。通过实际代码示例,解析常见问题如跨域请求、文件路径处理和状态管理,并提供解决方案。帮助开发者提升用户体验,减少误操作。
35 2