React源碼學習筆記---基本Api的源碼

createElement

使用過react開發的都知道,咱們直接寫的react element會被babel轉換爲react.createElement,具體以下圖所示,那麼先來看一下createElement的源碼react

dom元素標籤直接顯示是字符串數組

而自定義組件顯示的是變量 bash

  • createElement傳遞三個參數分別爲type(元素類型)、config(也就是props)、children(子元素)
export function createElement(type, config, children){}
複製代碼
  • 首先是對config的處理
if (config != null) {
    // key和ref單獨處理,所以他們不會出如今props上
    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
    // 遍歷配置,把內建的幾個屬性剔除後賦值到 props 中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
複製代碼
  • 而後是對children的處理

若是子元素只有一個,直接props.children賦值,若是大於一個,用一個數組存儲,而後賦值到props.childrenbabel

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;
      }
複製代碼
  • 接下來返回一個ReactElement
return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props,
     );
複製代碼
  • ReactElement的源碼
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,
      };
      // ...
      
      return element
    }  
複製代碼

React-component(ReactBaseClasses.js)

這裏面主要是Component和pureComponent的實現app

Component

  • 首先是Component源碼

函數Component有三個參數,props、context、updater(ReactDOM裏面用於更新狀態等的方法),props和context平時都是能夠用到的,this.refs當咱們使用最先的字符串ref的時候能夠這麼獲取到。dom

function Component(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      // We initialize the default updater but the real one gets injected by the
      this.updater = updater || ReactNoopUpdateQueue;
    }
複製代碼
  • Component原型上的setState方法
