#React V18.2 源碼
前置基礎知識:工廠函數
工廠函數是一種設計模式,用于動態創建對象或函數實例。其核心思想是通過封裝對象創建的細節,提供統一的接口,從而增強代碼的靈活性和可維護性,有一些核心作用:
- 解耦創建邏輯:將對象的實例化過程與使用分離,調用方無需關心具體實現細節。
- 動態生成:根據輸入參數返回不同類型的對象或函數。
- 統一接口:通過單一入口點管理多種創建場景。
工廠函數由構造函數進階而來,都是用來創建實例。
這是一個函數,KindofNode“某類節點”的構造函數,可以通過new,創建一個實例對象(開辟一塊空間,將原型拷貝到該空間,并將該空間的this指向該實例)。很好理解。
function KindOfNode(kind, properties1, properties2, properties3) {//這是一個構造函數this.kind = kind;this.properties1 = properties1;this.properties2 = properties2;this.properties3 = properties3;
}aNode = new KindOfNode(null, '屬性1', '屬性2', '屬性3'); //創建一個對象console.dir(aNode);
//kindOfNode {
// kind: null,
// properties1: '屬性1',
// properties2: '屬性2',
// properties3: '屬性3'
// }
create關鍵詞函數,在框架中約定俗成為構造函數(博主觀察發現),讓我們對這個構造函數進行改進,可以發現可以選擇性的對構造出來的實例某些屬性進行賦值。
function createNode(kind, properties1, properties2, properties3) {//這是一個工廠函數const node = new KindOfNode(kind, properties1, properties2, properties3);if (kind === null || kind === undefined) {node.kind = 'node';}if (kind === 'hostFiber') {node.kind = 'hostFiber';}return node;
}fa_node = createNode('hostFiber', '屬性1', '屬性2', '屬性3'); //創建一個對象
fb_node = createNode(null, '屬性1', '屬性2', '屬性3'); //創建一個對象console.dir(fa_node);
console.dir(fb_node);
// kindOfNode {
// kind: 'hostFiber',
// properties1: '屬性1',
// properties2: '屬性2',
// properties3: '屬性3'
// }
// kindOfNode {
// kind: 'node',
// properties1: '屬性1',
// properties2: '屬性2',
// properties3: '屬性3'
// }
讓我們正式進入createRoot()方法
相信有了前置知識,代碼看起來不那么費力了。
在React源碼中,構造函數的使用:通常以"create"關鍵字命名的函數,函數返回一個構造函數實例或者嵌套調用一個"create"關鍵字命名的函數進行嵌套調用。
CreateRoot方法比較復雜,我列舉出了重要"create"關鍵詞的五種。
這是createRoot過程中完整的函數調用棧,嵌套調用了多種函數:
其中:
- exports.createRoot是在node.js中遵循CommanJS標準的包導出。
- createRoot$1,出于安全性添加的一層。
- createRoot??createFiber,依此進行react初始化創建。
- FiberNode構造函數是push進棧最后一個棧幀,運行完后將從上到下依次pop出棧。
一、createRoot()
接收(container, options),返回對應React中虛擬DOM對象,其中:
- container:其中我們在index.html入口文件選擇的真實DOM根節點。
- options:用于配置這個 React 根節點的對象。(基本用不到這個參數吧)
//createRoot()
function createRoot(container, options) {if (!isValidContainer(container)) {throw new Error('createRoot(...): Target container is not a DOM element.');}warnIfReactDOMContainerInDEV(container);var isStrictMode = false;var concurrentUpdatesByDefaultOverride = false;var identifierPrefix = '';var onRecoverableError = defaultOnRecoverableError;var transitionCallbacks = null;if (options !== null && options !== undefined) {{if (options.hydrate) {warn('hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.');} else {if (typeof options === 'object' && options !== null && options.$$typeof === REACT_ELEMENT_TYPE) {error('You passed a JSX element to createRoot. You probably meant to ' + 'call root.render instead. ' + 'Example usage:\n\n' + ' let root = createRoot(domContainer);\n' + ' root.render(<App />);');}}}if (options.unstable_strictMode === true) {isStrictMode = true;}if (options.identifierPrefix !== undefined) {identifierPrefix = options.identifierPrefix;}if (options.onRecoverableError !== undefined) {onRecoverableError = options.onRecoverableError;}if (options.transitionCallbacks !== undefined) {transitionCallbacks = options.transitionCallbacks;}}var root = createContainer(container, ConcurrentRoot, null, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError);markContainerAsRoot(root.current, container);var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container;listenToAllSupportedEvents(rootContainerElement);return new ReactDOMRoot(root);
}
二、createContainer()
function createContainer(containerInfo, tag, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, transitionCallbacks) {var hydrate = false;var initialChildren = null;return createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError);
}
三、createFiberRoot()
function createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, // TODO: We have several of these arguments that are conceptually part of the
// host config, but because they are passed in at runtime, we have to thread
// them through the root constructor. Perhaps we should put them all into a
// single type, like a DynamicHostConfig that is defined by the renderer.
identifierPrefix, onRecoverableError, transitionCallbacks) {var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);// stateNode is any.var uninitializedFiber = createHostRootFiber(tag, isStrictMode);root.current = uninitializedFiber;uninitializedFiber.stateNode = root;{var _initialState = {element: initialChildren,isDehydrated: hydrate,cache: null,// not enabled yettransitions: null,pendingSuspenseBoundaries: null};uninitializedFiber.memoizedState = _initialState;}initializeUpdateQueue(uninitializedFiber);return root;
}
四、createHostRootFiber()
function createHostRootFiber(tag, isStrictMode, concurrentUpdatesByDefaultOverride) {var mode;if (tag === ConcurrentRoot) {mode = ConcurrentMode;if (isStrictMode === true) {mode |= StrictLegacyMode;{mode |= StrictEffectsMode;}}} else {mode = NoMode;}if ( isDevToolsPresent) {// Always collect profile timings when DevTools are present.// This enables DevTools to start capturing timing at any point–// Without some nodes in the tree having empty base times.mode |= ProfileMode;}return createFiber(HostRoot, null, null, mode);
}
五、creteFiber()
var createFiber = function (tag, pendingProps, key, mode) {// $FlowFixMe: the shapes are exact here but Flow doesn't like constructorsreturn new FiberNode(tag, pendingProps, key, mode);
};