React(虚拟)DOM术语[一源看世界][之React]

作者: 蛋先生DX | 来源:发表于2016-07-05 13:31 被阅读1082次

    阅读源码前,提前了解下作者对一些术语的定义,有助于更好地理解源码。以下为根据官方文档关于React (Virtual) DOM Terminology描述进行翻译并配上源码,以更深入地了解这些概念

    在React的名词术语中,有五种主要的类型需要加以区别
    . ReactElement / ReactElement Factory
    . ReactNode
    . ReactComponent / ReactComponent Class


    React Elements

    React中主要的类型是ReactElement,它有四种属性: typepropskeyref。从以下源码可以看出它就是一个普通的对象,通过$$typeof属性来识别是否是React Element,值为Symbol.for('react.element’)或60103

    var ReactElement = function(type, key, ref, self, source, owner, props) {
      var element = {
        // This tag allow us to uniquely identify this as a React Element
        $$typeof: REACT_ELEMENT_TYPE,
    
        // Built-in properties that belong on the element
        type: type,
        key: key,
        ref: ref,
        props: props,
    
        // Record the component responsible for creating this element.
        _owner: owner,
      };
      ...
      return element;
    };
    

    你可以通过React.createElement来创建这些对象。

    var root = React.createElement('div');
    

    我们来看下React.createElement的源码,看看它做了啥?

    ReactElement.createElement = function(type, config, children) {
      ...
    
      if (config != null) {
        ...
    
        if (hasValidRef(config)) {
          ref = config.ref;
        }
        if (hasValidKey(config)) {
          key = '' + config.key;
        }
    
        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source;
        // Remaining properties are added to a new props object
        for (propName in config) {
          if (hasOwnProperty.call(config, propName) &&
              !RESERVED_PROPS.hasOwnProperty(propName)) {
            props[propName] = config[propName];
          }
        }
      }
    
      // Children can be more than one argument, and those are transferred onto
      // the newly allocated props object.
      var childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        var childArray = Array(childrenLength);
        for (var i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
      }
    
      // Resolve default props
      if (type && type.defaultProps) {
        var defaultProps = type.defaultProps;
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
      ...
      return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props
      );
    };
    

    从源码中我们可以看到:

    1. config中可以包含保留属性refkey__self__source
    2. React Element的props属性包含了除保留属性外的所有config的属性值,children和type(通过React.createClass创建的类或称构造函数)的defaultProps的所有属性值
    3. ReactElement.createElement可以传不止3个参数,第3个以及后面的参数都作为child传进来,如:
    var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
    

    你可以创建多个ReactElement,它们之间的父子包含关系形成了一个树结构,将最top的ReactElement和它将所在的容器(一个普通的DOM element,可以是HTMLElement 或者 SVGElement)传给ReactDOM.render方法,这样就可以在页面上渲染出这个组件及其所包含的子组件

    ReactElement不是DOM element,它是DOM element的一个轻量的,无状态的,不可变的虚拟表示,就是传说中的虚拟DOM

    ReactDOM.render(root, document.getElementById('example'));
    

    可以通过传递一个属性对象给第2个参数为组件的DOM element增加属性,可以通过传递子React element对象给第3个参数为组件DOM element增加子的DOM element

    var child = React.createElement('li', null, 'Text Content');
    var root = React.createElement('ul', { className: 'my-list' }, child);
    ReactDOM.render(root, document.getElementById('example'));
    

    如果你用JSX,那么以下代码跟上面的代码是等价,JSX编译器会将以下JSX语法解析成上面的JS代码

    var root = <ul className="my-list">
                 <li>Text Content</li>
               </ul>;
    ReactDOM.render(root, document.getElementById('example'));
    

    看累了吗?小休息一下再接着看呗!!!


    Factories

    ReactElement-factory是一个可以生成特定type的ReactElement对象的工厂函数。React内置了有用的创建工厂函数的方法

    ReactElement.createFactory = function(type) {
      var factory = ReactElement.createElement.bind(null, type);
      ...
      return factory;
    };
    

    这里小普及一下Function.prototype.bind函数,它是用来创建一个新的函数,新的函数的this关键字指向第1个参数(如果为null则保持不变),第2, ...个参数的值作为参数值按顺序传给原来的函数。举个栗子吧

    var foo = function(p1, p2) { console.log(this, p1, p2); }
    
    var barContext = {};
    var bar = foo.bind(barContext, 'hello'); // bar函数的this关键字指向了barContext,传给foo函数的p1参数的值为'hello'
    
    bar('daniel') // 相当于 foo('hello', 'daniel'),但两个函数this关键字指向不同
    

    有了Factory工厂函数你就不用每次都敲打React.createElement('div')了。- Well done,程序员就应该能懒则懒

    var div = React.createFactory('div');
    var root = div({ className: 'my-div' });
    ReactDOM.render(root, document.getElementById('example'));
    

    React本身也内置了很多针对一般的HTML标签的Factory方法

    var root = React.DOM.ul({ className: 'my-list' },
                 React.DOM.li(null, 'Text Content')
               );
    

    React Nodes

    一个React Node可以是以下:
    . React Element
    . string (亦称ReactText)
    . number (亦称ReactText)
    . 一组React Node (亦称ReactFragment)
    它们已作为其它React Element的属性的形式(props.children)来表示子元素,从而创建了一个树结构的组件


    React Components

    一个ReactComponent Class就是一个javascript类(或称为构造函数),它同时也称为某个ReactElement的类型(type)

    var MyComponent = React.createClass({
      render: function() {
        ...
      }
    });
    

    当这个构造函数被调用时,返回的是一个至少有render方法的对象,这个对象被称为ReactComponent(即ReactComponent Class的实例)

    var component = new MyComponent(props); // 千万不要这样去创建ReactComponent
    

    除了测试,不要自己去调用构造函数,交给React来处理这事就好。
    将ReactComponent Class传给createElement方法,返回一个ReactElement

    var element = React.createElement(MyComponent);
    

    或者使用JSX:

    var element = <MyComponent />;
    

    当这个element传给ReactDom.render方法时,React会帮你调用构造函数并返回ReactComponent

    var component = ReactDOM.render(element, document.getElementById('example'));
    

    如果你传入相同类型的ReactElement和相同的DOM容器继续调用ReactDOM.render,它会一直返回同一个实例。这个实例是有状态的

    var componentA = ReactDOM.render(<MyComponent />, document.getElementById('example'));
    var componentB = ReactDOM.render(<MyComponent />, document.getElementById('example'));
    componentA === componentB; // true
    

    从源码中src/renderers/dom/client/ReactMount.js来验证这个逻辑。

     _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
        ...
    
        var prevComponent = getTopLevelWrapperInContainer(container);
    
        if (prevComponent) {
          var prevWrappedElement = prevComponent._currentElement;
          var prevElement = prevWrappedElement.props;
          if (shouldUpdateReactComponent(prevElement, nextElement)) {
            var publicInst = prevComponent._renderedComponent.getPublicInstance();
            var updatedCallback = callback && function() {
              callback.call(publicInst);
            };
            ReactMount._updateRootComponent(
              prevComponent,
              nextWrappedElement,
              container,
              updatedCallback
            );
            return publicInst;
          } else {
            ...
          }
        }
        ...
      },
    

    可以看出是通过shouldUpdateReactComponent方法来判断是否只更新并返回原来的实例publicInst,而shouldUpdateReactComponent针对ReactElement会判断typekey属性是否相等

    这就是为什么不让你自己创建ReactComponent的原因,因为React作了优化的处理,可不是随随便便就New New New哦。

    ReactComponentrender方法(就是你在声明一个组件时定义的render方法)会返回另外一个ReactElement,所以这些Component可以进行组合。最终会解析成DOM element实例并插入到document中


    正式的类型定义

    入口

    ReactDOM.render = (ReactElement, HTMLElement | SVGElement) => ReactComponent;
    

    Nodes 和 Elements

    type ReactNode = ReactElement | ReactFragment | ReactText;
    
    type ReactElement = ReactComponentElement | ReactDOMElement;
    
    type ReactDOMElement = {
      type : string,
      props : {
        children : ReactNodeList,
        className : string,
        etc.
      },
      key : string | boolean | number | null,
      ref : string | null
    };
    
    type ReactComponentElement<TProps> = {
      type : ReactClass<TProps> | ReactFunctionalComponent<TProps>,
      props : TProps,
      key : string | boolean | number | null,
      ref : string | null
    };
    
    type ReactFragment = Array<ReactNode | ReactEmpty>;
    
    type ReactNodeList = ReactNode | ReactEmpty;
    
    type ReactText = string | number;
    
    type ReactEmpty = null | undefined | boolean;
    

    Classes and Components

    type ReactClass<TProps> = (TProps) => ReactComponent<TProps>;
    
    type ReactComponent<TProps> = {
      props : TProps,
      render : () => ReactElement
    };
    
    type ReactFunctionalComponent<TProps> = (TProps) => ReactElement;
    

    最后,期待吐槽,期待指教!!!

    --EOF--

    相关文章

      网友评论

        本文标题:React(虚拟)DOM术语[一源看世界][之React]

        本文链接:https://www.haomeiwen.com/subject/mqlhjttx.html