Component.prototype.setState = function(partialState, callback) {
      // 警告的 沒啥用        
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      // 把state和callback回調函數 交給updater.enqueueSetState來處理
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    // 強制更新
    /*
        默認狀況下,當組件的state或props改變時,組件將從新渲染。若是你的render()方法依賴於一些其餘的數據,你能夠告訴React組件須要經過調用forceUpdate()從新渲染。
        調用forceUpdate()會致使組件跳過shouldComponentUpdate(),直接調用render()。這將觸發組件的正常生命週期方法,包括每一個子組件的shouldComponentUpdate()方法。
        forceUpdate就是從新render。有些變量不在state上,當時你又想達到這個變量更新的時候,刷新render;或者state裏的某個變量層次太深,更新的時候沒有自動觸發render。這些時候均可以手動調用forceUpdate自動觸發render
    */
    Component.prototype.forceUpdate = function(callback) {
      this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
    };
複製代碼

pureComponent

pureComponent是繼承自Component,只不過多了一個用於區分的isPureReactComponent屬性ide

// 幾乎是和Component是一致的
    
    function PureComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    // 這裏是相似於Object.create方式的繼承
    function ComponentDummy() {}
    ComponentDummy.prototype = Component.prototype;
    const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
    pureComponentPrototype.constructor = PureComponent;
    
    // 賦值prototype上的屬性
    // Avoid an extra prototype jump for these methods.
    Object.assign(pureComponentPrototype, Component.prototype);
    
    // 經過這個變量區別下普通的 Component
    pureComponentPrototype.isPureReactComponent = true;
複製代碼

refs

refs的三種建立方式:函數

  • 字符串方式(不推薦)
  • ref={input => this.input = input}
  • React.createRef

React.createRef的源碼

返回一個對象,經過對象的current來獲取refoop

import type {RefObject} from 'shared/ReactTypes';
    
    // an immutable object with a single mutable value
    
    export function createRef(): RefObject {
      const refObject = {
        current: null,
      };
      if (__DEV__) {
        Object.seal(refObject); // 封閉一個對象,阻止添加新屬性並將全部現有屬性標記爲不可配置
      }
      return refObject;
    }
複製代碼

React.forwardRef的源碼

render函數多傳了一個ref,注意這裏的$$typeof又不一樣了。ui

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {

  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
複製代碼

ReactChildren

這部分的源碼主要是針對React.Children的,而且裏面有一個對象池的概念。主要看一下React.children.map的處理

  • mapChildren
function mapChildren(children, func, context) {
      if (children == null) {
        return children;
      }
      // 遍歷出來的元素會存放到result中,最終返回
      const result = [];
      mapIntoWithKeyPrefixInternal(children, result, null, func, context);
      return result;
    }
複製代碼
  • mapIntoWithKeyPrefixInternal
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
      // 處理 key
      let escapedPrefix = '';
      if (prefix != null) {
        escapedPrefix = escapeUserProvidedKey(prefix) + '/';
      }
      // getPooledTraverseContext 和 releaseTraverseContext 是配套的函數
      // 用處其實很簡單,就是維護一個大小爲 10 的對象重用池
      // 每次從這個池子裏取一個對象去賦值,用完了就將對象上的屬性置空而後丟回池子
      const traverseContext = getPooledTraverseContext(
        array,
        escapedPrefix,
        func,
        context,
      );
      // 遍歷全部children節點
      traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
      // 釋放對象池
      releaseTraverseContext(traverseContext);
    }
複製代碼
  • getPooledTraverseContext / releaseTraverseContext
const POOL_SIZE = 10;
    const traverseContextPool = [];
    function getPooledTraverseContext(
      mapResult,
      keyPrefix,
      mapFunction,
      mapContext,
    ) {
      if (traverseContextPool.length) { // 數組中存在 直接取出來一個 賦值
        const traverseContext = traverseContextPool.pop();
        traverseContext.result = mapResult;
        traverseContext.keyPrefix = keyPrefix;
        traverseContext.func = mapFunction;
        traverseContext.context = mapContext;
        traverseContext.count = 0;
        return traverseContext;
      } else { // 沒有直接返回對象
        return {
          result: mapResult,
          keyPrefix: keyPrefix,
          func: mapFunction,
          context: mapContext,
          count: 0,
        };
      }
    }
    function releaseTraverseContext(traverseContext) { // 屬性值置爲null,而後放入到池子裏面
      traverseContext.result = null;
      traverseContext.keyPrefix = null;
      traverseContext.func = null;
      traverseContext.context = null;
      traverseContext.count = 0;
      if (traverseContextPool.length < POOL_SIZE) {
        traverseContextPool.push(traverseContext);
      }
    }
複製代碼
  • traverseAllChildren / traverseAllChildrenImpl

callback爲mapSingleChildIntoContext

function traverseAllChildren(children, callback, traverseContext) {
      if (children == null) {
        return 0;
      }
      return traverseAllChildrenImpl(children, '', callback, traverseContext);
    }
複製代碼
  • traverseAllChildrenImpl
function traverseAllChildrenImpl(
      children,
      nameSoFar,
      callback,
      traverseContext,
    ) {
      // 這個函數核心做用就是經過把傳入的 children 數組經過遍歷攤平成單個節點
      // 而後去執行 mapSingleChildIntoContext
    
      // 開始判斷 children 的類型
      const type = typeof children;
    
      if (type === 'undefined' || type === 'boolean') {
        // All of the above are perceived as null.
        children = null;
      }
    
      let invokeCallback = false;
    
      if (children === null) {
        invokeCallback = true;
      } else {
        switch (type) {
          case 'string':
          case 'number':
            invokeCallback = true;
            break;
          case 'object':
            switch (children.$$typeof) {
              case REACT_ELEMENT_TYPE:
              case REACT_PORTAL_TYPE:
                invokeCallback = true;
            }
        }
      }
      // 若是 children 是能夠渲染的節點的話,就直接調用 callback
      // callback 是 mapSingleChildIntoContext
      if (invokeCallback) {
        callback(
          traverseContext,
          children,
          // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows.
          nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
        );
        return 1;
      }
    
      // nextName 和 nextNamePrefix 都是在處理 key 的命名
      let child;
      let nextName;
      let subtreeCount = 0; // Count of children found in the current subtree.
      const nextNamePrefix =
        nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
    
      // 節點是數組的話,就開始遍歷數組,而且把數組中的每一個元素再遞歸執行 traverseAllChildrenImpl
      // 這一步操做也用來攤平數組的
      // React.Children.map(this.props.children, c => [[c, c]])
      // c => [[c, c]] 會被攤平爲 [c, c, c, c]
      
      if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
          child = children[i];
          nextName = nextNamePrefix + getComponentKey(child, i);
          subtreeCount += traverseAllChildrenImpl(
            child,
            nextName,
            callback,
            traverseContext,
          );
        }
      } else {
        // 不是數組的話,就看看 children 是否能夠支持迭代
        // 就是經過 obj[Symbol.iterator] 的方式去取
        const iteratorFn = getIteratorFn(children);
        // 只有取出來對象是個函數類型纔是正確的
        if (typeof iteratorFn === 'function') {
          // 而後就是執行迭代器,重複上面 if 中的邏輯了
          const iterator = iteratorFn.call(children);
          let step;
          let ii = 0;
          while (!(step = iterator.next()).done) {
            child = step.value;
            nextName = nextNamePrefix + getComponentKey(child, ii++);
            subtreeCount += traverseAllChildrenImpl(
              child,
              nextName,
              callback,
              traverseContext,
            );
          }
複製代碼
  • mapSingleChildIntoContext
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
      const {result, keyPrefix, func, context} = bookKeeping;
      // func 就是咱們在 React.Children.map(this.props.children, c => c)
      // 中傳入的第二個函數參數
      let mappedChild = func.call(context, child, bookKeeping.count++);
      // 判斷函數返回值是否爲數組
      // React.Children.map(this.props.children, c => [c, c])
      // 對於 c => [c, c] 這種狀況來講,每一個子元素都會被返回出去兩次
      // 也就是說假若有 2 個子元素 c1 c2,那麼經過調用 React.Children.map(this.props.children, c => [c, c]) 後
      // 返回的應該是 4 個子元素,c1 c1 c2 c2
      if (Array.isArray(mappedChild)) {
        // 是數組的話就回到最早調用的函數中
        // 而後回到以前 traverseAllChildrenImpl 攤平數組的問題
        // 假如 c => [[c, c]],當執行這個函數時,返回值應該是 [c, c]
        // 而後 [c, c] 會被當成 children 傳入
        // traverseAllChildrenImpl 內部邏輯判斷是數組又會從新遞歸執行
        // 因此說即便你的函數是 c => [[[[c, c]]]]
        // 最後也會被遞歸攤平到 [c, c, c, c]
        mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
      } else if (mappedChild != null) {
        // 不是數組且返回值不爲空,判斷返回值是否爲有效的 Element
        // 是的話就把這個元素 clone 一遍而且替換掉 key
        if (isValidElement(mappedChild)) {
          mappedChild = cloneAndReplaceKey(
            mappedChild,
            // Keep both the (mapped) and old keys if they differ, just as
            // traverseAllChildren used to do for objects as children
            keyPrefix +
              (mappedChild.key && (!child || child.key !== mappedChild.key)
                ? escapeUserProvidedKey(mappedChild.key) + '/'
                : '') +
              childKey,
          );
        }
        result.push(mappedChild);
      }
    }
複製代碼
  • cloneAndReplaceKey

替換新key,返回元素

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

ReactChildren的流程圖

相關文章
相關標籤/搜索