人生就是你全部選擇的總和。那麼,你今天要作什麼?——阿爾貝·加繆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); } } 複製代碼