React源码解析——ReactAPI
<p><span style="font-size: 1.5em">一、API背景</span></p><h3> api的具体转化关系</h3>
<p>可以通过到https://babeljs.io/repl/网站去将我们创建的Jsx进行实时的转译</p>
<p><img src="https://img2018.cnblogs.com/blog/988076/201908/988076-20190825122159409-1729536442.png" alt=""></p>
<p><img src="https://img2018.cnblogs.com/blog/988076/201908/988076-20190825123000749-1507555063.png" alt=""></p>
<p><img src="https://img2018.cnblogs.com/blog/988076/201908/988076-20190825133534348-1945314039.png" alt=""></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
</pre>
</div>
<p> 请先无视<code>__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED</code></p>
<p> <span style="background-color: rgba(255, 0, 0, 1)"><strong>__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED</strong></span>这个属性引用得是<code>ReactSharedInternals</code>,其中包括了<code>ReactCurrentOwner</code>和<code>ReactDebugCurrentFrame</code>(仅dev),ReactCurrentOwner是fiber算法用到的,这样是把<code>renderer</code>完全独立,所以以后即使换个render算法也没有问题,ReactDebugCurrentFrame则是用来调试render过程的,</p>
<h3 id="children">Children</h3>
<p>这个对象提供了一堆帮你处理<code>props.children</code>的方法,因为<code>children</code>是一个类似数组但是不是数组的数据结构,如果你要对其进行处理可以用<code>React.Children</code>外挂的方法。</p>
<h3 id="createref">createRef</h3>
<p>新的<code>ref</code>用法,React即将抛弃<code><div ref="myDiv" /></code>这种<code>string ref</code>的用法,将来你只能使用两种方式来使用<code>ref</code></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">class App extends React.Component{
constructor() {
this.ref = React.createRef()
}
render() {
return <div ref={this.ref} />
// or
return <div ref={(node) => this.funRef = node} />
}
}
</pre>
</div>
<p> </p>
<h3 id="component--purecomponent">Component & PureComponent</h3>
<p>这两个类基本相同,唯一的区别是<code>PureComponent</code>的原型上多了一个标识</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
</pre>
</div>
<p> </p>
<p>这是检查组件是否需要更新的一个判断,<code>ctor</code>就是你声明的继承自<code>Component or PureComponent</code>的类,他会判断你是否继承自<code>PureComponent</code>,如果是的话就<code>shallowEqual</code>比较<code>state</code>和<code>props</code>。</p>
<p>顺便说一下:React中对比一个ClassComponent是否需要更新,只有两个地方。一是看有没有<code>shouldComponentUpdate</code>方法,二就是这里的<code>PureComponent</code>判断</p>
<h3 id="createcontext">createContext</h3>
<p><code>createContext</code>是官方定稿的<code>context</code>方案,在这之前我们一直在用的老的<code>context API</code>都是React不推荐的API,现在新的API释出,官方也已经确定在17大版本会把老<code>API</code>去除。</p>
<p>新API的使用方法:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const { Provider, Consumer } = React.createContext('defaultValue')
const ProviderComp = (props) => (
<Provider value={'realValue'}>
{props.children}
</Provider>
)
const ConsumerComp = () => (
<Consumer>
{(value) => <p>{value}</p>}
</Consumber>
)
</pre>
</div>
<p> </p>
<p>后面讲<code>context</code>会专门比较新老的API的差异,提前说一句,老API的性能不是一般的差</p>
<h3 id="forwardref">forwardRef</h3>
<p><code>forwardRef</code>是用来解决HOC组件传递<code>ref</code>的问题的,所谓HOC就是<code>Higher Order Component</code>,比如使用<code>redux</code>的时候,我们用<code>connect</code>来给组件绑定需要的state,这其中其实就是给我们的组件在外部包了一层组件,然后通过<code>...props</code>的方式把外部的<code>props</code>传入到实际组件。<code>forwardRef</code>的使用方法如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const TargetComponent = React.forwardRef((props, ref) => (
<TargetComponent ref={ref} />
))
</pre>
</div>
<p> </p>
<p>这也是为什么要提供<code>createRef</code>作为新的<code>ref</code>使用方法的原因,如果用<code>string ref</code>就没法当作参数传递了。</p>
<p>这里只是简单说一下使用方法,后面讲<code>ref</code>的时候会详细分析。</p>
<h3 id="类型">类型</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
</pre>
</div>
<p> </p>
<p>这四个都是React提供的<em>组件</em>,但他们呢其实都只是占位符,都是一个<code>Symbol</code>,在React实际检测到他们的时候会做一些特殊的处理,比如<code>StrictMode</code>和<code>AsyncMode</code>会让他们的子节点对应的Fiber的<code>mode</code>都变成和他们一样的<code>mode</code></p>
<h3 id="createelement--cloneelement--createfactory--isvalidelement">createElement & cloneElement & createFactory & isValidElement</h3>
<p><code>createElement</code>可谓是React中最重要的API了,他是用来创建<code>ReactElement</code>的,但是很多同学却从没见过也没用过,这是为啥呢?因为你用了JSX,JSX并不是标准的js,所以要经过编译才能变成可运行的js,而编译之后,<code>createElement</code>就出现了:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">// jsx
<div id="app">content</div>
// js
React.createElement('div', { id: 'app' }, 'content')
</pre>
</div>
<p> </p>
<p><code>cloneElement</code>就很明显了,是用来克隆一个<code>ReactElement</code>的</p>
<p><code>createFactory</code>是用来创建专门用来创建某一类<code>ReactElement</code>的工厂的,</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">export function createFactory(type) {
const factory = createElement.bind(null, type);
factory.type = type;
return factory;
}
</pre>
</div>
<p> </p>
<p>他其实就是绑定了第一个参数的<code>createElement</code>,一般我们用JSX进行编程的时候不会用到这个API</p>
<p> </p>
<h1 id="reactelement">二、ReactElement</h1>
<p><code>ReactElement</code>通过<code>createElement</code>创建,调用该方法需要传入三个参数:</p>
<ul>
<li>type</li>
<li>config</li>
<li>children</li>
</ul>
<p><code>type</code>指代这个<code>ReactElement</code>的类型</p>
<ul>
<li>字符串比如<code>div</code>,<code>p</code>代表原生DOM,称为<code>HostComponent</code></li>
<li>Class类型是我们继承自<code>Component</code>或者<code>PureComponent</code>的组件,称为<code>ClassComponent</code></li>
<li>方法就是<code>functional Component</code></li>
<li>原生提供的<code>Fragment</code>、<code>AsyncMode</code>等是Symbol,会被特殊处理</li>
<li>TODO: 是否有其他的</li>
</ul>
<p>从源码可以看出虽然创建的时候都是通过<code>config</code>传入的,但是<code>key</code>和<code>ref</code>不会跟其他<code>config</code>中的变量一起被处理,而是单独作为变量出现在<code>ReactElement</code>上。</p>
<p>在最后创建<code>ReactElement</code>我们看到了这么一个变量<code>$$typeof</code>,这是个啥呢,在这里可以看出来他是一个常量:<code>REACT_ELEMENT_TYPE</code>,但有一个特例:<code>ReactDOM.createPortal</code>的时候是<code>REACT_PORTAL_TYPE</code>,不过他不是通过<code>createElement</code>创建的,所以他应该也不属于<code>ReactElement</code></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">export function createElement(type, config, children) {
// 处理参数
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
return element
}
</pre>
</div>
<p> </p>
<p><code>ReactElement</code>只是一个用来承载信息的容器,他会告诉后续的操作这个节点的以下信息:</p>
<ol>
<li><code>type</code>类型,用于判断如何创建节点</li>
<li><code>key</code>和<code>ref</code>这些特殊信息</li>
<li><code>props</code>新的属性内容</li>
<li><code>$$typeof</code>用于确定是否属于<code>ReactElement</code></li>
</ol>
<p>这些信息对于后期构建应用的树结构是非常重要的,而React通过提供这种类型的数据,来脱离平台的限制</p>
<p> </p>
<h1>二、React Children</h1>
<blockquote>
<p>最开始<code>React.Children</code>这个 API 是不想讲的,一方面平时不怎么用,另一方面跟数组处理功能差不多,不深究实现是比较容易理解的。但是后来实际去看了一下源码之后发现,他的实现方式还是非常有趣的,尤其是<code>map</code>和<code>forEach</code>,我们就按照<code>map</code>的流程来看一下,<code>forEach</code>其实差不多,只是没有返回新的节点。</p>
</blockquote>
<p>先来看一下流程图:</p>
<p><img src="https://img2018.cnblogs.com/blog/988076/201908/988076-20190826102851481-665373990.png" alt=""></p>
<h3>开始源码</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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)
}
</pre>
</div>
<p> </p>
<p><code>map</code>和<code>forEach</code>的最大区别就是有没有<code>return result</code>。</p>
<p><code>getPooledTraverseContext</code>就是从<code>pool</code>里面找一个对象,<code>releaseTraverseContext</code>会把当前的<code>context</code>对象清空然后放回到<code>pool</code>中。</p>
<p> </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const POOL_SIZE = 10
const traverseContextPool = []
function getPooledTraverseContext() {
// args
if (traverseContextPool.length) {
const traverseContext = traverseContextPool.pop()
// set attrs
return traverseContext
} else {
return {
/* attrs */
}
}
}
function releaseTraverseContext(traverseContext) {
// clear attrs
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext)
}
}
</pre>
</div>
<p> 那么按照这个流程来看,是不是<code>pool</code>永远都只有一个值呢,毕竟推出之后操作完了就推入了,这么循环着。答案肯定是否的,这就要讲到<code>React.Children.map</code>的一个特性了,那就是对每个节点的<code>map</code>返回的如果是数组,那么还会继续展开,这是一个递归的过程。接下去我们就来看看。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0
}
return traverseAllChildrenImpl(children, '', callback, traverseContext)
}
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,
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++) {
child = children
nextName = nextNamePrefix + getComponentKey(child, i)
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
)
}
} else {
const iteratorFn = getIteratorFn(children)
if (typeof iteratorFn === 'function') {
// iterator,和array差不多
} else if (type === 'object') {
// 提醒不正确的children类型
}
}
return subtreeCount
}
</pre>
</div>
<p> 这里就是一层递归了,对于可循环的<code>children</code>,都会重复调用<code>traverseAllChildrenImpl</code>,直到是一个节点的情况,然后调用<code>callback</code>,也就是<code>mapSingleChildIntoContext</code></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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) {
if (isValidElement(mappedChild)) {
mappedChild = cloneAndReplaceKey(
mappedChild,
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: '') +
childKey,
)
}
result.push(mappedChild)
}
}
</pre>
</div>
<p> </p>
<p><code>mapSingleChildIntoContext</code>这个方法其实就是调用<code>React.Children.map(children, callback)</code>这里的<code>callback</code>,就是我们传入的第二个参数,并得到<code>map</code>之后的结果。注意重点来了,如果<code>map</code>之后的节点还是一个数组,那么再次进入<code>mapIntoWithKeyPrefixInternal</code>,那么这个时候我们就会再次从<code>pool</code>里面去<code>context</code>了,而<code>pool</code>的意义大概也就是在这里了,如果循环嵌套多了,可以减少很多对象创建和<code>gc</code>的损耗。</p>
<p>而如果不是数组并且是一个合规的<code>ReactElement</code>,就触达顶点了,替换一下<code>key</code>就推入<code>result</code>了。</p>
<p>React 这么实现主要是两个目的:</p>
<ol>
<li>拆分<code>map</code>出来的数组</li>
<li>因为对<code>Children</code>的处理一般在<code>render</code>里面,所以会比较频繁,所以设置一个<code>pool</code>减少声明和<code>gc</code>的开销</li>
</ol>
<p>这就是<code>Children.map</code>的实现,虽然不算什么特别神奇的代码,但是阅读一下还是挺有意思的。</p>
<h3 class="title-article">react性能优化之ConcurrentMode 和 flushSync</h3>
<p> 使用 ConcurrentMode 组件包裹的子组件的渲染过程的优先级会被降低,react 会先渲染优先级高的,然后将js线程空闲出来先干其他的事,如动画的渲染,完了之后再渲染优先级低的,当我们想提高子组件渲染的优先级的时候,可以使用flushSync方法来包裹需要进行的操作。</p><br><br>
来源:https://www.cnblogs.com/fuGuy/p/11407514.html
頁:
[1]