ReactElement源碼解析

前言

ReactElement並不像以前所談的PureComponent和Component那樣被頻繁的顯示使用,但我估計他應該是在react暴露出的api中被調用最爲頻繁的,關於此看完後面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五個方法,總共400來行代碼,比較容易。javascript

文章中若有不當之處,歡迎交流指點。react版本 16.8.2。在源碼添加的註釋在github react-source-learn

jsx與ReactElement

在使用react時咱們常常在render方法返回(函數組件的話多是直接返回)相似下面的代碼。html

<Wrap>
    <h1>測試</h1>
    <List />
    <Footer />
</Wrap>

這就是傳說中的jsx語法,js並無這種東西,這種語法最終都會被轉換成標準的js。請看下圖:java

圖片描述

發現這些jsx被轉化成了js,每一個組件或者html標籤轉化後都是調用React.createElement(type, config, children)。這裏的React.createElement其實就是ReactElement.createElement。由此能夠推測,ReactElement暴露的方法是調用最頻繁的。react

createElement解析

createElement的主要調用以下:git

createElement -> ReactElement

固然在dev下還有些其餘的調用。github

createElement源碼以下api

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */

 // jsx轉換後調用的方法
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
    // 將config中的數據放到props中,  key,ref,__self,__source除外
    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.
  // children生成
  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
  // 複製默認props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 這裏否則從props中讀key, 和ref, 可是裏邊事實上就是沒有的
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        // displayName: 構造函數名, 或標籤名 a , h1
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 就一個普通對象
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

createElement主要作了以下事情:數組

  1. 將特殊屬性從config取出, 如key,ref,__self,__source
  2. 將非特殊屬性掛到props上,好比上邊那個圖中的className
  3. 將第三個及以後的參數掛到props.children上,多個是生成數組,單個是直接掛
  4. 默認值defaultProps的處理
  5. 將處理好的數據做爲參數調用ReactElement並返回

ReactElement源碼以下less

// 這個函數作的事很是簡單, 就是將傳進來的參放到一個對象裏邊返回
 // 其中source, self在生產模式沒有返回
 // owner 變成了_owner
 // 開發模式下, 返回了將source, self也掛在了返回的對象上, 變成了_source, _self
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, // 一個Symobol或者16進制數,
                                  //用於表示ReactElement類型

    // 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,
  };

  // 這裏邊放了self, source
  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,
    });

    // Object.freeze() 方法能夠凍結一個對象。一個被凍結的對象不再能被修改;
    // 凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、
    // 可配置性、可寫性,以及不能修改已有屬性的值。
    // 此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。


    // Object.seal()方法封閉一個對象,阻止添加新屬性並將全部現有屬性標記爲不可配置。當前屬性的值只要可寫就能夠改變。

    // Object.preventExtensions()方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。


    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

他幾乎是沒作什麼事情的,就是將傳入的參數放到一個對象返回,加了一個$$typeof標識ReactElement。其中使用了一個Object.freeze方法,這個方法不太經常使用,意思是凍結一個對象,使其不能被修改,相關的還有Object.seal,Object.preventExtensions,能夠找些文檔瞭解下。dom

小結下ReactElement.createElement

ReactElement.createElement最終返回的是一個普通的對象,對參數進行了校驗,提取等操做。上面爲解析dev下的代碼,去看一下會發現也是比較有趣的。

createFactory,cloneAndReplaceKey,cloneElement和isValidElement

createFactory

export function createFactory(type) {
  const factory = createElement.bind(null, type);
  // Expose the type on the factory and the prototype so that it can be
  // easily accessed on elements. E.g. `<Foo />.type === Foo`.
  // This should not be named `constructor` since this may not be the function
  // that created the element, and it may not even be a constructor.
  // Legacy hook: remove it
  factory.type = type;
  return factory;

  // 這樣
  // return function factory(...args) {
  //   return createElement(type, ...args);
  // }
}

這個方法很簡單,是對createElement的一個柯里化的操做。

cloneAndReplaceKey

// 克隆reactElement並將key改成新key
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屬性替換成新的

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,使用了建立時掛上去的$$typeof

cloneElement

// 和createElement基本相同
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很是類似,不一樣之處在於他是返回第一個參數ReactElement的一個副本。他的key,ref等屬性和提供的須要被克隆的ReactElement的相同,props也是原來的props,可是能夠傳入config修改。

/packages/react.js小結

至此,/packages/react.js總的最最重要的東西已經分析完了,關於hooks等其餘內容就像不分析了。這裏邊的代碼其實並無作什神奇的事情,ReactElement只是建立和操做普通對象,Component和PureComponent只是定義了兩個簡單的構造函數,定義了幾個方法,其中比較重要的應該是updater,可是到目前爲止尚未看到他的身影。這些東西都不涉及dom操做,是平臺無關的。

這裏代碼都比較好理解,後面就將進入深水區了,要開始研究ReactDOM裏邊的render了。加油!

相關文章
相關標籤/搜索