学习react源码首先从react入口函数开始学习,这篇文章你将学习到如何进行调试代码和学会react render执行流程。
调试代码
这里只是进行简单的流程介绍,具体的可以看卡颂老师的拉取源码教程
# 拉取代码
git clone https://github.com/facebook/react.git
# 执行打包命令
yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE
# 通过yarn link改变依赖包指向
cd build/node_modules/react
yarn link
cd build/node_modules/react-dom
yarn link
# 通过create-react-app创建应用后
yarn link react react-dom
render
render函数是执行react同步渲染的入口函数,在react18之前还是使用这个函数作为主入口。
render 接受三个参数,第一个参数是ReactElement,第二个参数为组件所要挂载的DOM节点,第三个参数为回调函数。
// 调用
ReactDOM.render(<App />, document.getElementById("root"));
// 源码
function render(element, container, callback) {
// 判断是否是有效dom节点
if (!isValidContainer(container)) {
//省略
}
{
// 是否已经是根节点
var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;
// 下面是抛出错误
}
// 挂载节点
return legacyRenderSubtreeIntoContainer(null, element, //jsx根节点
container, //root节点
false, callback //回调
);
}
var ELEMENT_NODE = 1;//元素节点
var TEXT_NODE = 3;//文本节点
var COMMENT_NODE = 8;//注释节点
var DOCUMENT_NODE = 9;//文档,DOM树根节点
var DOCUMENT_FRAGMENT_NODE = 11;//节点片段
legacyRenderSubtreeIntoContainer
react-dom\src\client\ReactDOMLegacy.js
function legacyRenderSubtreeIntoContainer(
parentComponent: ? React$Component <any, any> ,// 父节点
children : ReactNodeList,// <App />
container: Container, // div#root
forceHydrate: boolean, //服务端渲染
callback: ? Function, // 回调
) {
// 开发环境标识__DEV__
if (__DEV__) {
// 判断根节点是否符合规则
topLevelUpdateWarnings(container);
// 存在回调函数的话,验证是否是函数类型
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}
// _reactRootContainer指向根节点
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 进入scheduled阶段,render没有
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 存在root,更新dom使用
}
return getPublicRootInstance(fiberRoot);
}
legacyCreateRootFromDOMContainer
创建container为react挂载根节点
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// 是否服务端渲染相关
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
// ...判断节点属性
// root移除lastChild
container.removeChild(rootSibling);
}
}
return createLegacyRoot(
container,
shouldHydrate ? {
hydrate: true,
} :
undefined,
);
}
createLegacyRoot
createLegacyRoot在react-dom\src\client\ReactDOMRoot.js
createLegacyRoot函数流程
- 返回ReactDOMBlockingRoot实例
- ReactDOMBlockingRoot实例_internalRoot等于new createRootImpl
// react-reconciler\src\ReactRootTags.js
// 标记是那个模式下,createLegacyRoot函数内使用
export const LegacyRoot = 0;
export const BlockingRoot = 1;
export const ConcurrentRoot = 2;
createRootImpl
都在react-reconciler目录下
createContainer主要步骤:
- createContainer -> 返回createFiberRoot实例
- createFiberRoot:
- 调用FiberRootNode把div#root创建成fiber节点
- createHostRootFiber创建空的fiber节点,把root.current执行空Fiber节点
- initializeUpdateQueue创建更新的循环队列
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// Tag is either LegacyRoot or Concurrent Root
// ...
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 在dom节点添加属性执行root的fiber节点
markContainerAsRoot(root.current, container);
const containerNodeType = container.nodeType;
if (hydrate && tag !== LegacyRoot) {
// 服务端相关
} else if (
containerNodeType !== DOCUMENT_FRAGMENT_NODE &&
containerNodeType !== DOCUMENT_NODE
) {
// 添加事件监听
ensureListeningTo(container, 'onMouseEnter');
}
return root;
}
getPublicRootInstance
legacyRenderSubtreeIntoContainer最后执行返回的函数
- 获取当前 fiber 节点,即 rootFiber;
- rootFiber 还没有子节点,所以返回 null;
- 其他情况,返回 containerFiber.child.stateNode子节点的实例
export function getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
}
总结
- render只是入口函数,render会调用legacyRenderSubtreeIntoContainer进行挂载和更新
- 创建Fiber节点的函数是createRootImpl
- 执行updateContainer的时候是render和commit阶段入口函数
- 主要阶段:创建fiber -> 进行scheduled阶段 -> render阶段 -> commit阶段 -> 最后回到入口函数