构建本身的React:(4)Components and State

翻译自:https://engineering.hexacta.c…

上一节的代码有一些题目:

  • 每次更新都邑带来整颗假造DOM树的一致性校验;
  • 状况是全局的(没有私有状况);
  • 有变化发生后必需手动挪用render要领以便将变化反应到页面上。

组件能够帮我们处置惩罚上面的题目,同时还能带来一些新特征:

  • 许可自定义JSX的标署名
  • 生命周期钩子(这一节暂不引见这部份)

起首我们要定义一个Component的基本类,在建立别的组件时都要继续该类。我们须要一个带有props入参和setState要领的组织函数,setState要领能够吸收partialState作为入参来更新组件状况:

class Component{
    constructor(props){
        this.props = props;
        this.state = this.state || {}
    }
    
    setState(partialState){
        this.state = Object.assign({}, this.state, partialState);
    }
}

我们在建立组件时都邑继续上面这个类。组件的运用要领和原生的标签如div或许span一样,直接像如许<MyComponent />就能够了。而且我们的createElement也不须要做修正,元素的type属性能够直接取值为组件类,剩下的props属性也不须要迥殊的处置惩罚。我们须要一个要领能依据传入的元夙来建立组件的实例(称之为大众实例,实在就是依据这个组织函数new出来的一个对象)。

function createPublicInstance(element, internalInstance){
    const {type, props} = element;
    const publicInstance = new type(props); // 这处所的type对应组件的组织函数
    publicInstance.__internalInstance = internalInstance;
    return publicInstance;
}

组件的内部实例含有组件对应的dom元素(内部实例就是前几节我们说的实例,经由过程挪用instantiate要领天生的)。大众实例与内部实例的援用关联会被保留着,经由过程这个援用关联能够找到大众实例对应的内部实例及假造DOM,当大众实例状况发生变化时,我们就能够只更新发生变化的内部实例及其对应的那部份假造DOM:

class Component{
    constructor(props){
        this.props = props;
        this.state = this.state || {}
    }
    
    setState(partialState){
        this.state = Object.assign({}, this.state, partialState);
        updateInstance(this.__internalInstance);
    }
}

function updateInstance(internalInstance){
    const parentDom = internalInstance.dom.parentNode;
    const element = internalInstance.element;
    reconcile(parentDom, internalInstance, element);
}

instantiate要领须要做一些革新。对组件来说,我们须要先建立大众实例(先new一个组建),然后挪用组件的render要领来猎取组件内部的元素,末了把猎取到的元素传递给instantiate要领。

function instantiate(element){
    const { type, props } = element;
    const isDomElement = typeof type === 'string';
    
    if(isDomElement){ // 如果是原生的dom元素的话,直接建立实例
        const isTextElement = type === TEXT_ELEMENT;
        const dom = isTextElement
            ? document.createTextNode('')
            : document.createElement(type);
         
         updateDomProperties(dom, [], props);
         
         const childElements = props.children || [];
         const childInstances = childElements.map(instantiate);
         const childDoms = childInstances.map(childInstance => childInstance.dom);
         childDoms.forEach(childDom => dom.appendChild(childDom));
         
         const instance = { dom, element, childInstances };
         return instance;
    } else {// 不然先建立大众实例,然后再挪用instantiate要领建立内部实例
        const instance = {};
        // 这处所的element是一个type属性为一个组织函数的对象
        const publicInstance = createPublicInstance(element, instance);
        const childElement = publicInstance.render();
        const childInstance = instantiate(childElement);
        const dom = childInstance.dom;
        
        Object.assign(instance, { dom, element, childInstance, publicInstance});
        return instance;
    }
}

组件对应的内部实例和原生dom元素对应的实例有些不一样。组件内部实例只会具有一个子元素,即render要领返回的内容,而原生dom元素则能够含有多个子元素。所以关于组件内部实例来说,它们会有一个childInstance属性而不是一个childInstances数组。另外,由于在举行一致性校验时须要挪用组件的render要领,所以组件内部实例会保留对大众实例的援用(反过来大众实例也保留着对内部实例的援用)。

接下来我们来处置惩罚下组件实例的一致性校验。由于组件的内部实例只含有一个子元素(一切元素有一个一致的父类),只须要更新大众实例的props属性,实行render要领猎取子元素,然后再举行一致性校验就能够了。

function reconcile(parentDom, instance, element){
    if(instance == null){
        const newInstance = instantiate(element);
        parentDom.appendChild(newInstance.dom);
        return newInstance;
    } else if( element == null){
        parentDom.removeChild(instance.dom);
        return null;
    } else if(instance.element.type !== element.type){
        const newInstance = instantiate(element);
        parentDom.replaceChild(newInstance.dom, instance.dom);
        return newInstance;
    } else if(typeof element.type === 'string'){
        updateDomProperties(instance.dom, instance.element, props, element.props);
        instance.childInstances = reconcileChildren(instance, element);
        instance.element = element;
        return instance;
    } else {
        instance.publicInstance.props = element.props;// 更新大众实例的props
        const childElement = instance.publicInstance.render(); // 猎取最新的子元素
        const oldChildInstance = instance.childInstance;
        const childInstance = reconcile(parentDom, oldChildInstance, childElement);
        
        instance.dom = childInstance.dom;
        instance.childInstance = childInstance;
        instance.element = element;
        return instance;
    }
}

如今,我们的Didact.js已能够支撑组件了。这里能够在线编辑代码并能看到结果。

运用组件后,我们能够建立自定义的JSX标签,并具有了组件内部状况,而且组件有变化时只会变动本身的那部份dom内容。

相关内容到此结束。

    原文作者:许三多
    原文地址: https://segmentfault.com/a/1190000017321746
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