Open11
React のソースコードを読んでみる
とりあえず、EntryPointから見たほうが良い気がするので、EntryPointらしき react/packages/react/ を見てみる。
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}
const root = createRoot(document.getElementById('root'));
root.render(<Counter />);
Usage に、↑とあるので、createRootあたりを見てみる。
createRoot はこれ
さっそく、 react-dom パッケージに飛んだ。
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
if (__DEV__) {
Internals.usingClientEntryPoint = true;
}
try {
return createRootImpl(container, options);
} finally {
if (__DEV__) {
Internals.usingClientEntryPoint = false;
}
}
}
↑で使われている createRoot はここを経由して、最終的にはこれ↓
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
if (!isValidContainer(container)) {
throw new Error('createRoot(...): Target container is not a DOM element.');
}
warnIfReactDOMContainerInDEV(container);
let isStrictMode = false;
let concurrentUpdatesByDefaultOverride = false;
let identifierPrefix = '';
let onRecoverableError = defaultOnRecoverableError;
let transitionCallbacks = null;
if (options !== null && options !== undefined) {
if (__DEV__) {
if ((options: any).hydrate) {
console.warn(
'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.',
);
} else {
if (
typeof options === 'object' &&
options !== null &&
(options: any).$$typeof === REACT_ELEMENT_TYPE
) {
console.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 (
allowConcurrentByDefault &&
options.unstable_concurrentUpdatesByDefault === true
) {
concurrentUpdatesByDefaultOverride = true;
}
if (options.identifierPrefix !== undefined) {
identifierPrefix = options.identifierPrefix;
}
if (options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}
if (options.transitionCallbacks !== undefined) {
transitionCallbacks = options.transitionCallbacks;
}
}
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
markContainerAsRoot(root.current, container);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}
返り値は、ざっくりいうと
const root = createContainer(...)
return new ReactDomRoot(root);
というかんじ。
createContainer
は react-reconciler/src/ReactFiberReconciler
から import されている。
ここでReconcilerが出てきた。
new と old があるが、一旦newを見てみる。ここ
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
}
return createFiberRoot(...)
しているので、createFiberRootを見てみる
createFiberRootはこれ
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
// 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: string,
onRecoverableError: null | ((error: mixed) => void),
transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
if (enableTransitionTracing) {
root.transitionCallbacks = transitionCallbacks;
}
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
if (enableCache) {
const initialCache = createCache();
retainCache(initialCache);
// The pooledCache is a fresh cache instance that is used temporarily
// for newly mounted boundaries during a render. In general, the
// pooledCache is always cleared from the root at the end of a render:
// it is either released when render commits, or moved to an Offscreen
// component if rendering suspends. Because the lifetime of the pooled
// cache is distinct from the main memoizedState.cache, it must be
// retained separately.
root.pooledCache = initialCache;
retainCache(initialCache);
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: initialCache,
};
uninitializedFiber.memoizedState = initialState;
} else {
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
};
uninitializedFiber.memoizedState = initialState;
}
initializeUpdateQueue(uninitializedFiber);
return root;
}
今までの流れをまとめるとこんな感じ
- createRoot
- return new ReactDomRoot(createContainer)
- createContainer
- return createFiberRoot(...)
- createFiberRoot
- return new FiberRootNode(...)
最終的に↓で実行する
const root = createRoot(document.getElementById('root'));
root.render(<App />);
createRoot が変えすオブジェクトに render メソッドがないとおかしいのだけど、見当たらない。
const root = createRoot(document.getElementById('root'));
root.render(<App />);
createRootの返却値が RootType で render メソッドがあるので、実装されているはず。
export type RootType = {
render(children: ReactNodeList): void,
unmount(): void,
_internalRoot: FiberRoot | null,
...
};
... renderメソッドはこれな気がしてきた。prototypeの使い方がわからないけど
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(
children: ReactNodeList,
): void {
const root = this._internalRoot;
if (root === null) {
throw new Error('Cannot update an unmounted root.');
}
if (__DEV__) {
if (typeof arguments[1] === 'function') {
console.error(
'render(...): does not support the second callback argument. ' +
'To execute a side effect after rendering, declare it in a component body with useEffect().',
);
} else if (isValidContainer(arguments[1])) {
console.error(
'You passed a container to the second argument of root.render(...). ' +
"You don't need to pass it again since you already passed it to create the root.",
);
} else if (typeof arguments[1] !== 'undefined') {
console.error(
'You passed a second argument to root.render(...) but it only accepts ' +
'one argument.',
);
}
const container = root.containerInfo;
if (container.nodeType !== COMMENT_NODE) {
const hostInstance = findHostInstanceWithNoPortals(root.current);
if (hostInstance) {
if (hostInstance.parentNode !== container) {
console.error(
'render(...): It looks like the React-rendered content of the ' +
'root container was removed without using React. This is not ' +
'supported and will cause errors. Instead, call ' +
"root.unmount() to empty a root's container.",
);
}
}
}
}
updateContainer(children, root, null, null);
};
render の最後で実行されている function updateContainer が reconciler なのかな?
参考になりそう
これも理解の助けになりそう
自分の理解を書いてみました、よかったら、こちらご参考に