手写react3-组件更新

126 阅读2分钟

react中将更新抽象成了一个类Updater

如果某个场合下,用户触发点击时回调执行了setState,例如:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0, age: 10 };
  }
  handleClick = (event) => {
    this.setState({ number: this.state.number + 1 });
  }

下面的setState就会触发

class Component {
    static isReactComponent = true //当子类继承父类的时候 ,父类的静态属性也是可以继承的
    constructor(props) {
        this.props = props;
        this.state = {};
        this.updater = new Updater(this);
    }
    setState(partialState) {
        this.updater.addState(partialState);
      	this.emitUpdate();
    }
		emitUpdate() {
    		this.updateComponent();
    }
		updateComponent() {
    		const { classInstance, pendingStates } = this;
      	if (pendingStates.length > 0) {
        		shouldUpdate(classInstance, this.getState());
        }
    }
		getState() {
    		const { classInstance, pendingStates } = this;
    		let { state } = classInstance;
      	pendingStates.forEach(partialState => {
        		state = { ...state, ...partialState }
        });
      	pendingStates.length = 0;
      	return state;
    }

    //根据新的属性状态计算新的要渲染的虚拟DOM
    forceUpdate() {
        let oldRenderVdom = this.oldRenderVdom;//上一次类组件render方法计算得到的虚拟DOM
        //let oldDOM = oldRenderVdom.dom;
        let oldDOM = findDOM(oldRenderVdom);//获取 oldRenderVdom对应的真实DOM
        //然后基于新的属性和状态,计算新的虚拟DOM
        let newRenderVdom = this.render();
        compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
        this.oldRenderVdom = newRenderVdom;
    }
}
function shouldUpdate(classInstance, nextState) {
    classInstance.state = nextState;//先把新状态赋值给实例的state
    classInstance.forceUpdate();//强制更新
}
class Updater {
    constructor(classInstance) {
        this.classInstance = classInstance;
        this.pendingStates = [];//等待生效的数组
        //this.callbacks = [];
    }
    addState(partialState) {
        this.pendingStates.push(partialState);
        this.emitUpdate();// 触发更新
    }
    emitUpdate() {
        this.updateComponent();
    }
    updateComponent() {
        const { classInstance, pendingStates } = this;
        if (pendingStates.length > 0) {
            shouldUpdate(classInstance, this.getState());
        }
    }
    getState() {
        const { classInstance, pendingStates } = this;
        let { state } = classInstance;//{number:0} 老状态
        pendingStates.forEach((partialState) => {//和每个分状态
            state = { ...state, ...partialState }
        });
        pendingStates.length = 0;//清空等待生效的状态 的数组
        return state;
    }
}

react-dom -> findDOM

export function findDOM(vdom) {
    if (!vdom) return null;
    if (vdom.dom) {//vdom={type:'h1'}
        return vdom.dom;
    } else {
        //类组件 还是函数组件,他们虚拟DOM身上没有dom属性,但是oldRenderVdom
        return findDOM(vdom.oldRenderVdom);
    }
}

上述代码中,需要注意,在forceUpdate中,调用了findDOM(oldRenderVdom)获取虚拟DOM对应的真实DOM,oldRenderVdom有以下方式生成:

1、ReactDOM.render -> mount(vdom, container) -> createDOM(vdom)

2、调用类组件的render方法

    let renderVdom = classInstance.render();
    classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);

3、直接调用函数组件,返回值就是oldRenderVdom

    let renderVdom = type(props);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);

可以看到得到vdom或renderVdom之后,都通过调用createDOM将其渲染为真实DOM,而在createDOM里面,我们在最后将创建出来的dom挂到了vdom或renderVdom上:

export function createDOM(vdom) {
    if (!vdom) return null;
    let { type, props } = vdom;
    let dom;//真实DOM
    if (type === REACT_TEXT) {//如果这个元素是一个文本的话
        dom = document.createTextNode(props.content);
    } else if (typeof type === 'function') {//如果类型是一个函数的话
        if (type.isReactComponent) {//说明它是一个类组件
            return mountClassComponent(vdom);
        } else {
            return mountFunctionComponent(vdom);
        }
    } else {
        dom = document.createElement(type);// div span p
    }
    //处理属性
    if (props) {
        updateProps(dom, {}, props);
        if (props.children) {
            let children = props.children;
            if (typeof children === 'object' && children.type) {//说明这是一个React元素
                mount(children, dom);
            } else if (Array.isArray(children)) {
                reconcileChildren(props.children, dom);
            }
        }
    }
    vdom.dom = dom;//让虚拟DOM的dom属性指向这个虚拟DOM对应的真实DOM
    return dom;
}

不过需要注意,如果进入createDOM后走了mountClassComponent或者mountFunctionComponent分支,是不会执行vdom.dom = dom的,也就是说,类组件和函数组件对应的虚拟DOM对象上没有dom属性来表示它所对应的真实DOM

但是在mountClassComponent和mountFunctionComponent中我们也可以看到,vdom上挂了oldRenderVdom这个属性,通过它我们就可以得到类组件或函数组件渲染出的DOM对象了

function mountClassComponent(vdom) {
    let { type: ClassComponent, props } = vdom;
    let classInstance = new ClassComponent(props);
    let renderVdom = classInstance.render();
    classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}
function mountFunctionComponent(vdom) {
    let { type, props } = vdom;
    let renderVdom = type(props);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}