摘要:一例子看到一個(gè)有趣的現(xiàn)象,就是多層嵌套的數(shù)組經(jīng)過后,平鋪成了,接下來以該例解析二作用源碼進(jìn)行基本的判斷和初始化后,調(diào)用該方法就是重命名了,即解析注意,該數(shù)組在里面滾了一圈后,會(huì)結(jié)果三作用的包裹器源碼第一次第二次如果字符串中有連續(xù)多個(gè)的話
一、例子
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]]] :
看到一個(gè)有趣的現(xiàn)象,就是多層嵌套的數(shù)組[item, [item, [item]]]經(jīng)過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 = []; //進(jìn)行基本的判斷和初始化后,調(diào)用該方法 //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,該數(shù)組在里面滾了一圈后,會(huì)return結(jié)果
三、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 = ""; //如果字符串中有連續(xù)多個(gè) / 的話,在匹配的字串后再加 / if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + "/"; } //從pool中找一個(gè)對(duì)象 //[],"",(item)=>{return [item,[item,] ]},undefined //traverseContext= // { // result:[], // keyPrefix:"", // func:(item)=>{return [item,[item,] ]}, // context:undefined, // count:0, // } const traverseContext = getPooledTraverseContext( array, escapedPrefix, func, context, ); //將嵌套的數(shù)組展平 traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); releaseTraverseContext(traverseContext); }
解析:
① escapeUserProvidedKey()
這個(gè)函數(shù)一般是第二層遞歸時(shí),會(huì)用到
作用:
在/后再加一個(gè)/
源碼:
const userProvidedKeyEscapeRegex = //+/g; function escapeUserProvidedKey(text) { //如果字符串中有連續(xù)多個(gè) / 的話,在匹配的字串后再加 / return ("" + text).replace(userProvidedKeyEscapeRegex, "$&/"); }
解析:
react對(duì)key定義的一個(gè)規(guī)則:
如果字符串中有連續(xù)多個(gè)/的話,在匹配的字串后再加/
例:
let a="aa/a/" console.log(a.replace(//+/g, "$&/")); // aa//a//
② getPooledTraverseContext()
作用:
創(chuàng)建一個(gè)對(duì)象池,復(fù)用Object,從而減少很多對(duì)象創(chuàng)建帶來的內(nèi)存占用和gc(垃圾回收)的損耗
源碼:
//對(duì)象池的最大容量為10 const POOL_SIZE = 10; //對(duì)象池 const traverseContextPool = []; //[],"",(item)=>{return [item,[item,] ]},undefined function getPooledTraverseContext( mapResult, keyPrefix, mapFunction, mapContext, ) { //如果對(duì)象池內(nèi)存在對(duì)象,則出隊(duì)一個(gè)對(duì)象, //并將arguments的值賦給對(duì)象屬性 //最后返回該對(duì)象 if (traverseContextPool.length) { const traverseContext = traverseContextPool.pop(); traverseContext.result = mapResult; traverseContext.keyPrefix = keyPrefix; traverseContext.func = mapFunction; traverseContext.context = mapContext; traverseContext.count = 0; return traverseContext; } //如果不存在,則返回一個(gè)新對(duì)象 else { //{ // result:[], // keyPrefix:"", // func:(item)=>{return [item,[item,] ]}, // context:undefined, // count:0, // } return { result: mapResult, keyPrefix: keyPrefix, func: mapFunction, context: mapContext, count: 0, }; } }
解析:
在每次map()的過程中,每次遞歸都會(huì)用到traverseContext 對(duì)象,
創(chuàng)建traverseContextPool對(duì)象池的目的,就是**復(fù)用里面的對(duì)象,
以減少內(nèi)存消耗**,并且在map()結(jié)束時(shí),
將復(fù)用的對(duì)象初始化,并push進(jìn)對(duì)象池中(releaseTraverseContext),以供下次map()時(shí)使用
③ mapSingleChildIntoContext()
mapSingleChildIntoContext是traverseAllChildren(children, mapSingleChildIntoContext, traverseContext)的第二個(gè)參數(shù),為避免講traverseAllChildren要調(diào)頭看這個(gè) API,就先分析下
作用:
遞歸仍是數(shù)組的child;
將單個(gè)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) {
//解構(gòu)賦值
const {result, keyPrefix, func, context} = bookKeeping;
//func:(item)=>{return [item,[item,] ]},
//item即1
//第二個(gè)參數(shù)bookKeeping.count++很有意思,壓根兒沒用到,但仍起到計(jì)數(shù)的作用
let mappedChild = func.call(context, child, bookKeeping.count++);
//如果根據(jù)React.Children.map()第二個(gè)參數(shù)callback,執(zhí)行仍是一個(gè)數(shù)組的話,
//遞歸調(diào)用mapIntoWithKeyPrefixInternal,繼續(xù)之前的步驟,
//直到是單個(gè)ReactElement
if (Array.isArray(mappedChild)) {
//mappedChild:[item,[item,] ]
//result:[]
//childKey:.0
//func:c => c
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
}
//當(dāng)mappedChild是單個(gè)ReactElement并且不為null的時(shí)候
else if (mappedChild != null) {
if (isValidElement(mappedChild)) {
//賦給新對(duì)象除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對(duì)待objects做的那樣
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + "/"
: "") +
childKey,
);
}
//result即map時(shí),return的result
result.push(mappedChild);
}
}
解析:
(1)讓child調(diào)用func 方法,所得的結(jié)果如果是數(shù)組的話繼續(xù)遞歸;如果是單個(gè)ReactElement的話,將其放入result數(shù)組中
(2)cloneAndReplaceKey()字如其名,就是賦給新對(duì)象除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的觸發(fā)器
源碼:
// children, mapSingleChildIntoContext, traverseContext function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; } return traverseAllChildrenImpl(children, "", callback, traverseContext); }
⑤ traverseAllChildrenImpl()
作用:
核心遞歸函數(shù),目的是展平嵌套數(shù)組
源碼:
// 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") {
//以上所有的被認(rèn)為是null
// All of the above are perceived as null.
children = null;
}
//調(diào)用func的flag
let invokeCallback = false;
if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case "string":
case "number":
invokeCallback = true;
break;
case "object":
//如果props.children是單個(gè)ReactElement/PortalElement的話
//遞歸traverseAllChildrenImpl時(shí),1和2作為child
//必會(huì)觸發(fā)invokeCallback=true
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
if (invokeCallback) {
callback(
traverseContext,
children,
//如果只有一個(gè)子節(jié)點(diǎn),也將它放在數(shù)組中來處理
// 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;
//有多少個(gè)子節(jié)點(diǎn)
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];
//不手動(dòng)設(shè)置key的話第一層第一個(gè)是.0,第二個(gè)是.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,
);
}
}
//如果是一個(gè)純對(duì)象的話,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
調(diào)用callback 即mapSingleChildIntoContext ,復(fù)制除key外的屬性,替換key屬性,將其放入到result中
(2)children是Array
循環(huán)children,再用traverseAllChildrenImpl 執(zhí)行child
三、流程圖
四、根據(jù)React.Children.map()的算法出一道面試題
數(shù)組扁平化處理:
實(shí)現(xiàn)一個(gè)flatten方法,使得輸入一個(gè)數(shù)組,該數(shù)組里面的元素也可以是數(shù)組,該方法會(huì)輸出一個(gè)扁平化的數(shù)組
// 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] // 實(shí)現(xiàn)flatten方法使得flatten(givenArr)——>outputArr
解法一:根據(jù)上面的流程圖使用遞歸
function flatten(arr){ var res = []; for(var i=0;i解法二:ES6
function flatten(array) { //只要數(shù)組中的元素有一個(gè)嵌套數(shù)組,就合并 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 }(完)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/106359.html
摘要:本次分析的源碼采用的是的版本核心接口提供了處理的工具集我們先來看看做了什么事情即當(dāng)為空時(shí),返回不為時(shí)調(diào)用,最終返回一個(gè)數(shù)組這里說一下,可以通過傳入的對(duì)所有子組件進(jìn)行操作,具體使用方法看下圖參數(shù)通過配合的例子把父組件的賦值給每個(gè)子組件我們先不 本次分析的源碼采用的是16.4.1的版本 核心接口 showImg(https://segmentfault.com/img/bVbeT9f?w=...
摘要:我們先來看下這個(gè)函數(shù)的一些神奇用法對(duì)于上述代碼,也就是函數(shù)來說返回值是。不管你第二個(gè)參數(shù)的函數(shù)返回值是幾維嵌套數(shù)組,函數(shù)都能幫你攤平到一維數(shù)組,并且每次遍歷后返回的數(shù)組中的元素個(gè)數(shù)代表了同一個(gè)節(jié)點(diǎn)需要復(fù)制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個(gè)系列文章: 現(xiàn)在工作中基本都用 React 了,由此想了解下內(nèi)部原理 市面上 Vue 的源碼解讀數(shù)不勝數(shù),但是反觀...
Button Button包括了兩個(gè)組件,Button與ButtonGroup。 ButtonProps 看一個(gè)組件首先看的是他的傳參也就是props,所以我們這里先看Button組件的ButtonProps export type ButtonType = primary | ghost | dashed | danger; export type ButtonShape = circl...
摘要:這一周連續(xù)發(fā)表了兩篇關(guān)于的文章組件復(fù)用那些事兒實(shí)現(xiàn)按需加載輪子應(yīng)用設(shè)計(jì)之道化妙用其中涉及到組件復(fù)用輪子設(shè)計(jì)相關(guān)話題,并配合相關(guān)場景實(shí)例進(jìn)行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續(xù)發(fā)表了兩篇關(guān)于 React 的文章: 組件復(fù)用那些事兒 - React 實(shí)現(xiàn)按需加載輪子 React ...
閱讀 1801·2023-04-25 15:51
閱讀 2510·2021-10-13 09:40
閱讀 2145·2021-09-23 11:22
閱讀 3252·2019-08-30 14:16
閱讀 2666·2019-08-26 13:35
閱讀 1861·2019-08-26 13:31
閱讀 885·2019-08-26 11:39
閱讀 2743·2019-08-26 10:33