人生就是你全部選擇的總和。那麼,你今天要作什麼?——阿爾貝·加繆node
四年,如人生小溪中的一窪清水,如歷史長河中的一點水滴,而卻就是這四年,我完成了從懵懂到成熟的蛻變。回首這四年,有過創業,有過生病,有過說不出的苦楚,也有過讓我笑不間斷的喜悅。react
那年的揹包,依然在揹着;那年的代碼,依然還在用相似的邏輯實現着;一件好的東西總會讓我愛不釋手,react就是其中一個,從React.createClass到React.createElement到React.Component;從Mixin到class component到functional component;從flux到redux、mobx到hooks;每一次更進一步,每一次愛更深一籌。就在這個時間節點,我以爲我做爲一個禪意開發者,應該記念一下我這位老情人了。git
這一系列文章與視頻講解(微信公衆號:《JavaScript全棧》)將深刻剖析React源碼。github
爲了保證源碼一致,請閱讀與本文及視頻相同版本,可到github下載,地址:github.com/Walker-Leee…redux
解讀安排以下react-native
好了,感慨發完,咱們來一塊兒揭開React神祕面紗吧!api
早期作react開發的同窗應該都知道,最開始react和react-dom在同一個包,後來爲了作平臺移植性,將react與react-dom分拆,相信作過react-native的同窗都知道,咱們寫react-native項目時,也是用的react,只是表現層用了react-native的組件與api。因此看react源碼咱們先分析react對於api的定義。數組
我將react中的部分代碼片斷展現於此promise
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);
}
}
複製代碼