這篇文章主要經過源碼的方式去講述reactAPI,主要是在react(v16.12.0)這個包中的源碼,不涉及react-dom的代碼,react-dom會在後面去講,而在講述API的時候也不會過多的去講解這個API的使用方式。而在react這個包裏面的API其實更多的只是定義一些組件的type去供給react-dom執行真正更新邏輯的時候使用,所以可能會缺乏一些API源碼並無包含到真正執行邏輯的講解。react
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
從上圖能夠清晰大體的瞭解該API的調用站棧流程數組
const POOL_SIZE = 10; // 對象池最大數量 const traverseContextPool = []; // 緩存對象,重複使用,減小內存開銷以及重複生明對象
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); }
以上是這個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; }
function forEachSingleChild(bookKeeping, child) { const { func, context } = bookKeeping; func.call(context, child, bookKeeping.count++); }
// 釋放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); } }
從上圖上能夠看到 其實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; }
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); }
這裏的幾個步驟幾乎和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); } }
count 函數特別簡單,就下面三行代碼函數
function countChildren(children) { return traverseAllChildren(children, () => null, null); }
function toArray(children) { const result = []; mapIntoWithKeyPrefixInternal(children, result, null, child => child); return result; }
這個API感受並無什麼用性能
function onlyChild(children) { invariant( isValidElement(children), 'React.Children.only expected to receive a single React element child.', ); return children; }
這個API估計一看代碼就懂,就不解釋了,直接貼spa
export function createRef() { const refObject = { current: null }; return refObject; }
累了先寫到這裏了,我會盡可能把這個包的內容儘快寫完,而後開始寫react-dom中的內容