zoukankan      html  css  js  c++  java
  • 转-react源码分析-虚拟组件

    转载于:https://www.yuque.com/ant-h5/sourcecode/zfu699
    作者: ant-h5, 蚂蚁金服前端团队
    转载方式: 手打
    转载者: lemon-Xu

    虚拟组件

    组件初始化

    组件创建

    组件创建流程

    组件创建流程并不复杂, 消费者需要调用createClass,把配置的回调函数(包含初始化、属性、状态、生命周期钩子、渲染、自定义函数等、以下统称spec)传入即可

    1. 消费者配置回调声明

    var ExampleApplication = React.createClass({
        getInitialState(){
            return { }
        }
        
        componentWillMount(){
        
        },
        
        componentDidMount(){
        
        },
        
        render: function(){
            return <div>hello world</div>
        }
    })
    

    在声明阶段,用户可以声明react的生命周期的钩子函数、状态和渲染函数等。
    spec的渲染函数(render)不能为空,react组件需要根据render的返回值来渲染最终的页面元素。
    应该有一些读者很熟悉这个API的使用方法,不再累述。

    2. 编译阶段

    初涉react的会有这样的疑问,jsx并不是js规范,浏览器的js解析引擎不能识别

    hello world
    这样的抽象语法树(AST),在词法分析阶段就抛出异常。

    执行消费者配置的代码之前,react代码需要经过一次预编译阶段,把jsx编译成js解析引擎能识别的js。

    在上一章初<探React源码>中, 讲解编译后的结果。这里介绍下各个版本编译的区别:

    • 在react-15里,使用babel-preset-react来编译jsx,这个preset又包含了4个插件,其中transform-react-jsx负责编译jsx,调用了React.createElement函数生成虚拟组件。
    • 在react-0.3里,编译结果稍稍有些不同,官方给出的示例文件,使用JSXTransformer.js编译jsx,对于native组件和composite组件编译的方式也不一致,结果如下

    1. native组件:编译成React.DOM.xxx(xxx如div),函数运行返回一个ReactNativeComponent实例。
    2. composite组件:编译成createClass返回的函数调用,函数返回一个ReactCompositeComponent实例。

    3. 组件创建

    createClass: function(spec){
        var Constructor = function(initialProps, children){
            this.construct(initialProps, children);
        };
        Constructor.prototype = new ReactCompositeComponentBase();
        Constructor.prototype.constructor = Constructor;
        mixSpecIntoComponent(Constructor, spec);
        invariant(
            Constructor.prototype.render,
            'createClass(...): class specification must implement a 'render' method'
        );
        
        var ConvenienceConstructor = function(props, children){
            return new Constructor(props, children);
        }
        ConvenienceConstructor.componentConstructor = Constructor;
        ConvenienceConstructor.originalSpec = spec;
        return ConvenienceConstructor;
        
    }
    

    代码短短不到20行,却可圈可点。最终返回ConvenienceConstructor,这个函数调用了Constructor,传入了props,children两个参数,也就是编译时生成ExampleApplication传入的参数。然后把spec和Constructor分别挂载到ConvenienceConstructor函数上。

    Constuctor是每个React组件的原型函数,原型指向ReactCompositeComponentBase,又把构造器指向Constructor自己。然后把消费者声明配置spec合并到Constructor.prototype中。判断合并后的结果有没有render,如果没有render,抛出一个异常’createClass(...): Class specification must implement a 'render' method‘

    ReactCompositeComponentBase是React复合组件的原型函数,稍后会详细介绍它。

    有的读者已经发现了,为什么不使用以下写法?

    new ReactCompositeComponentBase(props, children);
    

    而中间套了两层函数对象呢?

    • 我们先来看看第一个嵌套,以下两种写法有什么不同。
    // 写法1
    const createClass = function(spec){
        mixSpecIntoComponent(ReactCompositeComponentBase, spec)
        return ReactCompositeComponentBase
    }
    
    const Table1 = new createClass(spec)(props, children);
    const Table2 = new createClass(spec)(props, children);
    
    // 写法2
    const createClass = function(){
        var Constructor = function(initialProps, children) {
            this.construct(initialProps, children);
        };
        
        Constructor.prototype = new ReactComsiteComponentBase();
        Constructor.prototype.constructor = Constructor;
        mixSpecIntoComponent(ReactCompositeComponentBase, spec)
        return Constructor
    }
    
    const Table1 = new createClass(spec)(props, children);
    const Table2 = new createCkass(spec)(props, children);
    

    起到了包装作用?如果你开心,Constructor外面可以再包无限层,当然不是包装。

    写法1的两个组件,constructor都指向ReactCompositeComponentBase,这样做diff时候,区分不出来Table1和Table2是否类型相同。

    而方法2的constructor指向新创建的Constructor函数,所以 每个createClass创建出来的组件都是一个新的Constructor

    写法1还有个致命的缺点:任何创建出来的组件的原型修改,都会影响到ReactCompositeComponentBase。

    react做dom-diff时候,使用constructor来判断组件是否相同

    • 我们继续看第二个嵌套,这个写法又有什么不同
    // 写法1
    const createClass = function(spec){
        var Constructor = function(initialProps, children){
            this.construct(initialProps, children);
        }
        Constructor.prototype = new ReactCompositeComponentBase();
        Constructor.prototype.constructor = Constructor;
        mixSpecIntoComponent(ReactCompositeComponentBase, spec);
        return Constructor;
    }
    
    const Table1 = new createClass(spec)(props, children);
    
    // 写法2
    const createClass = function(){
        var Constructor = function(initialProps, children){
            this.construct(inititalProps, children);
        };
        Constructor.prototype = new ReactCompositeComponentBase();
        Constructor.prototype.constructor = Constructor;
        mixSpeIntoCompoent(Constructor, spec);
        
        var ConvenienceConstructor = function(props, children){
            return new Constructor(props, children);
        }
        ConvenienceConstructor.componentConstructor = Constructor;
        ConvenienceConstructor.originalSpec = spec;
        return ConvenienceConstructor;
        
    }
    
    const Table1 = new createClass(spec)(props, children);
    
    • 很多人(包括我在内)第一次看到得时候,并不理解为什么又包装了一层ConvenienConstructor。的确,写法1在大多情况下并不会产生什么问题,但是,当团队里的人无意修改错点什么,比如:
    Table1.prototype.onClick = null;
    

    这样,所有Table1实例化的组件,onClick全部为修改后的控制。

    <Table1 />
    <Table1 />
    

    我们知道,js是动态解释型语言,函数可以运行时被随意篡改。而静态编译语言在运行时期间,函数不可修改(某些静态语言也可以修改)。所以采用这种方式防御用户对代码的篡改。

    4. 组件实例化

    construct: function(initialProps, children){
        this.props = initialProps || {};
        if(typeof children !== 'undefined'){
            this.porps.children = children;
        }
        // Record the component responsible for creating this component。
        this.props[OWNER] = ReactCurrentOwner.current;
        // All components start unmounted.
        this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
    }
    

    分别挂载prosp和children,注意这里把children也挂载到this.props属性上。在语义上,children也属于属性。children用来在虚拟组件转dom的渲染阶段递归使用。然后赋值了OWNER,最后设置了组件的生命周期为ComponentLifeCycle.UNMOUNTED。

    这里特别说明下

    this.props[OWNER] = ReactCurrentOwner.current;
    

    this.porps[OWNER]指的是当前组件的容器(父)组件实例,比如

    const Children = React.createClass({
        componentDidMount = () => console.log(this.porps["{owner}"]),
        render = () => null
    })
    
    const Parent = React.createClass({
        render: () => <Children />
    })
    

    这里输出的就是Parent组件实例。

    ReactCurrentOwner.current在哪里赋值呢?

    _renderValidatedComponent: function(){
        ReactCurrentOwner.current = this;
        var renderedComponent = this.render();
        ReactCurrentOwner.current = null;
        invariant(
            ReactComponent.isValidComponent(renderedComponent)
        '%s.render(): A valid ReactComponent must be returned.',
        this.constructor.displayName || 'ReactCompositeComponent'
        ),
        return renderedComponent;
    }
    

    _renderValidatedComponent函数会在调用rebderComponent函数调用(我们会在下一章讲解)。可以看出来, 在执行render前后,分别设置了ReactCurrentOwner.current的值,这样就能保证render函数内的子组件能赋上当前组件的实例,也就是this。

  • 相关阅读:
    骑行的乐趣
    亲子运动会
    【转载】程序员接私活经验总结,来自csdn论坛语录
    【原创】Asp.Net MVC 学习笔记之使用Model验证
    【转载】经典js技巧
    【原创】关于Sqlserver的LogFiles超大的问题
    【总结】Winform编程常用小技巧
    【总结】WebBrowser相关资料索引
    【原创】Asp.Net MVC学习笔记之使用AcceptVerbs标签来制定Action的响应行为
    【转载】一个资深SOHO程序员对新人的建议网上接活(转自CSDN)
  • 原文地址:https://www.cnblogs.com/xiaoxu-xmy/p/13707623.html
Copyright © 2011-2022 走看看