1. Configure React source code local debugging environment
Use create-react-app scaffolding to create projects
npx create-react-app react-test
Ejection create-react-app scaffolding internal configuration
npm run eject
Clone the official source code of react (clone in the root directory of the project)
git clone --branch v16.13.1 --depth=1 https://github.com/facebook/react.git src/react
Link local source code
// 文件位置: react-test/config/webpack.config.js resolve: { alias: { "react-native": "react-native-web", "react": path.resolve(__dirname, "../src/react/packages/react"), "react-dom": path.resolve(__dirname, "../src/react/packages/react-dom"), "shared": path.resolve(__dirname, "../src/react/packages/shared"), "react-reconciler": path.resolve(__dirname, "../src/react/packages/react-reconciler"), "legacy-events": path.resolve(__dirname, "../src/react/packages/legacy-events") } }
Modify environment variables
// 文件位置: react-test/config/env.js const stringified = { "process.env": Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]) return env }, {}), __DEV__: true, SharedArrayBuffer: true, spyOnDev: true, spyOnDevAndProd: true, spyOnProd: true, __PROFILE__: true, __UMD__: true, __EXPERIMENTAL__: true, __VARIANT__: true, gate: true, trustedTypes: true }
Tell babel to ignore type checking when converting code
npm install @babel/plugin-transform-flow-strip-types -D
// 文件位置: react-test/config/webpack.config.js [babel-loader] plugins: [ require.resolve("@babel/plugin-transform-flow-strip-types"), ]
Export HostConfig
// 文件位置: /react/packages/react-reconciler/src/ReactFiberHostConfig.js + export * from './forks/ReactFiberHostConfig.dom'; - invariant(false, 'This module must be shimmed by a specific renderer.');
Modify the ReactSharedInternals.js file
// 文件位置: /react/packages/shared/ReactSharedInternals.js - import * as React from 'react'; - const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + import ReactSharedInternals from '../react/src/ReactSharedInternals';
Close eslint extension
// 文件位置: react/.eslingrc.js [module.exports] // 删除 extends extends: [ 'fbjs', 'prettier' ]
Prohibit invariant reporting errors
// 文件位置: /react/packages/shared/invariant.js export default function invariant(condition, format, a, b, c, d, e, f) { if (condition) return; throw new Error( 'Internal React error: invariant() is meant to be replaced at compile ' + 'time. There is no runtime version.', ); }
eslint configuration
Create a new .eslintrc.json in the react source code folder and add the following configuration
{ "extends": "react-app", "globals": { "SharedArrayBuffer": true, "spyOnDev": true, "spyOnDevAndProd": true, "spyOnProd": true, "__PROFILE__": true, "__UMD__": true, "__EXPERIMENTAL__": true, "__VARIANT__": true, "gate": true, "trustedTypes": true } }
Modify the introduction method of react react-dom
import * as React from "react" import * as ReactDOM from "react-dom"
Solve flow errors in vsCode
"javascript.validate.enable": false
Optional configuration
If your vscode editor installs the prettier plugin and the following error appears in the lower right corner when saving the react source file, follow the steps below to solve
<img src="./images/1.png" width="60%" />
Install prettier globally
npm i prettier -g
Configure prettier path
Settings > Extensions > Prettier > Prettier path
<img src="./images/2.png" width="80%" />
\_\_DEV\_\_ report an error
Delete the node_modules folder and execute npm install
2. Create React Elements
JSX is compiled by Babel into a call to the React.createElement method. After the call, the createElement method returns ReactElement, which is virtualDOM.
2.1 createElement
file location: packages/react/src/ReactElement.js
/**
* 创建 React Element
* type 元素类型
* config 配置属性
* children 子元素
* 1. 分离 props 属性和特殊属性
* 2. 将子元素挂载到 props.children 中
* 3. 为 props 属性赋默认值 (defaultProps)
* 4. 创建并返回 ReactElement
*/
export function createElement(type, config, children) {
/**
* propName -> 属性名称
* 用于后面的 for 循环
*/
let propName;
/**
* 存储 React Element 中的普通元素属性 即不包含 key ref self source
*/
const props = {};
/**
* 待提取属性
* React 内部为了实现某些功能而存在的属性
*/
let key = null;
let ref = null;
let self = null;
let source = null;
// 如果 config 不为 null
if (config != null) {
// 如果 config 对象中有合法的 ref 属性
if (hasValidRef(config)) {
// 将 config.ref 属性提取到 ref 变量中
ref = config.ref;
// 在开发环境中
if (__DEV__) {
// 如果 ref 属性的值被设置成了字符串形式就报一个提示
// 说明此用法在将来的版本中会被删除
warnIfStringRefCannotBeAutoConverted(config);
}
}
// 如果在 config 对象中拥有合法的 key 属性
if (hasValidKey(config)) {
// 将 config.key 属性中的值提取到 key 变量中
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 遍历 config 对象
for (propName in config) {
// 如果当前遍历到的属性是对象自身属性
// 并且在 RESERVED_PROPS 对象中不存在该属性
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
// 将满足条件的属性添加到 props 对象中 (普通属性)
props[propName] = config[propName];
}
}
}
/**
* 将第三个及之后的参数挂载到 props.children 属性中
* 如果子元素是多个 props.children 是数组
* 如果子元素是一个 props.children 是对象
*/
// 由于从第三个参数开始及以后都表示子元素
// 所以减去前两个参数的结果就是子元素的数量
const childrenLength = arguments.length - 2;
// 如果子元素的数量是 1
if (childrenLength === 1) {
// 直接将子元素挂载到到 props.children 属性上
// 此时 children 是对象类型
props.children = children;
// 如果子元素的数量大于 1
} else if (childrenLength > 1) {
// 创建数组, 数组中元素的数量等于子元素的数量
const childArray = Array(childrenLength);
// 开启循环 循环次匹配子元素的数量
for (let i = 0; i < childrenLength; i++) {
// 将子元素添加到 childArray 数组中
// i + 2 的原因是实参集合的前两个参数不是子元素
childArray[i] = arguments[i + 2];
}
// 如果是开发环境
if (__DEV__) {
// 如果 Object 对象中存在 freeze 方法
if (Object.freeze) {
// 调用 freeze 方法 冻结 childArray 数组
// 防止 React 核心对象被修改 冻结对象提高性能
Object.freeze(childArray);
}
}
// 将子元素数组挂载到 props.children 属性中
props.children = childArray;
}
/**
* 如果当前处理是组件
* 看组件身上是否有 defaultProps 属性
* 这个属性中存储的是 props 对象中属性的默认值
* 遍历 defaultProps 对象 查看对应的 props 属性的值是否为 undefined
* 如果为undefined 就将默认值赋值给对应的 props 属性值
*/
// 将 type 属性值视为函数 查看其中是否具有 defaultProps 属性
if (type && type.defaultProps) {
// 将 type 函数下的 defaultProps 属性赋值给 defaultProps 变量
const defaultProps = type.defaultProps;
// 遍历 defaultProps 对象中的属性 将属性名称赋值给 propName 变量
for (propName in defaultProps) {
// 如果 props 对象中的该属性的值为 undefined
if (props[propName] === undefined) {
// 将 defaultProps 对象中的对应属性的值赋值给 props 对象中的对应属性的值
props[propName] = defaultProps[propName];
}
}
}
/**
* 在开发环境中 如果元素的 key 属性 或者 ref 属性存在
* 监测开发者是否在组件内部通过 props 对象获取了 key 属性或者 ref 属性
* 如果获取了 就报错
*/
// 如果处于开发环境
if (__DEV__) {
// 元素具有 key 属性或者 ref 属性
if (key || ref) {
// 看一下 type 属性中存储的是否是函数 如果是函数就表示当前元素是组件
// 如果元素不是组件 就直接返回元素类型字符串
// displayName 用于在报错过程中显示是哪一个组件报错了
// 如果开发者显式定义了 displayName 属性 就显示开发者定义的
// 否者就显示组件名称 如果组件也没有名称 就显示 'Unknown'
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
// 如果 key 属性存在
if (key) {
// 为 props 对象添加key 属性
// 并指定当通过 props 对象获取 key 属性时报错
defineKeyPropWarningGetter(props, displayName);
}
// 如果 ref 属性存在
if (ref) {
// 为 props 对象添加 ref 属性
// 并指定当通过 props 对象获取 ref 属性时报错
defineRefPropWarningGetter(props, displayName);
}
}
}
// 返回 ReactElement
return ReactElement(
type,
key,
ref,
self,
source,
// 在 Virtual DOM 中用于识别自定义组件
ReactCurrentOwner.current,
props,
);
}
2.2 ReactElement
file location: packages/react/src/ReactElement.js
/**
* 接收参数 返回 ReactElement
*/
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
/**
* 组件的类型, 十六进制数值或者 Symbol 值
* React 在最终在渲染 DOM 的时候, 需要确保元素的类型是 REACT_ELEMENT_TYPE
* 需要此属性作为判断的依据
*/
$$typeof: REACT_ELEMENT_TYPE,
/**
* 元素具体的类型值 如果是元素节点 type 属性中存储的就是 div span 等等
* 如果元素是组件 type 属性中存储的就是组件的构造函数
*/
type: type,
/**
* 元素的唯一标识
* 用作内部 vdom 比对 提升 DOM 操作性能
*/
key: key,
/**
* 存储元素 DOM 对象或者组件 实例对象
*/
ref: ref,
/**
* 存储向组件内部传递的数据
*/
props: props,
/**
* 记录当前元素所属组件 (记录当前元素是哪个组件创建的)
*/
_owner: owner,
};
// 返回 ReactElement
return element;
};
2.3 hasValidRef
file location: packages/react/src/ReactElement.js
/**
* 查看参数对象中是否有合法的 ref 属性
* 返回布尔值
*/
function hasValidRef(config) {
return config.ref !== undefined;
}
2.4 hasValidKey
file location: packages/react/src/ReactElement.js
/**
* 查看参数对象中是否有合法的 key 属性
* 返回布尔值
*/
function hasValidKey(config) {
return config.key !== undefined;
}
2.5 isValidElement
file location: packages/react/src/ReactElement.js
/**
* 验证 object 参数是否是 ReactElement. 返回布尔值
* 验证成功的条件:
* object 是对象
* object 不为null
* object对象中的 $$typeof 属性值为 REACT_ELEMENT_TYPE
*/
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
2.6 defineKeyPropWarningGetter
file location: packages/react/src/ReactElement.js
/**
* 指定当通过 props 对象获取 key 属性时报错
* props 组件中的 props 对象
* displayName 组件名称标识
*/
function defineKeyPropWarningGetter(props, displayName) {
// 通过 props 对象获取 key 属性报错
const warnAboutAccessingKey = function () {
// 在开发环境中
if (__DEV__) {
// specialPropKeyWarningShown 控制错误只输出一次的变量
if (!specialPropKeyWarningShown) {
// 通过 specialPropKeyWarningShown 变量锁住判断条件
specialPropKeyWarningShown = true;
// 指定报错信息和组件名称
console.error(
'%s: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://reactjs.org/link/special-props)',
displayName,
);
}
}
};
warnAboutAccessingKey.isReactWarning = true;
// 为 props 对象添加 key 属性
Object.defineProperty(props, 'key', {
// 当获取 key 属性时调用 warnAboutAccessingKey 方法进行报错
get: warnAboutAccessingKey,
configurable: true,
});
}
2.7 defineRefPropWarningGetter
file location: packages/react/src/ReactElement.js
/**
* 指定当通过 props 对象获取 ref 属性时报错
* props 组件中的 props 对象
* displayName 组件名称标识
*/
function defineRefPropWarningGetter(props, displayName) {
// 通过 props 对象获取 ref 属性报错
const warnAboutAccessingRef = function () {
if (__DEV__) {
// specialPropRefWarningShown 控制错误只输出一次的变量
if (!specialPropRefWarningShown) {
// 通过 specialPropRefWarningShown 变量锁住判断条件
specialPropRefWarningShown = true;
// 指定报错信息和组件名称
console.error(
'%s: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://reactjs.org/link/special-props)',
displayName,
);
}
}
};
warnAboutAccessingRef.isReactWarning = true;
// 为 props 对象添加 key 属性
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true,
});
}
3. React architecture
The architecture of React 16 can be divided into three layers: scheduling layer, coordination layer, and rendering layer.
- Scheduler (scheduling layer): priority of scheduling tasks, high-quality tasks enter the coordinator first
- Reconciler (coordination layer): Build Fiber data structure, compare Fiber objects to find differences, and record DOM operations to be performed by Fiber objects
- Renderer (rendering layer): responsible for rendering the changed part to the page
3.1 Scheduler scheduling layer
In the React 15 version, a loop plus recursion method is used to compare the virtualDOM. Because recursion uses JavaScript's own execution stack, once it starts, it cannot be stopped until the task execution is completed. If the level of the VirtualDOM tree is relatively deep, the comparison of virtualDOM will occupy the main JavaScript thread for a long time. Because JavaScript is single-threaded and cannot perform other tasks at the same time, it cannot respond to user operations during the comparison and cannot perform element animation immediately. , Causing the page to freeze.
In the React 16 version, the JavaScript recursive method for virtualDOM comparison is abandoned, but a loop simulation recursion is used. Moreover, the comparison process is completed by using the idle time of the browser, and will not occupy the main thread for a long time, which solves the problem of page freezing caused by virtualDOM comparison.
The requestIdleCallback API is provided in the window object, which can use the browser’s free time to perform tasks, but it also has some problems. For example, not all browsers support it, and its trigger frequency is not very stable. So React finally abandoned the use of requestIdleCallback.
In React, the official implementation of its own task scheduling library, this library is called Scheduler. It can also perform tasks when the browser is idle, and can also set the priority of tasks, with high priority tasks being executed first, and low priority tasks being executed later.
The Scheduler is stored in the packages/scheduler
folder.
3.2 Reconciler coordination layer
In the version of React 15, the coordinator and the renderer are executed alternately, that is, the difference is directly updated when the difference is found. In the version of React 16, this situation has changed, and the coordinator and renderer are no longer alternately executed. The coordinator is responsible for finding the differences. After all the differences are found, they will be handed over to the renderer to update the DOM. In other words, the main task of the coordinator is to find the difference and mark the difference.
3.3 Renderer rendering layer
The renderer synchronously executes the corresponding DOM operation according to the mark marked by the coordinator for the Fiber node.
Since the comparison process has changed from recursion to an interruptible loop, how does React solve the problem of incomplete DOM rendering when the update is interrupted?
In fact, this problem does not exist at all, because in the entire process, the work of the scheduler and the coordinator is completed in memory and can be interrupted, and the work of the renderer is set to not be interrupted, so it is not There is a problem of incomplete DOM rendering.
4. Data Structure
4.1 Fiber
type Fiber = {
/************************ DOM 实例相关 *****************************/
// 标记不同的组件类型, 值详见 WorkTag
tag: WorkTag,
// 组件类型 div、span、组件构造函数
type: any,
// 实例对象, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 因此该属性是空
stateNode: any,
/************************ 构建 Fiber 树相关 ***************************/
// 指向自己的父级 Fiber 对象
return: Fiber | null,
// 指向自己的第一个子级 Fiber 对象
child: Fiber | null,
// 指向自己的下一个兄弟 iber 对象
sibling: Fiber | null,
// 在 Fiber 树更新的过程中,每个 Fiber 都会有一个跟其对应的 Fiber
// 我们称他为 current <==> workInProgress
// 在渲染完成之后他们会交换位置
// alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber
alternate: Fiber | null,
/************************ 状态数据相关 ********************************/
// 即将更新的 props
pendingProps: any,
// 旧的 props
memoizedProps: any,
// 旧的 state
memoizedState: any,
/************************ 副作用相关 ******************************/
// 该 Fiber 对应的组件产生的状态更新会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,
// 用来记录当前 Fiber 要执行的 DOM 操作
effectTag: SideEffectTag,
// 存储要执行的 DOM 操作
firstEffect: Fiber | null,
// 单链表用来快速查找下一个 side effect
nextEffect: Fiber | null,
// 存储 DOM 操作完后的副租用 比如调用生命周期函数或者钩子函数的调用
lastEffect: Fiber | null,
// 任务的过期时间
expirationTime: ExpirationTime,
// 当前组件及子组件处于何种渲染模式 详见 TypeOfMode
mode: TypeOfMode,
};
<img src="./images/6.png"/>
4.2 WorkTag
file location: packages/shared/ReactWorkTags.js
type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22;
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostPortal = 4;
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;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
4.3 TypeOfMode
file location: packages/react-reconciler/src/ReactTypeOfMode.js
export type TypeOfMode = number;
// 0 同步渲染模式
export const NoMode = 0b0000;
// 1 严格模式
export const StrictMode = 0b0001;
// 10 异步渲染过渡模式
export const BlockingMode = 0b0010;
// 100 异步渲染模式
export const ConcurrentMode = 0b0100;
// 1000 性能测试模式
export const ProfileMode = 0b1000;
4.3 SideEffectTag
file location: packages/shared/ReactSideEffectTags.js
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b0000000000000; // 0
export const PerformedWork = /* */ 0b0000000000001; // 1
// You can change the rest (and add more).
export const Placement = /* */ 0b0000000000010; // 2
export const Update = /* */ 0b0000000000100; // 4
export const PlacementAndUpdate = /* */ 0b0000000000110; // 6
export const Deletion = /* */ 0b0000000001000; // 8
export const ContentReset = /* */ 0b0000000010000; // 16
export const Callback = /* */ 0b0000000100000; // 32
export const DidCapture = /* */ 0b0000001000000; // 64
export const Ref = /* */ 0b0000010000000; // 128
export const Snapshot = /* */ 0b0000100000000; // 256
export const Passive = /* */ 0b0001000000000; // 512
export const Hydrating = /* */ 0b0010000000000; // 1024
export const HydratingAndUpdate = /* */ 0b0010000000100; // 1028
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b0001110100100; // 932
// Union of all host effects
export const HostEffectMask = /* */ 0b0011111111111; // 2047
export const Incomplete = /* */ 0b0100000000000; // 2048
export const ShouldCapture = /* */ 0b1000000000000; // 4096
4.4 Update
let update: Update<*> = {
expirationTime,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: (null: any),
};
4.5 UpdateQueue
const queue: <State> = {
// 上一次更新之后的 state, 作为下一次更新的基础
baseState: fiber.memoizedState,
baseQueue: null,
shared: {
pending: null,
},
effects: null,
}
fiber.updateQueue = queue;
4.6 RootTag
file location: packages/shared/ReactRootTags.js
export type RootTag = 0 | 1 | 2;
// ReactDOM.render
export const LegacyRoot = 0;
// ReactDOM.createBlockingRoot
export const BlockingRoot = 1;
// ReactDOM.createRoot
export const ConcurrentRoot = 2;
4.7 Double buffer technology
In React, DOM updates use double cache technology, which is dedicated to faster DOM updates.
What is double buffering? For example, when using canvas to draw an animation, the previous frame will be cleared before each frame is drawn, and it will take time to clear the previous frame. If the calculation of the current frame is relatively large, it will take a relatively long time. This results in a long gap between the previous frame being cleared to the next frame, and a white screen will appear.
In order to solve this problem, we can draw the current frame of animation in the memory, and directly replace the previous frame with the current frame after drawing. In this way, a lot of time will be saved in the process of frame replacement, and no white space will appear. Screen problem. This technique of building in memory and replacing it directly is called double buffering.
React uses double-buffer technology to complete the construction and replacement of the Fiber tree to achieve rapid update of DOM objects.
At most two Fiber trees exist in React at the same time. The Fiber tree corresponding to the content currently displayed on the screen is called the current Fiber tree. When an update occurs, React will rebuild a new Fiber tree in memory. The constructed Fiber tree is called workInProgress Fiber tree. In the double-buffer technology, the workInProgress Fiber tree is the Fiber tree that will be displayed on the page. When the Fiber tree is built, React will use it to directly replace the current Fiber tree to achieve the purpose of quickly updating the DOM, because the workInProgress Fiber tree is It is built in memory so the speed of building it is very fast.
Once the workInProgress Fiber tree is presented on the screen, it will become the current Fiber tree.
There is an alternate attribute in the current Fiber node object that points to the corresponding workInProgress Fiber node object, and an alternate attribute in the workInProgress Fiber node also points to the corresponding current Fiber node object.
<img src="./images/3.png" width="40%"/>
<img src="./images/4.png" width="40%"/>
4.8 Distinguish between fiberRoot and rootFiber
fiberRoot represents the fiber data structure object, which is the outermost object in the fiber data structure
rootFiber represents the Fiber object corresponding to the component mount point. For example, the default component mount point in a React application is the div whose id is root
fiberRoot contains rootFiber, there is a current attribute in the fiberRoot object, which stores rootFiber
rootFiber points to fiberRoot, there is a stateNode property in the rootFiber object, which points to fiberRoot
In React applications, there is only one FiberRoot, but rootFiber can have multiple, because the render method can be called multiple times
The fiberRoot will record the update information of the application. For example, after the coordinator completes the work, it will store the work results in the fiberRoot.
<img src="./images/7.png" width="90%" />
5. Initial rendering
To render React elements into the page, it is divided into two stages, the render stage and the commit stage.
The render phase is responsible for creating the Fiber data structure and marking the Fiber node, marking the DOM operation to be performed on the current Fiber node.
The commit phase is responsible for performing corresponding DOM operations based on the Fiber node tag (effectTag).
5.1 render phase
5.1.1 render
file location: packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 渲染入口
* element 要进行渲染的 ReactElement, createElement 方法的返回值
* container 渲染容器 <div id="root"></div>
* callback 渲染完成后执行的回调函数
*/
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
// 检测 container 是否是符合要求的渲染容器
// 即检测 container 是否是真实的DOM对象
// 如果不符合要求就报错
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
return legacyRenderSubtreeIntoContainer(
// 父组件 初始渲染没有父组件 传递 null 占位
null,
element,
container,
// 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染
false,
callback,
);
}
5.1.2 isValidContainer
file location: packages/react-dom/src/client/ReactDOMRoot.js
/**
* 判断 node 是否是符合要求的 DOM 节点
* 1. node 可以是元素节点
* 2. node 可以是 document 节点
* 3. node 可以是 文档碎片节点
* 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable
* react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法, 将 element 插入到注释节点的前面
*/
export function isValidContainer(node: mixed): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
5.1.3 Initialize FiberRoot
5.1.3.1 legacyRenderSubtreeIntoContainer
File location: packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
* parentComponent: 父组件, 初始渲染传入了 null
* children: render 方法中的第一个参数, 要渲染的 ReactElement
* container: 渲染容器
* forceHydrate: true 为服务端渲染, false 为客户端渲染
* callback: 组件渲染完成后需要执行的回调函数
**/
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
/**
* 检测 container 是否已经是初始化过的渲染容器
* react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
* react 会根据此属性进行不同的渲染方式
* root 不存在 表示初始渲染
* root 存在 表示更新
*/
// 获取 container 容器对象下是否有 _reactRootContainer 属性
let root: RootType = (container._reactRootContainer: any);
// 即将存储根 Fiber 对象
let fiberRoot;
if (!root) {
// 初始渲染
// 初始化根 Fiber 数据结构
// 为 container 容器添加 _reactRootContainer 属性
// 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
// _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构
// legacyCreateRootFromDOMContainer
// createLegacyRoot
// new ReactDOMBlockingRoot -> this._internalRoot
// createRootImpl
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 获取 Fiber Root 对象
fiberRoot = root._internalRoot;
/**
* 改变 callback 函数中的 this 指向
* 使其指向 render 方法第一个参数的真实 DOM 对象
*/
// 如果 callback 参数是函数类型
if (typeof callback === 'function') {
// 使用 originalCallback 存储 callback 函数
const originalCallback = callback;
// 为 callback 参数重新赋值
callback = function () {
// 获取 render 方法第一个参数的真实 DOM 对象
// 实际上就是 id="root" 的 div 的子元素
// rootFiber.child.stateNode
// rootFiber 就是 id="root" 的 div
const instance = getPublicRootInstance(fiberRoot);
// 调用 callback 函数并改变函数内部 this 指向
originalCallback.call(instance);
};
}
// 初始化渲染不执行批量更新
// 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断
// 所以不执行批量更新
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 非初始化渲染 即更新
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
// 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
// 就是说渲染谁 返回谁的真实 DOM 对象
return getPublicRootInstance(fiberRoot);
}
<img src="./images/5.png" width="80%" />
5.1.3.2 legacyCreateRootFromDOMContainer
file location: packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 判断是否为服务器端渲染 如果不是服务器端渲染
* 清空 container 容器中的节点
*/
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// container => <div id="root"></div>
// 检测是否为服务器端渲染
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 如果不是服务器端渲染
if (!shouldHydrate) {
let rootSibling;
// 开启循环 删除 container 容器中的节点
while ((rootSibling = container.lastChild)) {
// 删除 container 容器中的节点
container.removeChild(rootSibling);
/**
* 为什么要清除 container 中的元素 ?
* 为提供首屏加载的用户体验, 有时需要在 container 中放置一些占位图或者 loading 图
* 就无可避免的要向 container 中加入 html 标记.
* 在将 ReactElement 渲染到 container 之前, 必然要先清空 container
* 因为占位图和 ReactElement 不能同时显示
*
* 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能
* <div>
* <p>placement<p>
* <p>placement<p>
* <p>placement<p>
* </div>
*/
}
}
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
5.1.3.3 createLegacyRoot
file location: packages/react-dom/src/client/ReactDOMRoot.js
/**
* 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot
*/
export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
// container => <div id="root"></div>
// LegacyRoot 常量, 值为 0,
// 通过 render 方法创建的 container 就是 LegacyRoot
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
5.1.3.3 ReactDOMBlockingRoot
File location: packages/react-dom/src/client/ReactDOMRoot.js
/**
* 类, 通过它可以创建 LegacyRoot 的 Fiber 数据结构
*/
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// tag => 0 => legacyRoot
// container => <div id="root"></div>
// container._reactRootContainer = {_internalRoot: {}}
this._internalRoot = createRootImpl(container, tag, options);
}
5.1.3.4 createRootImpl
File location: packages/react-dom/src/client/ReactDOMRoot.js
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// container => <div id="root"></div>
// tag => 0
// options => undefined
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
return root;
}
5.1.3.5 createContainer
File location: packages/react-reconciler/src/ReactFiberReconciler.js
// 创建 container
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
// containerInfo => <div id="root"></div>
// tag: 0
// hydrate: false
// hydrationCallbacks: null
// 忽略了和服务器端渲染相关的内容
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
5.1.3.6 createFiberRoot
file location: packages/react-reconciler/src/ReactFiberRoot.js
// 创建根节点对应的 fiber 对象
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建 FiberRoot
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建根节点对应的 rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 为 fiberRoot 添加 current 属性 值为 rootFiber
root.current = uninitializedFiber;
// 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
uninitializedFiber.stateNode = root;
// 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
// updateQueue 用于存放 Update 对象
// Update 对象用于记录组件状态的改变
initializeUpdateQueue(uninitializedFiber);
// 返回 root
return root;
}
5.1.3.7 FiberRootNode
file location: packages/react-reconciler/src/ReactFiberRoot.js
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.current = null;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
this.callbackPriority = NoPriority;
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}
5.3.1.8 initializeUpdateQueue
File location: packages/react-reconciler/src/ReactFiberRoot.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
baseQueue: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
5.1.4 Get the rootFiber.child instance object
5.1.4.1 getPublicRootInstance
File location: packages/react-reconciler/src/ReactFiberReconciler.js
/**
* 获取 container 的第一个子元素的实例对象
*/
export function getPublicRootInstance(
// FiberRoot
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
// 获取 rootFiber
const containerFiber = container.current;
// 如果 rootFiber 没有子元素
// 指的就是 id="root" 的 div 没有子元素
if (!containerFiber.child) {
// 返回 null
return null;
}
// 匹配子元素的类型
switch (containerFiber.child.tag) {
// 普通
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
// 返回子元素的真实 DOM 对象
return containerFiber.child.stateNode;
}
}
5.1.4.2 getPublicInstance
File location: packages/react-dom/src/client/ReactDOMHostConfig.js
export function getPublicInstance(instance: Instance): * {
return instance;
}
5.1.5 updateContainer
file location: packages/react-reconciler/src/ReactFiberReconciler.js
/**
* 计算任务的过期时间
* 再根据任务过期时间创建 Update 任务
*/
export function updateContainer(
// element 要渲染的 ReactElement
element: ReactNodeList,
// container Fiber Root 对象
container: OpaqueRoot,
// parentComponent 父组件 初始渲染为 null
parentComponent: ?React$Component<any, any>,
// ReactElement 渲染完成执行的回调函数
callback: ?Function,
): ExpirationTime {
// container 获取 rootFiber
const current = container.current;
// 获取当前距离 react 应用初始化的时间 1073741805
const currentTime = requestCurrentTimeForUpdate();
// 异步加载设置
const suspenseConfig = requestCurrentSuspenseConfig();
// 计算过期时间
// 为防止任务因为优先级的原因一直被打断而未能执行
// react 会设置一个过期时间, 当时间到了过期时间的时候
// 如果任务还未执行的话, react 将会强制执行该任务
// 初始化渲染时, 任务同步执行不涉及被打断的问题 1073741823
const expirationTime = computeExpirationForFiber(
currentTime,
current,
suspenseConfig,
);
// 设置FiberRoot.context, 首次执行返回一个emptyContext, 是一个 {}
const context = getContextForSubtree(parentComponent);
// 初始渲染时 Fiber Root 对象中的 context 属性值为 null
// 所以会进入到 if 中
if (container.context === null) {
// 初始渲染时将 context 属性值设置为 {}
container.context = context;
} else {
container.pendingContext = context;
}
// 创建一个待执行任务
const update = createUpdate(expirationTime, suspenseConfig);
// 将要更新的内容挂载到更新对象中的 payload 中
// 将要更新的组件存储在 payload 对象中, 方便后期获取
update.payload = {element};
// 判断 callback 是否存在
callback = callback === undefined ? null : callback;
// 如果 callback 存在
if (callback !== null) {
// 将 callback 挂载到 update 对象中
// 其实就是一层层传递 方便 ReactElement 元素渲染完成调用
// 回调函数执行完成后会被清除 可以在代码的后面加上 return 进行验证
update.callback = callback;
}
// 将 update 对象加入到当前 Fiber 的更新队列当中 (updateQueue)
enqueueUpdate(current, update);
// 调度和更新 current 对象
scheduleWork(current, expirationTime);
// 返回过期时间
return expirationTime;
}
5.1.6 enqueueUpdate
File location: packages/react-reconciler/src/ReactUpdateQueue.js
// 将任务(Update)存放于任务队列(updateQueue)中
// 创建单向链表结构存放 update, next 用来串联 update
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// 获取当前 Fiber 的 更新队列
const updateQueue = fiber.updateQueue;
// 如果更新队列不存在 就返回 null
if (updateQueue === null) {
// 仅发生在 fiber 已经被卸载
return;
}
// 获取待执行的 Update 任务
// 初始渲染时没有待执行的任务
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
// 如果没有待执行的 Update 任务
if (pending === null) {
// 这是第一次更新, 创建一个循环列表.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
// 将 Update 任务存储在 pending 属性中
sharedQueue.pending = update;
}
5.1.7 scheduleUpdateOnFiber
file location: packages/react-reconciler/src/ReactFiberWorkLoop.js
/**
* 判断任务是否为同步 调用同步任务入口
*/
export function scheduleUpdateOnFiber(
// rootFiber
fiber: Fiber,
expirationTime: ExpirationTime,
) {
/**
* fiber: 初始化渲染时为 rootFiber, 即 <div id="root"></div> 对应的 Fiber 对象
* expirationTime: 任务过期时间 =>1073741823
*/
/**
* 判断是否是无限循环的 update 如果是就报错
* 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用
* setState 方法时, 可能会发生这种情况, React 限制了嵌套更新的数量以防止无限循环
* 限制的嵌套更新数量为 50, 可通过 NESTED_UPDATE_LIMIT 全局变量获取
*/
checkForNestedUpdates();
// 判断任务是否是同步任务 Sync的值为: 1073741823
if (expirationTime === Sync) {
if (
// 检查是否处于非批量更新模式
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// 检查是否没有处于正在进行渲染的任务
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 同步任务入口点
performSyncWorkOnRoot(root);
}
// 忽略了一些初始化渲染不会得到执行的代码
}
5.1.8 Constructing Fiber Objects
5.1.8.1 performSyncWorkOnRoot
File location: packages/react-reconciler/src/ReactFiberWorkLoop.js
// 进入 render 阶段, 构建 workInProgress Fiber 树
function performSyncWorkOnRoot(root) {
// 参数 root 为 fiberRoot 对象
// 检查是否有过期的任务
// 如果没有过期的任务 值为 0
// 初始化渲染没有过期的任务待执行
const lastExpiredTime = root.lastExpiredTime;
// NoWork 值为 0
// 如果有过期的任务 将过期时间设置为 lastExpiredTime 否则将过期时间设置为 Sync
// 初始渲染过期时间被设置成了 Sync
const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
// 如果 root 和 workInProgressRoot 不相等
// 说明 workInProgressRoot 不存在, 说明还没有构建 workInProgress Fiber 树
// workInProgressRoot 为全局变量 默认值为 null, 初始渲染时值为 null
// expirationTime => 1073741823
// renderExpirationTime => 0
// true
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
// 构建 workInProgressFiber 树及rootFiber
prepareFreshStack(root, expirationTime);
}
// workInProgress 如果不为 null
if (workInProgress !== null) {
do {
try {
// 以同步的方式开始构建 Fiber 对象
workLoopSync();
// 跳出 while 循环
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
if (workInProgress !== null) {
// 这是一个同步渲染, 所以我们应该完成整棵树.
// 无法提交不完整的 root, 此错误可能是由于React中的错误所致. 请提出问题.
invariant(
false,
'Cannot commit an incomplete root. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
} else {
// 将构建好的新 Fiber 对象存储在 finishedWork 属性中
// 提交阶段使用
root.finishedWork = (root.current.alternate: any);
root.finishedExpirationTime = expirationTime;
// 结束 render 阶段
// 进入 commit 阶段
finishSyncRender(root);
}
}
}
5.1.8.2 prepareFreshStack
File location: packages/react-reconciler/src/ReactFiberWorkLoop.js
/**
* 根据 currentFiber 树中的 rootFiber
* 构建 workInProgressFiber 树中的 rootFiber
*/
function prepareFreshStack(root, expirationTime) {
// 为 FiberRoot 对象添加 finishedWork 属性
// finishedWork 表示 render 阶段执行完成后构建的待提交的 Fiber 对象
root.finishedWork = null;
// 初始化 finishedExpirationTime 值为 0
root.finishedExpirationTime = NoWork;
// 建构 workInProgress Fiber 树的 Fiber 对象
workInProgressRoot = root;
// 构建 workInProgress Fiber 树中的 rootFiber
workInProgress = createWorkInProgress(root.current, null);
renderExpirationTime = expirationTime;
workInProgressRootExitStatus = RootIncomplete;
}
5.1.8.3 createWorkInProgress
file location: packages/react-reconciler/src/ReactFiber.js
// 构建 workInProgress Fiber 树中的 rootFiber
// 构建完成后会替换 current fiber
// 初始渲染 pendingProps 为 null
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
// current: current Fiber 中的 rootFiber
// 获取 current Fiber 中对应的 workInProgress Fiber
let workInProgress = current.alternate;
// 如果 workInProgress 不存在
if (workInProgress === null) {
// 创建 fiber 对象
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
// 属性复用
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 使用 alternate 存储 current
workInProgress.alternate = current;
// 使用 alternate 存储 workInProgress
current.alternate = workInProgress;
}
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
// 返回创建好的 workInProgress Fiber 对象
return workInProgress;
}
5.1.8.4 workLoopSync
File location: packages/react-reconciler/src/ReactFiberWorkLoop.js
// 以同步的方式构建 workInProgress Fiber 对象
function workLoopSync() {
// workInProgress 是一个 fiber 对象
// 它的值不为 null 意味着该 fiber 对象上仍然有更新要执行
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
5.1.8.5 performUnitOfWork
File location: packages/react-reconciler/src/ReactFiberWorkLoop.js
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
// unitOfWork => workInProgress Fiber 树中的 rootFiber
// current => currentFiber 树中的 rootFiber
const current = unitOfWork.alternate;
// 存储下一个要构建的子级 Fiber 对象
let next;
// false
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// 初始渲染 不执行
} else {
// beginWork: 从父到子, 构建 Fiber 节点对象
// 返回值 next 为当前节点的子节点
next = beginWork(current, unitOfWork, renderExpirationTime);
}
// 为旧的 props 属性赋值
// 此次更新后 pendingProps 变为 memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 如果子节点不存在说明当前节点向下遍历子节点已经到底了
// 继续向上返回 遇到兄弟节点 构建兄弟节点的子 Fiber 对象 直到返回到根 Fiber 对象
if (next === null) {
// 从子到父, 构建其余节点 Fiber 对象
next = completeUnitOfWork(unitOfWork);
}
return next;
}
5.1.8.6 beginWork
File location: packages/react-reconciler/src/ReactFiberBeginWork.js
// 从父到子, 构建 Fiber 节点对象
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
// NoWork 常量 值为0 清空过期时间
workInProgress.expirationTime = NoWork;
// 根据当前 Fiber 的类型决定如何构建起子级 Fiber 对象
// 文件位置: shared/ReactWorkTags.js
switch (workInProgress.tag) {
// 2
// 函数型组件在第一次渲染组件时使用
case IndeterminateComponent: {
return mountIndeterminateComponent(
// 旧 Fiber
current,
// 新 Fiber
workInProgress,
// 新 Fiber 的 type 值 初始渲染时是App组件函数
workInProgress.type,
// 同步 整数最大值 1073741823
renderExpirationTime,
);
}
// 0
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
// 1
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
// 3
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
// 5
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
// 6
case HostText:
return updateHostText(current, workInProgress);
// 组件类型未知 报错
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}
5.1.8.7 updateHostRoot
file location: packages/react-reconciler/src/ReactFiberBeginWork.js
// HostRoot => <div id="root"></div> 对应的 Fiber 对象
// 找出 HostRoot 的子 ReactElement 并为其构建 Fiber 对象
function updateHostRoot(current, workInProgress, renderExpirationTime) {
// 获取更新队列
const updateQueue = workInProgress.updateQueue;
// 获取新的 props 对象 null
const nextProps = workInProgress.pendingProps;
// 获取上一次渲染使用的 state null
const prevState = workInProgress.memoizedState;
// 获取上一次渲染使用的 children null
const prevChildren = prevState !== null ? prevState.element : null;
// 浅复制更新队列, 防止引用属性互相影响
// workInProgress.updateQueue 浅拷贝 current.updateQueue
cloneUpdateQueue(current, workInProgress);
// 获取 updateQueue.payload 并赋值到 workInProgress.memoizedState
// 要更新的内容就是 element 就是 rootFiber 的子元素
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
// 获取 element 所在对象
const nextState = workInProgress.memoizedState;
// 从对象中获取 element
const nextChildren = nextState.element;
// 获取 fiberRoot 对象
const root: FiberRoot = workInProgress.stateNode;
// 服务器端渲染走 if
if (root.hydrate && enterHydrationState(workInProgress)) {
// 忽略
} else {
// 客户端渲染走 else
// 构建子节点 fiber 对象
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}
// 返回子节点 fiber 对象
return workInProgress.child;
}
5.1.8.8 reconcileChildren
File location: packages/react-reconciler/src/ReactFiberBeginWork.js
export function reconcileChildren(
// 旧 Fiber
current: Fiber | null,
// 父级 Fiber
workInProgress: Fiber,
// 子级 vdom 对象
nextChildren: any,
// 初始渲染 整型最大值 代表同步任务
renderExpirationTime: ExpirationTime,
) {
/**
* 为什么要传递 current ?
* 如果不是初始渲染的情况, 要进行新旧 Fiber 对比
* 初始渲染时则用不到 current
*/
// 如果就 Fiber 为 null 表示初始渲染
if (current === null) {
// 为当前构建的 Fiber 对象添加子级 Fiber 对象
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
}
// 忽略了 else 的情况
}
5.1.8.9 ChildReconciler
File location: packages/react-reconciler/src/ReactChildFiber.js
/**
* shouldTrackSideEffects 标识, 是否为 Fiber 对象添加 effectTag
* true 添加 false 不添加
* 对于初始渲染来说, 只有根组件需要添加, 其他元素不需要添加, 防止过多的 DOM 操作
*/
// 用于初始渲染
export const mountChildFibers = ChildReconciler(false);
function ChildReconciler(shouldTrackSideEffects) {
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
return lastPlacedIndex;
}
// 忽略了一部分初始化渲染不执行的代码
}
function placeSingleChild(newFiber: Fiber): Fiber {
// 如果是初始渲染 会在根组件(App)上设置 effectTag 属性为 Placement 值为 1
// 其他子级节点具有默认值为 0 防止在 commit 阶段反复操作真实DOM
// 初始渲染时如果当前处理的是根组件 true 其他组件 false
if (shouldTrackSideEffects && newFiber.alternate === null) {
// Placement 表示新创建的节点
newFiber.effectTag = Placement;
}
return newFiber;
}
// 处理子元素是数组的情况
function reconcileChildrenArray(
// 父级 Fiber
returnFiber: Fiber,
currentFirstChild: Fiber | null,
// 子级 vdom 数组
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
/**
* 存储第一个子节点 Fiber 对象
* 方法返回的也是第一个子节点 Fiber 对象
* 因为其他子节点 Fiber 对象都存储在上一个子 Fiber 节点对象的 sibling 属性中
*/
let resultingFirstChild: Fiber | null = null;
// 上一次创建的 Fiber 对象
let previousNewFiber: Fiber | null = null;
// 初始渲染没有旧的子级 所以为 null
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// oldFiber 为空 说明是初始渲染
if (oldFiber === null) {
// 遍历子 vdom 对象
for (; newIdx < newChildren.length; newIdx++) {
// 创建子 vdom 对应的 fiber 对象
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
// 如果 newFiber 为 null
if (newFiber === null) {
// 进入下次循环
continue;
}
// 初始渲染时只为 newFiber 添加了 index 属性,
// 其他事没干. lastPlacedIndex 被原封不动的返回了
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 为当前节点设置下一个兄弟节点
if (previousNewFiber === null) {
// 存储第一个子 Fiber 发生在第一次循环时
resultingFirstChild = newFiber;
} else {
// 为节点设置下一个兄弟 Fiber
previousNewFiber.sibling = newFiber;
}
// 在循环的过程中更新上一个创建的Fiber 对象
previousNewFiber = newFiber;
}
// 返回创建好的子 Fiber
// 其他 Fiber 都作为 sibling 存在
return resultingFirstChild;
}
// 返回第一个子元素 Fiber 对象
return resultingFirstChild;
}
// 处理子元素是文本或者数值的情况
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
): Fiber {
// 初始渲染不执行
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// 现有的第一个子节点不是文本节点,因此我们需要创建一个并删除现有的.
// 初始渲染不执行
deleteRemainingChildren(returnFiber, currentFirstChild);
// 根据文本创建 Fiber 对象
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
// 设置父 Fiber 对象
created.return = returnFiber;
// 返回创建好的 Fiber 对象
return created;
}
// 处理子元素是单个对象的情况
function reconcileSingleElement(
// 父 Fiber 对象
returnFiber: Fiber,
// 备份子 fiber
currentFirstChild: Fiber | null,
// 子 vdom 对象
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
// 查看子 vdom 对象是否表示 fragment
if (element.type === REACT_FRAGMENT_TYPE) {
// false
} else {
// 根据 React Element 创建 Fiber 对象
// 返回创建好的 Fiber 对象
const created = createFiberFromElement(
element,
// 用来表示当前组件下的所有子组件要用处于何种渲染模式
// 文件位置: ./ReactTypeOfMode.js
// 0 同步渲染模式
// 100 异步渲染模式
returnFiber.mode,
expirationTime,
);
// 添加 ref 属性 { current: DOM }
created.ref = coerceRef(returnFiber, currentFirstChild, element);
// 添加父级 Fiber 对象
created.return = returnFiber;
// 返回创建好的子 Fiber
return created;
}
}
function reconcileChildFibers(
// 父 Fiber 对象
returnFiber: Fiber,
// 旧的第一个子 Fiber 初始渲染 null
currentFirstChild: Fiber | null,
// 新的子 vdom 对象
newChild: any,
// 初始渲染 整型最大值 代表同步任务
expirationTime: ExpirationTime,
): Fiber | null {
// 这是入口方法, 根据 newChild 类型进行对应处理
// 判断新的子 vdom 是否为占位组件 比如 <></>
// false
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果 newChild 为占位符, 使用 占位符组件的子元素作为 newChild
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// 检测 newChild 是否为对象类型
const isObject = typeof newChild === 'object' && newChild !== null;
// newChild 是单个对象的情况
if (isObject) {
// 匹配子元素的类型
switch (newChild.$$typeof) {
// 子元素为 ReactElement
case REACT_ELEMENT_TYPE:
// 为 Fiber 对象设置 effectTag 属性
/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。