摘要:一例子看到一個有趣的現象,就是多層嵌套的數組經過后,平鋪成了,接下來以該例解析二作用源碼進行基本的判斷和初始化后,調用該方法就是重命名了,即解析注意,該數組在里面滾了一圈后,會結果三作用的包裹器源碼第一次第二次如果字符串中有連續多個的話
一、例子
function ChildrenDemo(props) { console.log(props.children, "children30"); console.log(React.Children.map(props.children, item => [item, [item, [item]]]), "children31"); // console.log(React.Children.map(props.children,item=>item),"children31") return props.children; } export default ()=>(1 2 )
props.children :
React.Children.map(props.children, item => [item, [item, [item]]] :
看到一個有趣的現象,就是多層嵌套的數組[item, [item, [item]]]經過map()后,平鋪成[item,item,item]了,接下來以該例解析React.Child.map()
二、React.Children.map()
作用:
https://zh-hans.reactjs.org/docs/react-api.html#reactchildren
源碼:
// React.Children.map(props.children,item=>[item,[item,] ]) function mapChildren(children, func, context) { if (children == null) { return children; } const result = []; //進行基本的判斷和初始化后,調用該方法 //props.children,[],null,(item)=>{return [item,[item,] ]},undefined mapIntoWithKeyPrefixInternal(children, result, null, func, context); return result; } export { //as就是重命名了,map即mapChildren forEachChildren as forEach, mapChildren as map, countChildren as count, onlyChild as only, toArray, };
解析:
注意result,該數組在里面滾了一圈后,會return結果
三、mapIntoWithKeyPrefixInternal()
作用:
getPooledTraverseContext()/traverseAllChildren()/releaseTraverseContext()的包裹器
源碼:
//第一次:props.children , [] , null , (item)=>{return [item,[item,] ]} , undefined //第二次:[item,[item,] ] , [] , .0 , c => c , undefined function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { let escapedPrefix = ""; //如果字符串中有連續多個 / 的話,在匹配的字串后再加 / if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + "/"; } //從pool中找一個對象 //[],"",(item)=>{return [item,[item,] ]},undefined //traverseContext= // { // result:[], // keyPrefix:"", // func:(item)=>{return [item,[item,] ]}, // context:undefined, // count:0, // } const traverseContext = getPooledTraverseContext( array, escapedPrefix, func, context, ); //將嵌套的數組展平 traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); releaseTraverseContext(traverseContext); }
解析:
① escapeUserProvidedKey()
這個函數一般是第二層遞歸時,會用到
作用:
在/后再加一個/
源碼:
const userProvidedKeyEscapeRegex = //+/g; function escapeUserProvidedKey(text) { //如果字符串中有連續多個 / 的話,在匹配的字串后再加 / return ("" + text).replace(userProvidedKeyEscapeRegex, "$&/"); }
解析:
react對key定義的一個規則:
如果字符串中有連續多個/的話,在匹配的字串后再加/
例:
let a="aa/a/" console.log(a.replace(//+/g, "$&/")); // aa//a//
② getPooledTraverseContext()
作用:
創建一個對象池,復用Object,從而減少很多對象創建帶來的內存占用和gc(垃圾回收)的損耗
源碼:
//對象池的最大容量為10 const POOL_SIZE = 10; //對象池 const traverseContextPool = []; //[],"",(item)=>{return [item,[item,] ]},undefined function getPooledTraverseContext( mapResult, keyPrefix, mapFunction, mapContext, ) { //如果對象池內存在對象,則出隊一個對象, //并將arguments的值賦給對象屬性 //最后返回該對象 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 { //{ // result:[], // keyPrefix:"", // func:(item)=>{return [item,[item,] ]}, // context:undefined, // count:0, // } return { result: mapResult, keyPrefix: keyPrefix, func: mapFunction, context: mapContext, count: 0, }; } }
解析:
在每次map()的過程中,每次遞歸都會用到traverseContext 對象,
創建traverseContextPool對象池的目的,就是**復用里面的對象,
以減少內存消耗**,并且在map()結束時,
將復用的對象初始化,并push進對象池中(releaseTraverseContext),以供下次map()時使用
③ mapSingleChildIntoContext()
mapSingleChildIntoContext是traverseAllChildren(children, mapSingleChildIntoContext, traverseContext)的第二個參數,為避免講traverseAllChildren要調頭看這個 API,就先分析下
作用:
遞歸仍是數組的child;
將單個ReactElement的child加入result中
源碼:
//bookKeeping:traverseContext=
// {
// result:[],
// keyPrefix:"",
// func:(item)=>{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
//child:1
//childKey:.0
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
//解構賦值
const {result, keyPrefix, func, context} = bookKeeping;
//func:(item)=>{return [item,[item,] ]},
//item即1
//第二個參數bookKeeping.count++很有意思,壓根兒沒用到,但仍起到計數的作用
let mappedChild = func.call(context, child, bookKeeping.count++);
//如果根據React.Children.map()第二個參數callback,執行仍是一個數組的話,
//遞歸調用mapIntoWithKeyPrefixInternal,繼續之前的步驟,
//直到是單個ReactElement
if (Array.isArray(mappedChild)) {
//mappedChild:[item,[item,] ]
//result:[]
//childKey:.0
//func:c => c
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
}
//當mappedChild是單個ReactElement并且不為null的時候
else if (mappedChild != null) {
if (isValidElement(mappedChild)) {
//賦給新對象除key外同樣的屬性,替換key屬性
mappedChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
//如果新老keys是不一樣的話,兩者都保留,像traverseAllChildren對待objects做的那樣
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + "/"
: "") +
childKey,
);
}
//result即map時,return的result
result.push(mappedChild);
}
}
解析:
(1)讓child調用func 方法,所得的結果如果是數組的話繼續遞歸;如果是單個ReactElement的話,將其放入result數組中
(2)cloneAndReplaceKey()字如其名,就是賦給新對象除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; }
(3)isValidElement() 判斷是否為ReactElement
簡單看下源碼:
export function isValidElement(object) { return ( typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); }
④ traverseAllChildren()
作用:
traverseAllChildrenImpl的觸發器
源碼:
// children, mapSingleChildIntoContext, traverseContext function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; } return traverseAllChildrenImpl(children, "", callback, traverseContext); }
⑤ traverseAllChildrenImpl()
作用:
核心遞歸函數,目的是展平嵌套數組
源碼:
// children, "", mapSingleChildIntoContext, traverseContext
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
//traverseContext=
// {
// result:[],
// keyPrefix:"",
// func:(item)=>{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
traverseContext,
) {
const type = typeof children;
if (type === "undefined" || type === "boolean") {
//以上所有的被認為是null
// All of the above are perceived as null.
children = null;
}
//調用func的flag
let invokeCallback = false;
if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case "string":
case "number":
invokeCallback = true;
break;
case "object":
//如果props.children是單個ReactElement/PortalElement的話
//遞歸traverseAllChildrenImpl時,1和2作為child
//必會觸發invokeCallback=true
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
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.
//.$=0
//1 key=".0"
nameSoFar === "" ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}
let child;
let nextName;
//有多少個子節點
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
//.
nameSoFar === "" ? SEPARATOR : nameSoFar + SUBSEPARATOR;
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
//1
child = children[i];
//不手動設置key的話第一層第一個是.0,第二個是.1
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === "function") {
if (__DEV__) {
// Warn about using Maps as children
if (iteratorFn === children.entries) {
warning(
didWarnAboutMaps,
"Using Maps as children is unsupported and will likely yield " +
"unexpected results. Convert it to a sequence/iterable of keyed " +
"ReactElements instead.",
);
didWarnAboutMaps = true;
}
}
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,
);
}
}
//如果是一個純對象的話,throw error
else if (type === "object") {
let addendum = "";
if (__DEV__) {
addendum =
" If you meant to render a collection of children, use an array " +
"instead." +
ReactDebugCurrentFrame.getStackAddendum();
}
const childrenString = "" + children;
invariant(
false,
"Objects are not valid as a React child (found: %s).%s",
childrenString === "[object Object]"
? "object with keys {" + Object.keys(children).join(", ") + "}"
: childrenString,
addendum,
);
}
}
return subtreeCount;
}
解析:
分為兩部分:
(1)children是Object,并且$$typeof是REACT_ELEMENT_TYPE/REACT_PORTAL_TYPE
調用callback 即mapSingleChildIntoContext ,復制除key外的屬性,替換key屬性,將其放入到result中
(2)children是Array
循環children,再用traverseAllChildrenImpl 執行child
三、流程圖
四、根據React.Children.map()的算法出一道面試題
數組扁平化處理:
實現一個flatten方法,使得輸入一個數組,該數組里面的元素也可以是數組,該方法會輸出一個扁平化的數組
// Example let givenArr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]; let outputArr = [1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10] // 實現flatten方法使得flatten(givenArr)——>outputArr
解法一:根據上面的流程圖使用遞歸
function flatten(arr){ var res = []; for(var i=0;i解法二:ES6
function flatten(array) { //只要數組中的元素有一個嵌套數組,就合并 while(array.some(item=>Array.isArray(item))) array=[].concat(...array) console.log(array) //[1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10] return array }(完)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106359.html
摘要:本次分析的源碼采用的是的版本核心接口提供了處理的工具集我們先來看看做了什么事情即當為空時,返回不為時調用,最終返回一個數組這里說一下,可以通過傳入的對所有子組件進行操作,具體使用方法看下圖參數通過配合的例子把父組件的賦值給每個子組件我們先不 本次分析的源碼采用的是16.4.1的版本 核心接口 showImg(https://segmentfault.com/img/bVbeT9f?w=...
摘要:我們先來看下這個函數的一些神奇用法對于上述代碼,也就是函數來說返回值是。不管你第二個參數的函數返回值是幾維嵌套數組,函數都能幫你攤平到一維數組,并且每次遍歷后返回的數組中的元素個數代表了同一個節點需要復制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個系列文章: 現在工作中基本都用 React 了,由此想了解下內部原理 市面上 Vue 的源碼解讀數不勝數,但是反觀...
Button Button包括了兩個組件,Button與ButtonGroup。 ButtonProps 看一個組件首先看的是他的傳參也就是props,所以我們這里先看Button組件的ButtonProps export type ButtonType = primary | ghost | dashed | danger; export type ButtonShape = circl...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
閱讀 1792·2023-04-25 15:51
閱讀 2501·2021-10-13 09:40
閱讀 2137·2021-09-23 11:22
閱讀 3247·2019-08-30 14:16
閱讀 2657·2019-08-26 13:35
閱讀 1852·2019-08-26 13:31
閱讀 879·2019-08-26 11:39
閱讀 2739·2019-08-26 10:33