深入react-基础API(一)
2020/2/27 14:15:40
本文主要是介绍深入react-基础API(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
这篇文章主要通过源码的方式去讲述reactAPI,主要是在react(v16.12.0)这个包中的源码,不涉及react-dom的代码,react-dom会在后面去讲,而在讲述API的时候也不会过多的去讲解这个API的使用方式。而在react这个包里面的API其实更多的只是定义一些组件的type去供给react-dom执行真正更新逻辑的时候使用,因此可能会缺少一些API源码并没有包含到真正执行逻辑的讲解。
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包所暴露出来的API
React.Children
forEach
从上图可以清晰大致的了解该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中是否有可以用的对象,如果有则取出第一个,并进行赋值返回,如果没有则返回一个全新的对象
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和forEach差不多,唯一的区别只是在最外层多了一个大的递归,为了扁平化map的返回值,如果已经了解了forEach,下面很多重复的步骤可以跳过
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下面看下这个函数
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估计一看代码就懂,就不解释了,直接贴
export function createRef() { const refObject = { current: null }; return refObject; }
累了先写到这里了,我会尽量把这个包的内容尽快写完,然后开始写react-dom中的内容
这篇关于深入react-基础API(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Vite多环境配置学习:新手入门教程
- 2024-11-23实现OSS直传,前端怎么实现?-icode9专业技术文章分享
- 2024-11-22在 HTML 中怎么实现当鼠标光标悬停在按钮上时显示提示文案?-icode9专业技术文章分享
- 2024-11-22html 自带属性有哪些?-icode9专业技术文章分享
- 2024-11-21Sass教程:新手入门及初级技巧
- 2024-11-21Sass学习:初学者必备的简单教程
- 2024-11-21Elmentplus入门:新手必看指南
- 2024-11-21Sass入门:初学者的简单教程
- 2024-11-21前端页面设计教程:新手入门指南
- 2024-11-21Elmentplus教程:初学者必备指南