继续学习写源码系列,本系列是学习系列。
- 简单手写实现 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打开自己文件的路径
正常运行,✌️~~~