美文网首页
packages/react/ReactElement.js 解

packages/react/ReactElement.js 解

作者: Ahungrynoob | 来源:发表于2019-08-03 13:45 被阅读0次

    APIs

    createElement

    React.createElement(
      type,
      [props],
      [...children]
    )
    

    创建并返回一个指定类型的 React element。 type参数可以是标签字符串 (比如 'div' 或者 'span'), 一个 React component 组件(a class or a function),或者是 React fragment 类型.

    cloneElement

    React.cloneElement(
      element,
      [props],
      [...children]
    )
    

    克隆并返回一个新的react-element。返回的新的reactElement会有原始element的属性,新赋予的属性将会和原始属性进行浅合并。新的子组件将会替换原来的子组件。key和ref属性将会被保留。

    React.cloneElement() 近似等于:

    <element.type {...element.props} {...props}>{children}</element.type>
    

    但是,这也保留了组件的 ref。这意味着当通过 ref 获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref 将添加到克隆后的新元素中。

    createFactory

    React.createFactory(type)
    

    返回用于生成指定类型 React 元素的函数。与 React.createElement() 相似的是,类型参数既可以是标签名字符串(像是 'div''span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。

    此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。

    isValidElement()

    React.isValidElement(object)
    

    验证对象是否为 React 元素,返回值为 true 或 false。


    源码解析:

    some utils:

    import invariant from 'shared/invariant';
    import warningWithoutStack from 'shared/warningWithoutStack';
    import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
    
    import ReactCurrentOwner from './ReactCurrentOwner';
    
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    
    const RESERVED_PROPS = {
      key: true,
      ref: true,
      __self: true,
      __source: true,
    };
    
    let specialPropKeyWarningShown, specialPropRefWarningShown;
    
    function hasValidRef(config) {
      if (__DEV__) {
        if (hasOwnProperty.call(config, 'ref')) {
          const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
          if (getter && getter.isReactWarning) {
            return false;
          }
        }
      }
      return config.ref !== undefined;
    }
    
    function hasValidKey(config) {
      if (__DEV__) {
        if (hasOwnProperty.call(config, 'key')) {
          const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
          if (getter && getter.isReactWarning) {
            return false;
          }
        }
      }
      return config.key !== undefined;
    }
    
    function defineKeyPropWarningGetter(props, displayName) {
      const warnAboutAccessingKey = function() {
        if (!specialPropKeyWarningShown) {
          specialPropKeyWarningShown = true;
          warningWithoutStack(
            false,
            '%s: `key` is not a prop. Trying to access it will result ' +
              'in `undefined` being returned. If you need to access the same ' +
              'value within the child component, you should pass it as a different ' +
              'prop. (https://fb.me/react-special-props)',
            displayName,
          );
        }
      };
      warnAboutAccessingKey.isReactWarning = true;
      Object.defineProperty(props, 'key', {
        get: warnAboutAccessingKey,
        configurable: true,
      });
    }
    
    function defineRefPropWarningGetter(props, displayName) {
      const warnAboutAccessingRef = function() {
        if (!specialPropRefWarningShown) {
          specialPropRefWarningShown = true;
          warningWithoutStack(
            false,
            '%s: `ref` is not a prop. Trying to access it will result ' +
              'in `undefined` being returned. If you need to access the same ' +
              'value within the child component, you should pass it as a different ' +
              'prop. (https://fb.me/react-special-props)',
            displayName,
          );
        }
      };
      warnAboutAccessingRef.isReactWarning = true;
      Object.defineProperty(props, 'ref', {
        get: warnAboutAccessingRef,
        configurable: true,
      });
    }
    
    • RESERVED_PROPS保留属性:在构造ReactElement时,如果config参数中含有保留属性,这些属性会被单独提取出来,作为参数另外传入ReactElement()
    • hasValidRefhasValidKey: 判断config参数中是否有保留属性的两个工具方法。
    • defineKeyPropWarningGetterdefineRefPropWarningGetter对于ref和key这两个特殊的属性,react不允许开发者去props上直接获取,开发环境下,会给出警告日志。

    核心API

    /**
     * Factory method to create a new React element. This no longer adheres to
     * the class pattern, so do not use new to call it. Also, no instanceof check
     * will work. Instead test $$typeof field against Symbol.for('react.element') to check
     * if something is a React Element.
     *
     * @param {*} type
     * @param {*} props
     * @param {*} key
     * @param {string|object} ref
     * @param {*} owner
     * @param {*} self A *temporary* helper to detect places where `this` is
     * different from the `owner` when React.createElement is called, so that we
     * can warn. We want to get rid of owner and replace string `ref`s with arrow
     * functions, and as long as `this` and owner are the same, there will be no
     * change in behavior.
     * @param {*} source An annotation object (added by a transpiler or otherwise)
     * indicating filename, line number, and/or other information.
     * @internal
     */
    const ReactElement = function(type, key, ref, self, source, owner, props) {
      const element = {
        // This tag allows 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,
      };
    
      if (__DEV__) {
        // The validation flag is currently mutative. We put it on
        // an external backing store so that we can freeze the whole object.
        // This can be replaced with a WeakMap once they are implemented in
        // commonly used development environments.
        element._store = {};
    
        // To make comparing ReactElements easier for testing purposes, we make
        // the validation flag non-enumerable (where possible, which should
        // include every environment we run tests in), so the test framework
        // ignores it.
        Object.defineProperty(element._store, 'validated', {
          configurable: false,
          enumerable: false,
          writable: true,
          value: false,
        });
        // self and source are DEV only properties.
        Object.defineProperty(element, '_self', {
          configurable: false,
          enumerable: false,
          writable: false,
          value: self,
        });
        // Two elements created in two different places should be considered
        // equal for testing purposes and therefore we hide it from enumeration.
        Object.defineProperty(element, '_source', {
          configurable: false,
          enumerable: false,
          writable: false,
          value: source,
        });
        if (Object.freeze) {
          Object.freeze(element.props);
          Object.freeze(element);
        }
      }
    
      return element;
    };
    

    ReactElement函数定义了ReactElement对象,并且会返回ReactElement对象,这个对象中会有ref、key、props等属性。另外在开发环境中,会有额外的_self_source用来帮助开发者判断debug this指针和出错时定位代码。
    额外定义element._store.validated属性为不可枚举的原因是为了测试的时候方便书写测试用例,同时可以冻结element对象。

    createElement

    /**
     * Create and return a new ReactElement of the given type.
     * See https://reactjs.org/docs/react-api.html#createelement
     */
    export function createElement(type, config, children) {
      let propName;
    
      // Reserved names are extracted
      const props = {};
    
      let key = null;
      let ref = null;
      let self = null;
      let source = null;
    
      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.
      const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        if (__DEV__) {
          if (Object.freeze) {
            Object.freeze(childArray);
          }
        }
        props.children = childArray;
      }
    
      // Resolve default props
      if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
      if (__DEV__) {
        if (key || ref) {
          const displayName =
            typeof type === 'function'
              ? type.displayName || type.name || 'Unknown'
              : type;
          if (key) {
            defineKeyPropWarningGetter(props, displayName);
          }
          if (ref) {
            defineRefPropWarningGetter(props, displayName);
          }
        }
      }
      return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props,
      );
    }
    

    createElement函数会接受type, config, children三个参数,针对config会比对RESERVED_PROPS做特殊处理,把保留的属性作为额外参数传入ReactElement;另外针对children会去判断children是否是多参数的,如果是多个children,会作为数组赋给props.children

    cloneAndReplaceKey

    export function cloneAndReplaceKey(oldElement, newKey) {
      const newElement = ReactElement(
        oldElement.type,
        newKey,
        oldElement.ref,
        oldElement._self,
        oldElement._source,
        oldElement._owner,
        oldElement.props,
      );
    
      return newElement;
    }
    

    会复制原来的ReactElement只是替换下key而已。

    cloneElement

    /**
     * Clone and return a new ReactElement using element as the starting point.
     * See https://reactjs.org/docs/react-api.html#cloneelement
     */
    export function cloneElement(element, config, children) {
      invariant(
        !(element === null || element === undefined),
        'React.cloneElement(...): The argument must be a React element, but you passed %s.',
        element,
      );
    
      let propName;
    
      // Original props are copied
      const props = Object.assign({}, element.props);
    
      // Reserved names are extracted
      let key = element.key;
      let ref = element.ref;
      // Self is preserved since the owner is preserved.
      const self = element._self;
      // Source is preserved since cloneElement is unlikely to be targeted by a
      // transpiler, and the original source is probably a better indicator of the
      // true owner.
      const source = element._source;
    
      // Owner will be preserved, unless ref is overridden
      let owner = element._owner;
    
      if (config != null) {
        if (hasValidRef(config)) {
          // Silently steal the ref from the parent.
          ref = config.ref;
          owner = ReactCurrentOwner.current;
        }
        if (hasValidKey(config)) {
          key = '' + config.key;
        }
    
        // Remaining properties override existing props
        let defaultProps;
        if (element.type && element.type.defaultProps) {
          defaultProps = element.type.defaultProps;
        }
        for (propName in config) {
          if (
            hasOwnProperty.call(config, propName) &&
            !RESERVED_PROPS.hasOwnProperty(propName)
          ) {
            if (config[propName] === undefined && defaultProps !== undefined) {
              // Resolve default props
              props[propName] = defaultProps[propName];
            } else {
              props[propName] = config[propName];
            }
          }
        }
      }
    
      // Children can be more than one argument, and those are transferred onto
      // the newly allocated props object.
      const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
      }
    
      return ReactElement(element.type, key, ref, self, source, owner, props);
    }
    

    类似于createElement,不同的是对于props会做一次浅合并。

    isValidElement

    /**
     * Verifies the object is a ReactElement.
     * See https://reactjs.org/docs/react-api.html#isvalidelement
     * @param {?object} object
     * @return {boolean} True if `object` is a ReactElement.
     * @final
     */
    export function isValidElement(object) {
      return (
        typeof object === 'object' &&
        object !== null &&
        object.$$typeof === REACT_ELEMENT_TYPE
      );
    }
    

    判断是否是有效的ReactElement类型。

    相关文章

      网友评论

          本文标题:packages/react/ReactElement.js 解

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