资讯专栏INFORMATION COLUMN

构建自己的React:(4)Components and State

cncoder / 996人阅读

摘要:我们需要一个带有入参和方法的构造函数,方法可以接收作为入参来更新组件状态我们在创建组件时都会继承上面这个类。我们需要一个方法能根据传入的元素来创建组件的实例称之为公共实例,其实就是根据这个构造函数出来的一个对象。

翻译自: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一样,直接像这样就可以了。而且我们的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://www.ucloud.cn/yun/99870.html

相关文章

  • 构建自己React:(4Components and State

    摘要:我们需要一个带有入参和方法的构造函数,方法可以接收作为入参来更新组件状态我们在创建组件时都会继承上面这个类。我们需要一个方法能根据传入的元素来创建组件的实例称之为公共实例,其实就是根据这个构造函数出来的一个对象。 翻译自:https://engineering.hexacta.c... 上一节的代码有一些问题: 每次更新都会带来整颗虚拟DOM树的一致性校验; 状态是全局的(没有私有状...

    sixgo 评论0 收藏0
  • 程序员练级攻略(2018):前端性能优化和框架

    摘要:,谷歌给的一份性能指南和最佳实践。目前而言,前端社区有三大框架和。随后重点讲述了和两大前端框架,给出了大量的文章教程和相关资源列表。我认为,使用函数式编程方式,更加符合后端程序员的思路,而是更符合前端工程师习惯的框架。 showImg(https://segmentfault.com/img/bVbjQAM?w=1142&h=640); 这个是我订阅 陈皓老师在极客上的专栏《左耳听风》...

    VEIGHTZ 评论0 收藏0
  • 程序员练级攻略(2018):前端性能优化和框架

    摘要:,谷歌给的一份性能指南和最佳实践。目前而言,前端社区有三大框架和。随后重点讲述了和两大前端框架,给出了大量的文章教程和相关资源列表。我认为,使用函数式编程方式,更加符合后端程序员的思路,而是更符合前端工程师习惯的框架。 showImg(https://segmentfault.com/img/bVbjQAM?w=1142&h=640); 这个是我订阅 陈皓老师在极客上的专栏《左耳听风》...

    CoffeX 评论0 收藏0
  • 浅谈MVC,MVP,MVVM渐进变化及React与Vue比较

    摘要:将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。此示例使用类似的语法,称为。执行更快,因为它在编译为代码后进行了优化。基于的模板使得将已有的应用逐步迁移到更为容易。 前言 因为没有明确的界定,这里不讨论正确与否,只表达个人对前端MV*架构模式理解看法,再比较React和Vue两种框架不同.写完之后我知道这文章好水,特别是框架对比部分都是别人说烂的,而我也是打算把...

    DrizzleX 评论0 收藏0

发表评论

0条评论

cncoder

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<