setState实现
setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
let batchingStrategy = {
// 默认是非批量更新模式
isBatchingUpdates: false,
// 脏组件 组件的状态改变了
dirtyComponents: [],
// 批量更新的方法
batchedUpdates() {},
};
class Transaction {
constructor(wraps) {
this.wraps = wraps || [];
}
perform(fn) {
this.wraps.forEach((wrap) => wrap.initialize());
fn.call();
this.wraps.forEach((wrap) => wrap.close());
}
}
let transaction = new Transaction([
{
initialize() {
// 开启批量更新模式
batchingStrategy.isBatchingUpdates = true;
},
close() {
// 关闭批量更新模式
batchingStrategy.isBatchingUpdates = false;
// 将脏组件全部更新
batchingStrategy.dirtyComponents.forEach((component) =>
component.updateComponent()
);
},
},
]);
window.trigger = function (event, method) {
let component = event.target.component;
transaction.perform(component[method].bind(component));
};
class Updater {
constructor(component) {
this.component = component;
this.pendingStates = [];
}
addState(partcialState) {
this.pendingStates.push(partcialState);
// 判读是否批量更新,是批量更新,就先把组件缓存起来
// 不是批量更新,直接更新
batchingStrategy.isBatchingUpdates
? batchingStrategy.dirtyComponents.push(this.component)
: this.component.updateComponent();
}
}
class Component {
constructor(props) {
this.props = props;
this.$updater = new Updater(this);
}
renderElement() {
let htmlStr = this.render();
let div = document.createElement("div");
div.innerHTML = htmlStr;
this.domElement = div.children[0];
// 将组件实例挂到dom元素上
this.domElement.component = this;
return this.domElement;
}
mount(container) {
container.appendChild(this.renderElement());
}
setState(partcialState) {
this.$updater.addState(partcialState);
}
updateComponent() {
this.$updater.pendingStates.forEach((partcialState) =>
Object.assign(this.state, partcialState)
);
this.$updater.pendingStates.length = 0;
let oldElement = this.domElement;
let newElement = this.renderElement();
oldElement.parentElement.replaceChild(newElement, oldElement);
}
}
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
number: 0,
};
}
add() {
this.setState({
number: this.state.number + 1,
});
console.log(this.state); //0
this.setState({
number: this.state.number + 1,
});
console.log(this.state); //0
setTimeout((_) => {
this.setState({
number: this.state.number + 1,
});
console.log(this.state); //3
this.setState({
number: this.state.number + 1,
});
console.log(this.state); //4
});
}
render() {
return `<button οnclick="trigger(event,'add')" >${this.props.name}:${this.state.number}</button>`;
}
}