简单手写实现React类组件的state批量更新
继续学习写源码系列,本系列是学习系列。
- 简单手写实现 React 的 createElement 和 render,里面有准备工作,不再赘述。
- 简单手写实现 react 的函数组件
- 简单手写实现 react 的类组件
- 简单手写实现 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 更新一般发生的情况:
- 请求
- 交互事件
React17 之前,触发事件的时候,同一事件里面的 setState 会被攒在一起,然后更新。 而事件之外的,每次 setState,state 就会当场更新。
setTimeout 和请求 都是在事件之外的,所以会当场更新。
核心原理是,事件函数被处理了,同一事件函数,里面的 setState 被放到集合里,事件函数结束的时候一起更新。而其他的并未做处理。
分析 state 的批量更新
- setState 的时候,之前直接更新,设置一个 flag,才判断是是更新还是将修改状态暂存
- 事件触发,事件开始的时候,设置一个 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
打开自己文件的路径
正常运行,✌️~~~