模拟 setState

113 阅读2分钟

setState是同步还是异步?

如果是正常情况下 也就是没有使用Concurrent组件到的情况下 react的更新是同步的 但是! 不会立即获取到最新的state的值 因为正常情况下 调用setState只是单纯的将你 传进来的新的状态 放入一条链表中 等这个事件执行完毕之后 会触发一个回调函数 这个函数中 才会真正地执行react的更新状态以及渲染的流程 并且 是以完全同步的方式进行的

当使用了Concurrent组件的时候 这种情况下 才是真正的异步更新模式 同样的 无法在事件中 立即获取到最新的状态 并且在执行react的更新和渲染的流程中 使用了真正的异步方式(postMessage) 这个才是真正的异步

简单实现 setState

流程如下

  1. 初始化创建Updater实例,挂载在组件的 $updater 上, $updater 上有两个属性,一个是当前的组件, 一个是存放更新的state 数组 pendingStates 是一个数组;
  2. react 的默认更新策略isBatchingUpdatesfalse;
  3. 当点击时间触发调用setState时,调用$updater 的addState 方法,详情在Transaction.perform,
  4. react 的更新策略是isBatchingUpdates 是否批量更新在点击变成 true,
  5. 然后执行点击事件,调用 $updateraddState 方法,会将 更新的 state 方法 pendingStates 这个数组中, 判断此时的 isBatchingUpdates 的值, 当为true 时,将当前组件放入dirtyCommpnents 中,当事件执行完毕状态会被修改为false, 此时调用 batchingStrategy.batchedUpdates方法, 主要是为了更新操作; 为fasle 时直接调用组件的 updateCommponent 方法.
  6. 事件方法执行完成之后,再将react 的更新策略是isBatchingUpdates 是否批量更新在点击变成 false
class Transaction{// 交易 封装一下 事件处理是个合成事件
    constructor(wrappers){
        this.wrappers = wrappers;//{initialize,close}
    }
    perform(anyMethod){
        this.wrappers.forEach(wrapper=>wrapper.initialize()); //处理事件之前先修改策略
        anyMethod.call(); //执行事件
        this.wrappers.forEach(wrapper=>wrapper.close()); // 处理完事件之后恢复策略
    }
}
//batchingStrategy.isBatchingUpdates batchedUpdates
let batchingStrategy = { // 更新策略
    isBatchingUpdates:false,//默认是非批量更新模式
    dirtyComponents:[],// 脏组件 就组件的状态和界面上显示的不一样
    batchedUpdates(){
        console.log(1111111)
        this.dirtyComponents.forEach(component=>component.updateComponent());
    }
}
class Updater{ // 更新类 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);
    }
    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.createDOMFromDOMString();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    //把一个DOM模板字符串转成真实的DOM元素
    createDOMFromDOMString(){
        //this;
        let htmlString = this.render();
        let div = document.createElement('div');
        div.innerHTML = htmlString;
        this.domElement =  div.children[0];
        //让这个BUTTONDOM节点的component属性等于当前Counter组建的实例
        this.domElement.component = this;
        //this.domElement.addEventListener('click',this.add.bind(this));
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.createDOMFromDOMString());
    }
}
let transaction = new Transaction([
    {
        initialize(){
            batchingStrategy.isBatchingUpdates = true;//开始批量更新模式
        },
        close(){
            batchingStrategy.isBatchingUpdates = false;
            batchingStrategy.batchedUpdates();//进行批量更新,把所有的脏组件根据自己的状态和属性重新渲染
        }
    }
]);
window.trigger = function(event,method){
 let component = event.target.component;//event.target=this.domElement
 transaction.perform(component[method].bind(component));
}
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);//2
            this.setState({number:this.state.number+1});
            console.log(this.state);//3
        },1000);
    }
    render(){
        return `<button onclick="trigger(event,'add')">
            ${this.props.name}:${this.state.number}
            </button>`;
    }
}