人生就是你所有选择的总和。那么,你今天要做什么?——阿尔贝·加缪
四年,如人生小溪中的一洼清水,如历史长河中的一点水滴,而却就是这四年,我完成了从懵懂到成熟的蜕变。回首这四年,有过创业,有过生病,有过说不出的苦楚,也有过让我笑不间断的喜悦。
那年的背包,依然在背着;那年的代码,依然还在用类似的逻辑实现着;一件好的东西总会让我爱不释手,react就是其中一个,从React.createClass到React.createElement到React.Component;从Mixin到class component到functional component;从flux到redux、mobx到hooks;每一次更进一步,每一次爱更深一筹。就在这个时间节点,我觉得我作为一个禅意开发者,应该纪念一下我这位老情人了。
这一系列文章与视频讲解(微信公众号:《JavaScript全栈》)将深入剖析React源码。
如需倍速播放视频,bilibili搜索:合一大师
为了保证源码一致,请阅读与本文及视频相同版本,可到github下载,地址:https://github.com/Walker-Leee/react-learn-code-v16.12.0
解读安排如下
好了,感慨发完,我们来一起揭开React神秘面纱吧!
早期做react开发的同学应该都知道,最开始react和react-dom在同一个包,后来为了做平台移植性,将react与react-dom分拆,相信做过react-native的同学都知道,我们写react-native项目时,也是用的react,只是表现层用了react-native的组件与api。所以看react源码我们先分析react对于api的定义。
我将react中的部分代码片段展示于此
import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
import {forEach, map, count, toArray, only} from './ReactChildren';
import {
createElement,
createFactory,
cloneElement,
isValidElement,
jsx,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
useResponder,
useTransition,
useDeferredValue,
} from './ReactHooks';
两者的区别在于,PureComponent多给了一个标识,通过该标识在ReactFiberClassComponent
中处理,决定是否进行shalloEqual。
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
比较state和props的值,来判断是否需要更新。
类似该对比的地方还有一个,就是在 shouldComponentUpdate
。
更新后的ref用法,我们可以看到React即将抛弃<div>123</div>
,以后只能使用以下两种方式使用ref。
class App extends React.Component{
constructor() {
this.ref = React.createRef()
}
render() {
return <div ref={this.ref} />
// 或者是
return <div ref={(node) => this.ref = node} />
}
}
用来解决组件封装时,ref
的传递问题,大家看过antd源码的应该知道,很多组件使用到了 forwardRef
。比如form组件中,@Form.create()
将form组件相关的props绑定到组件上,this.props.validate
该文件中包含api有:forEach, map, count, toArray, only
,这些方法都是对于reactChildren的处理。
我们在使用react似乎少见createElement方法,因为在我们现在项目中大多用上了jsx,大多时候是babel帮我们将jsx转换为createElement,React.createElement('h1', {id: 'title'}, 'hello world')
。
cloneElement顾名思义,拷贝已有元素。
函数组件中类似pureComponent的用法,浅比较函数式组件的props,确定是否需要更新。
export default function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
}
在react中,调用createElement方法,返回值为ReactElement
。
export function createElement(type, config, children) {
// ...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
// 与createElement相比较,预先定义ReactElement的type值,并返回ReactElement
export function createFactory(type) {
const factory = createElement.bind(null, type);
factory.type = type;
return factory;
}
我们再来看看ReactElement的定义
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
// 该参数指明React节点类型
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
// 标识该ReactElement属于什么类型
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
// 记录
_owner: owner,
};
return element;
};
我们可以发现,ReactElement
只是一个用来记录节点相关信息的对象,在后续的操作中通过该对象中的这些属性值,执行不同类型逻辑。同时,这些信息在不同平台渲染时,提供了脱离平台的能力。
type BaseFiberRootProperties = {|
// 挂载节点,在ReactDOM.render方法接收的第二个参数
containerInfo: any,
// 在持久更新时用到该属性,换言之不支持增量更新平台,在react-dom中不涉及
pendingChildren: any,
// 当前应用对应的Fiber,即Root Fiber
current: Fiber,
// 以下顺序表示优先级
// 1) 还没提交(committed)的任务
// 2) 还未提交的挂起任务
// 3) 未提交的可能被挂起的任务
// 在提交时被挂起最老和最新任务
earliestSuspendedTime: ExpirationTime,
latestSuspendedTime: ExpirationTime,
// The earliest and latest priority levels that are not known to be suspended.
// 不确定是否会挂起的最老和最新任务(所有任务初始化都是该状态)
earliestPendingTime: ExpirationTime,
latestPendingTime: ExpirationTime,
// The latest priority level that was pinged by a resolved promise and can be retried.
latestPingedTime: ExpirationTime,
// 如果有抛出错误且此时没有更多更新,此时我们将尝试在处理错误前同步从头渲染
// 在renderRoot出现无法处理的错误时,该值会被置为`true`
didError: boolean,
// 等待提交任务的`expirationTime`属性
pendingCommitExpirationTime: ExpirationTime,
// 已经完成的任务的FiberRoot对象,如果你只有一个Root,那他永远只可能是这个Root对应的Fiber,或者是null
// 在commit阶段,只会处理这个值对应的任务
finishedWork: Fiber | null,
// 在任务被挂起时,通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout
timeoutHandle: TimeoutHandle | NoTimeout,
// 顶层context对象,只有主动调用renderSubtreeIntoContainer时才会使用到
context: Object | null,
pendingContext: Object | null,
// 用来确定在第一次渲染时,是否需要合并
hydrate: boolean,
// 当前root对象上所剩余的过期时间
nextExpirationTimeToWorkOn: ExpirationTime,
// 当前更新对应的过期时间
expirationTime: ExpirationTime,
// List of top-level batches. This list indicates whether a commit should be
// deferred. Also contains completion callbacks.
// 顶层批处理任务,该变量指明一个commit是否应该被推迟处理,同时包含了完成处理后的回调
firstBatch: Batch | null,
// root之间关联的链表结构
nextScheduledRoot: FiberRoot | null,
|};
// Fiber对应一个需要被处理或者已经处理的组件,组件与Fiber可以是一对多关系
type Fiber = {|
// 不同的组件类型
tag: WorkTag,
// ReactElement里面的key
key: null | string,
// ReactElement.type,我们调用`createElement`的第一个参数
elementType: any,
// The resolved function/class/ associated with this fiber.
// 异步组件resolved之后返回的内容,一般是`function`或者`class`,表示函数或class
type: any,
// The local state associated with this fiber.
// 跟当前Fiber相关本地状态(若在浏览器环境中,该值为DOM节点)
stateNode: any,
// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
return: Fiber | null,
// 指向自身的第一个子节点
// 单链表树结构
child: Fiber | null,
// 指向自身的兄弟节点
// 兄弟节点的return与之指向同一个父节点
sibling: Fiber | null,
index: number,
// ref属性
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
// 新的更新带来的props
pendingProps: any,
// 上次渲染完成后的props
memoizedProps: any,
// 队列,存放该Fiber对应的组件产生的Update
updateQueue: UpdateQueue<any> | null,
// 上一次渲染时的state
memoizedState: any,
// 列表,存放这个Fiber依赖的context
firstContextDependency: ContextDependency<mixed> | null,
// 用来描述当前Fiber和他子树的`Bitfield`
// 共存的模式表示这个子树是否默认是异步渲染的
// Fiber被创建的时候他会继承父Fiber
// 其他的标识也可以在创建的时候被设置
// 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
mode: TypeOfMode,
// Effect
// 用来记录Side Effect
effectTag: SideEffectTag,
// 单链表用来快速查找下一个side effect
nextEffect: Fiber | null,
// 子树中第一个side effect
firstEffect: Fiber | null,
// 子树中最后一个side effect
lastEffect: Fiber | null,
// 代表任务在未来的哪个时间点应该被完成
// 不包括他的子树产生的任务
expirationTime: ExpirationTime,
// 快速确定子树中是否有不在等待的变化
childExpirationTime: ExpirationTime,
// 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber,current <==> workInProgress
//在渲染完成后,保存fiber
alternate: Fiber | null,
// 调试相关,收集每个Fiber和子树渲染时间
actualDuration?: number,
actualStartTime?: number,
selfBaseDuration?: number,
treeBaseDuration?: number,
_debugID?: number,
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
|};
这三个文件主要定义了react中操作相关的类型,值得一提的是,react中类型的定义与组合很巧妙,如果同学之前未使用过这种思路,可以在权限设计系统中试用该方法。
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010;
export const Update = /* */ 0b00000000100;
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b00111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010;
export const Update = /* */ 0b00000000100;
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b00111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
export type Update<State> = {
// 更新的过期时间
expirationTime: ExpirationTime,
// 该tag标识更新类型
// UpdateState -> 0;
// ReplaceState -> 1;
// ForceUpdate -> 2;
// CaptureUpdate -> 3;
tag: 0 | 1 | 2 | 3,
// 更新内容,如调用setState时接收的第一个参数
payload: any,
// 对应的回调函数,调用setState或render时
callback: (() => mixed) | null,
// 指向下一个更新
next: Update<State> | null,
// 指向下一个side effect
nextEffect: Update<State> | null,
};
export type UpdateQueue<State> = {
// 每次操作完更新后的state
baseState: State,
// 队首的Update
firstUpdate: Update<State> | null,
// 队尾的Update
lastUpdate: Update<State> | null,
firstCapturedUpdate: Update<State> | null,
lastCapturedUpdate: Update<State> | null,
firstEffect: Update<State> | null,
lastEffect: Update<State> | null,
firstCapturedEffect: Update<State> | null,
lastCapturedEffect: Update<State> | null,
};
数据结构中有一个结构——链表,不知可否记得链表的遍历?最常见链表的遍历使用递归实现,该api实现就是借助递归。我们以forEach为例来看看代码片段实现。
function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
const traverseContext = getPooledTraverseContext(
null,
null,
forEachFunc,
forEachContext,
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext,
) {
const type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
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,
// 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,
);
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[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 if (type === 'object') {
let addendum = '';
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;
}
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
const POOL_SIZE = 10;
const traverseContextPool = [];
function releaseTraverseContext(traverseContext) {
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext);
}
}
本文分享自 JavaScript全栈 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有