React源码解读02,页面更新与批处理

页面更新与批处理

何为更新

  1. 那类组件来说,我们可以取出当前组件的虚拟dom,让该虚拟dom跟旧的虚拟dom进行比对,替换掉不同的地方。然后渲染到界面上去。从而完成更新。
  2. 我们在类组件中,操作state是通过setState()来操作的,该方法是Component类的方法,该方法修改实例中state的属性,又调用更新的方法。那我们可以简单地编写出如下代码
class Component {
    static isReactComponent = true
    constructor(props) {
        this.props = props;
        this.state = {};
        this.updater = new Updater(this);
    }
    setState(partialState, callback) {
        partialState() // 用户传入的方法
        forceUpdate() // 页面更新
        callback() // 用户传入的回调函数
    }
    forceUpdate() {
        let newVdom = this.render();
        updateClassComponent(this, newVdom);
    }
}
//TODO 更新类组件
function updateClassComponent(classInstance, newVdom) {
    let oldDOM = classInstance.dom;//取出这个类组件上次渲染出来的真实DOM
    // 下面我们应该使用diff算法进行比对,但是为了简化操作,我们选择直接替换。
    let newDOM = createDOM(newVdom);
    oldDOM.parentNode.replaceChild(newDOM, oldDOM);
    classInstance.dom = newDOM;
}

批处理

现象演示

请看以下例子

class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = { name: this.props.name, number: 0 };
    }
    handleClick = (syntheticEvent) => {
        this.setState((lastState) => ({ number: lastState.number + 1 }), () => {
            console.log('callback1', this.state.number);
        });
        console.log(this.state.number);
        this.setState((lastState) => ({ number: lastState.number + 1 }), () => {
            console.log('callback2', this.state.number);
        });
        console.log(this.state.number);
        Promise.resolve().then(() => {
            console.log(this.state.number);
            this.setState((lastState) => ({ number: lastState.number + 1 }), () => {
                console.log('callback3', this.state.number);
            });
            console.log(this.state.number);
            this.setState((lastState) => ({ number: lastState.number + 1 }), () => {
                console.log('callback4', this.state.number);
            });
            console.log(this.state.number);
        }, 0);
    }
    render() {
        return (
            <div>
                <p>{this.state.name}</p>
                <p>{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
ReactDOM.render((
    <Counter name="计数器" />
), document.getElementById('root'));

请问当我们点击+按钮时,控制台得到的结果是什么?
以下为结果
在这里插入图片描述
为什么会有这个结果呢?我们来分析一下

  1. 在React里,事件的更新可能是异步的,是批量的,不是同步的
  2. 调用state之后状态并没有立刻更新,而是先缓存起来了 等事件函数处理完成后,再进行批量更新,一次更新并重新渲染
  3. 因为jsx事件处理函数是react控制的,只要归react控制就是批量,只要不归react管了。就是非批量。也就是说在 Promise,setTimeout 中,更新逻辑是同步的。

调度队列

一个点击事件或者其他事件可能会调用多次setState方法,为了达到批处理,我们给Component类添加一个Updater任务队列。当我们在React中使用setState时,调用addState()将该函数插入到任务队列中。接着该方法通过updateQueue.isBatchingUpdate()判断当前是否为批处理。如果是,则直接放入到 updateQueue 更新队列中。否则,直接更新,并且当更新队列为空时,便会依次执行callbacks回调函数。
代码如下:
Component

class Component{
    static isReactComponent = true
    constructor(props){
        this.props = props;
        this.state = {};
        this.updater = new Updater(this);
    }
    setState(partialState,callback){
       this.updater.addState(partialState,callback);
    }
    forceUpdate(){
        let newVdom = this.render();
        updateClassComponent(this,newVdom);
    }
}
//TODO 更新类组件
function updateClassComponent(classInstance,newVdom){
   let oldDOM = classInstance.dom;//取出这个类组件上次渲染出来的真实DOM
   let newDOM = createDOM(newVdom);
   oldDOM.parentNode.replaceChild(newDOM,oldDOM);
   classInstance.dom=newDOM;
}

Updater

class Updater{
    constructor(classInstance){
        this.classInstance = classInstance;//类组件的实例
        this.pendingStates = [];//等待生效的状态,可能是一个对象,也可能是一个函数
        this.callbacks = [];
    }
    addState(partialState,callback){
        this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
        if(typeof callback ==='function')
            this.callbacks.push(callback);//状态更新后的回调
        if(updateQueue.isBatchingUpdate){//如果当前的批量模式。先缓存updater
            updateQueue.updaters.add(this);//本次setState调用结束 
        }else{
            this.updateClassComponent();//直接更新组件
        }
    }
    updateClassComponent(){
        let {classInstance,pendingStates,callbacks} = this;
        // 如果有等待更新的状态对象的话
        if(pendingStates.length>0){
            classInstance.state = this.getState();//计算新状态
            classInstance.forceUpdate();
            callbacks.forEach(callback=>callback());
            callbacks.length=0;
        }
    }
    getState(){
        let {classInstance,pendingStates} = this;
        let {state}=classInstance;
        pendingStates.forEach((nextState)=>{
            //如果pendingState是一个函数的话,传入老状态,返回新状态,再进行合并
            if(typeof nextState === 'function'){
                nextState=nextState(state);
            }
            state={...state,...nextState};
        });
        pendingStates.length=0;//清空数组
       
        return state;
    }
}

更新队列 updateQueue

let updateQueue = {
    isBatchingUpdate:false,//当前是否处于批量更新模式,默认值是false
    updaters:new Set(),
    batchUpdate(){//批量更新
      for(let updater of this.updaters){
        updater.updateClassComponent();
      }
      this.isBatchingUpdate = false; // 更新完毕后,将设置为非批处理。
    }
}

下图为整个流程图
在这里插入图片描述

总结

  1. 在React中,所有更新逻辑默认都是异步的,其原理就是将更新的逻辑统一放到组件的updater中,再将updater放入更新队列中,然后统一处理。简称批处理,这样可以大大减少对dom的重复渲染。
  2. 如何开启关闭批处理的关键,是更新队列 updateQueue.isBatchingUpdate 是否为true。由于我们每次批处理后都会将其改为false,于是在我们使用Promise或者setTimeout等其他异步函数时,更新逻辑是同步的。

十分感谢 张仁阳 老师的教导

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值