深刻react-基礎API(一)

前言

這篇文章主要經過源碼的方式去講述reactAPI,主要是在react(v16.12.0)這個包中的源碼,不涉及react-dom的代碼,react-dom會在後面去講,而在講述API的時候也不會過多的去講解這個API的使用方式。而在react這個包裏面的API其實更多的只是定義一些組件的type去供給react-dom執行真正更新邏輯的時候使用,所以可能會缺乏一些API源碼並無包含到真正執行邏輯的講解。react

React-API

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: createElement,
  cloneElement:  cloneElement,
  createFactory: createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

以上就是全部react包所暴露出來的APIapi

React.Children

forEach

children.forEach.png

從上圖能夠清晰大體的瞭解該API的調用站棧流程數組

const POOL_SIZE = 10; // 對象池最大數量
const traverseContextPool = []; // 緩存對象,重複使用,減小內存開銷以及重複生明對象
  • 建立一個空數組做爲traverseContextPool,主要爲了減小對象重複聲明的開銷,對性能產生影響。
  • 預設poll size的大小
function forEachChildren(children, forEachFunc, forEachContext) {
  // 判斷children是否存在
  if (children == null) {
    return children;
  }

  // 從context pool中獲取對象
  const traverseContext = getPooledTraverseContext(
    null,
    null,
    forEachFunc,
    forEachContext
  );

  // 開始遍歷
  traverseAllChildren(children, forEachSingleChild, traverseContext);
  // 釋放緩存
  releaseTraverseContext(traverseContext);
}
  • 開始調用forEachChildren
  • 首先判斷children是否不存在,是則直接返回
  • 調用getPooledTraverseContext獲取context對象
  • 調用traverseAllChildren開始遍歷
  • 調用releaseTraverseContext釋放緩存

以上是這個api的一個大體流程,下面再看下幾個調用棧分別作了什麼緩存

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

上面這個函數很簡單,不用瞭解的太過於複雜,它主要先看traverseContextPool中是否有能夠用的對象,若是有則取出第一個,並進行賦值返回,若是沒有則返回一個全新的對象app

function traverseAllChildrenImpl(
  children,
  nameSoFar,
  callback,
  traverseContext
) {
  const type = typeof children;

  if (type === "undefined" || type === "boolean") {
    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;
        }
    }
  }

  if (invokeCallback) {
    callback(traverseContext, children);
    return 1;
  }

  let child;
  let nextName;
  let subtreeCount = 0;

  const nextNamePrefix =
    nameSoFar === "" ? SEPARATOR : nameSoFar + SUBSEPARATOR;

  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 {
    const iteratorFn = getIteratorFn(children);
    if (typeof iteratorFn === "function") {
      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
        );
      }
    } else {
    }
  }

  return subtreeCount;
}
  • 判斷children是否爲單個節點,簡單的理解爲非數組
  • 若是是則將invokeCallback設置爲true,不是則爲false
  • 只有當invokeCallback爲true的時候則進行callback()這裏的callback指的是forEachSingleChild後面再介紹,這個函數比較簡單
  • 若是invokeCallback爲false則繼續遍歷,再調用traverseAllChildrenImpl,整個函數其實只是一個遞歸扁平化數組
function forEachSingleChild(bookKeeping, child) {
  const { func, context } = bookKeeping;
  func.call(context, child, bookKeeping.count++);
}
  • 這個函數只是經過call函數去進行調用func(forEach的第二個參數)
// 釋放context緩存
function releaseTraverseContext(traverseContext) {
  traverseContext.result = null;
  traverseContext.keyPrefix = null;
  traverseContext.func = null;
  traverseContext.context = null;
  traverseContext.count = 0;
  // 若是Context Pool小於最大值,則保留對象,防止對象重複聲明
  if (traverseContextPool.length < POOL_SIZE) {
    traverseContextPool.push(traverseContext);
  }
}
  • 最後調用releaseTraverseContext清空對象,並經過判斷是否小於POOL_SIZE,若是小於則將清空後的對象進行緩存。
map

map.png

從上圖上能夠看到 其實map和forEach差很少,惟一的區別只是在最外層多了一個大的遞歸,爲了扁平化map的返回值,若是已經瞭解了forEach,下面不少重複的步驟能夠跳過dom

function mapChildren(children, func, context) {
  if (children == null) {
    return children;
  }
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}
  • 判斷children是否存在,是則直接返回
  • 聲明result,用來保存最終返回的結果
  • 調用mapIntoWithKeyPrefixInternal
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
  let escapedPrefix = '';
  if (prefix != null) {
    escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  }
  const traverseContext = getPooledTraverseContext(
    array,
    escapedPrefix,
    func,
    context,
  );
  traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
  releaseTraverseContext(traverseContext);
}
  • 生成key值
  • 調用getPooledTraverseContext獲取context對象
  • 調用traverseAllChildren開始遍歷
  • 調用releaseTraverseContext釋放緩存

這裏的幾個步驟幾乎和forEach一摸同樣就不重複說明了,惟一不一樣的是forEach調用的是forEachSingleChild,而這邊調用的是mapSingleChildIntoContext下面看下這個函數ide

function mapSingleChildIntoContext(bookKeeping, child, childKey) {
  const { result, keyPrefix, func, context } = bookKeeping;
  let mappedChild = func.call(context, child, bookKeeping.count++);

  if (Array.isArray(mappedChild)) {
    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
  } else if (mappedChild != null) {
    // 驗證是不是react對象,主要是經過對象上的$$typeof屬性
    if (isValidElement(mappedChild)) {
      // 返回一個全新的reactElement
      mappedChild = cloneAndReplaceKey(mappedChild);
    }

    result.push(mappedChild);
  }
}
  • 調用func(map函數的第二個參數)
  • 拿到func函數調用後的返回結果
  • 進行判斷是否爲Array
  • 若是是Array則遞歸調用mapIntoWithKeyPrefixInternal
  • 若是不是,則將結果push到result中做爲最終結果進行返回
count

count 函數特別簡單,就下面三行代碼函數

function countChildren(children) {
  return traverseAllChildren(children, () => null, null);
}
  • 直接調用了traverseAllChildren這個函數,能夠回到上面看一下這個函數最終的返回值是什麼,應該就知道了吧
toArray
function toArray(children) {
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, child => child);
  return result;
}
  • 仍是重複使用了mapIntoWithKeyPrefixInternal這個函數,這個函數會把最終結果放到一個result數組中進行返回
only

這個API感受並無什麼用性能

function onlyChild(children) {
  invariant(
    isValidElement(children),
    'React.Children.only expected to receive a single React element child.',
  );
  return children;
}
  • 僅僅只是經過isValidElement判斷是否爲單個的節點,是則返回自己

React.createRef

這個API估計一看代碼就懂,就不解釋了,直接貼spa

export function createRef() {
  const refObject = {
    current: null
  };

  return refObject;
}

累了先寫到這裏了,我會盡可能把這個包的內容儘快寫完,而後開始寫react-dom中的內容

相關文章
相關標籤/搜索