前言
作为一个前端页面仔和需求粉碎机,在日常的工作中重复雷同的业务需求,能够获得的提高是很有限的。要想跳出此山中,开阔新视野,笔者墙裂建议大家阅读市面上顶尖开源库的源码。这是学习和掌握js语言特性的绝佳机会(前端发展到现在,大型应用高度依赖框架,正常情况下普通开发者是没有机会接触底层的语言特性),同时也是深刻理解框架底层思维的契机。这里笔者选择react
第一个开刀,市面上不少关于react源码分析的文章要么过于老旧,要么只截取部分代码或者是伪代码,笔者这里将选取react的16.8.6版本作为示例,从第0行开始,不漏过任何一个源码细节,和大家分享笔者在源码阅读过程中的体会。希望和大家共同进步,本系列博文中涉及的源码本人会放在git仓库中,链接在文末。
正文
子节点遍历
/**
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
* @param {!function} callback Callback to invoke with each child found.
* @param {?*} traverseContext Used to pass information throughout the traversal
* process.
* @return {!number} The number of children in this subtree.
*/
// 这个是个递归函数,来统计子节点数目,也会执行回调
// 遍历所有子节点的接口实现
// traverseContext这个上下文本质上就是一个存储处理结果的对象
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
// 获取children的类型
var type = typeof children;
// 如果类型为undifined或者布尔,children为null
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
var invokeCallback = false;
// 如果type为undefined、boolean、string、number、REACT_ELEMENT_TYPE、REACT_PORTAL_TYPE时,表示已经调用到底层元素,要调用回调
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) {
// 使用上级传下来的上下文跑一下回调,同时计数,第三个参数,累加当前的组件名
// 针对mapIntoWithKeyPrefixInternal,这个callback其实是mapSingleChildIntoContext
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);
// 返回计数1
return 1;
}
var child = void 0;
// 往下传递的名字
var nextName = void 0;
// 当前子树下子元素的节点个数
var subtreeCount = 0; // Count of children found in the current subtree.
// 下一个名字的前缀,如果当前的名字是空串,设置为.,否则是当前的名字+分隔符:
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// 数组的话继续递归
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
// 拼出下一个名字
nextName = nextNamePrefix + getComponentKey(child, i);
// 递归调用,获得当前子树下挂载的节点数
subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
}
} else {
// 如果是迭代器的话也继续递归
// 获取children的迭代器
var iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
{
// Warn about using Maps as children
// 如果使用map当做子元素,报错
if (iteratorFn === children.entries) {
// 控制这个报错只出现一次
!didWarnAboutMaps ? warning$1(false, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.'