导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-react01] ReactDOM.render01
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
(一) 前置知识
(1) 一些单词
queue 队列
enqueue 排队 // enqueueSetState
lane 车道
batch 批次 // 批次更新 batched
internal 内部的 // key._reactInternals 保留字段
immutable 不可变 永恒的 // immutable.js
mutable 可变的 // an immutable object with a single mutable value
condition 条件 状态
seal 密封 封闭 // Object.seal 不能添加,删除,但是可以修改
freeze 冻结 // Object.freeze 不能添加,删除,修改
legacy blocking concurrent
// blocking 阻塞 块
// concurrent 并发
reuse 复用
hack 黑客,也有浏览器兼容的意思
pure 纯净的
shallow 浅的 // shallowEqual
flush 清空,刷新,水洗
process 加工 进程
weak 弱的 // WeakMap
passive 被动的 消极的
(2) React新老生命周期对比
- 老生命周期
- 新生命周期
- 新生命周期中
- 1.废除了
componentWillMount componentWillReceiveProps componentWillUpdate - 2.新增了
getDerivedStateFromProps 和 getSnapshotBeforeUpate来取代上面三个生命周期,注意:上面1和2的新老生生命周期不能混用 - 3.新增了几个不常用的生命周期
componentDidCatch, getDerivedStateFromError
- 1.废除了
(3) PureComponent 和 React.memo 和 shouldComponentUpdate
- PureComponent
- 只能用于类组件
- 浅比较了 props 和 state 的前后值,浅比较(===)
- 如果 ( 相等返回false,不重新渲染 ) ( 不相等返回true,重新渲染 )
- React.memo
- React.memo(functionComponent, areEqual)
- function areEqual(prevProps, nextProps) {}
- 只能用于函数式组件
- 只浅比较了 props 的前后值,浅比较(===)
- 如果 ( 相等返回true,不重新渲染 ) ( 不相等返回false,重新渲染 )
- shouldComponentUpdate
- shouldComponentUpdate(nextProps, nextState)
- 返回true重新渲染,返回false不重新渲染
(4) Object.seal() 和 Object.freeze()
- Objext.seal()
- ( 密封 ) 一个对象,( 阻止添加新属性 ),并将所有现有属性标记为 ( 不可配置 - 即属性不可被删除 ),当前属性的值,只要原来是可以写的就 ( 可以改变 )
- 简单的说就是:(
不能添加/删除属性,但是可以修改属性) - 参数: 将要密封的对象
- 返回值:被密封的对象
- seal 是密封的意思
- Object.freeze()
- ( 冻结 ) 对象,不能 (
添加,修改,删除) 对象的属性 - 参数: 将要密封的对象
- 返回值:被密封的对象
- ( 冻结 ) 对象,不能 (
Object.seal()
---
const obj = {name: 'woow_wu7'}
const obj2 = Object.seal(obj)
obj1 === obj2 // true
delete obj.name // false
delete obj2.name // -------- false,不能删除属性
obj2.name = 'wu7' // ------- 可以修改已有属性
obj.age = 20 // ------------ obj和obj2中都不会有age属性,不能添加属性
Object.freeze()
---
const obj = {name: 'woow_wu7'}
const obj2 = Object.freeze(obj)
obj1 === obj2 // true
delete obj.name // -------- false,不能删除
obj.mame = 'wu7' // ------- 不能修改
obj.age = 20 // ----------- 不能添加
(5) React是如何区分 classComponent 和 functionComponent 的?
- isReactComponent
Component.prototype.isReactComponent = {};- 在 classComponent 类的prototype上挂载了 isReactComponent 属性
- 如果 isReactComponent 存在,则是classComponent
- isPureReactComponent
- 用来标记是否是 pureComponent
- 用来标记是否是 pureComponent
(6) React源码中的一些概念
- react中的 ( 三种模式 )
- legacy
- 当前react的使用方式,没有计划清除,但该模式可能以后不会支持新功能
ReactDOM.render(<App />, rootNode)
- blocking
- 目前正在试验中,作为迁移到 concurrent 模式的过渡
ReactDOM.createBlockingRoot(rootNode).render(<App />)- blocking 阻塞 块
- concurrent
- 目前正在试验中,以后将作为react的默认开发模式,该模式开启所有新功能
ReactDOM.createRoot(rootNode).render(<App />)- concurrent 并发
- legacy
work- 在react reconciliation过程中出现的各种必须执行的活动叫作work
- 比如 ( state update ) ( props update ) ( ref update )
WorkTag--fiber.tag- 用来描述react元素的类型,也就是fiber对象的类型,即 ( fiber.tag )
side effects- 不能在render阶段完成的work,我们称这些工作为side effects
- 比如:手动更改DOM或在生命周期中执行数据请求、订阅等操作,
effectTag--fiber.effectTag- 除了update等常见的effect以外,fiber节点提供了一种方便的机制去追踪effect。
- 每一个fiber节点都有一个和它相关联的effct,这些被编码为effectTag字段对应的值,即为fiber.effectTag
Effect list- effect list 是由 firstEffect,nextEffect,lastEffect 构成的链表结构
- 重点如下
- render阶段结束,会生成一个effect list
- commit阶段,react会遍历effect list,并检查fiber对象的effect类型,当发现和其他类型相关用途的函数就调用
- stateNode
- 一个组件、一个DOM节点或其他跟fiber节点相关联的React元素的实例的引用
- 通常,我们可以说这个属性是用于保存与一个fiber相关联的本地状态
- fiber.stateNode
(7) Performance.now() 和 Date.now()
- performance.now()
- 返回从 ( time-origin ) 到 ( 当前调用经过的时间 )
- 一个精确到毫秒的 DOMHighResTimeStamp
- DOMHighResTimeStamp 是一个double类型,用于存储毫秒级的时间值
- 返回从 ( time-origin ) 到 ( 当前调用经过的时间 )
- Date.now()
- 返回当前时间距离时间零点的毫秒数,时间零点是1970/1/1/00:00:00
Date.now() === +new Date() === new Date - 0
performance.now
--
const t0 = window.performance.now(); // 启始时间
doSomething();
const t1 = window.performance.now(); // 终结时间
console.log("doSomething函数执行了" + (t1 - t0) + "毫秒.")
(二) Component 和 PureComponent
(2.1) PureComponent
- 概念
PureComponent = Component + shouldComponentUpdate(nextProps, nextState)- 在 shouldComponentUpdate() 中对 ( 新旧props ) 和 ( 新旧state ) 做了 ( 一层钱比较 )
- 一般这样使用:class A extends React.Component/React.PureComponent {}
- 什么是 ( 浅比较 )
- 浅比较也称 ( 引用相等 ),也就是全等 ( === )
const o1 = {a: 1}
const o2 = {a: 1}
const o3 = o2
const o4 = (obj) => { obj.b = 1; return obj }
o1 === o2 // false
o3 === o2 // true,两个变量,栈内存中的指针都指向了同一个堆内存数据的地址
o4(o1) === o1 // true
(2.2) PureComponent 源码
- isPureReactComponent
- 相对于Component,在prototype上多了
isPureReactComponent=true属性 - 文件位置:
packages/react/src/ReactBaseClasses.js
- 相对于Component,在prototype上多了
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject; // 赋值空对象
this.updater = updater || ReactNoopUpdateQueue;
// ReactNoopUpdateQueue
// - 是一个对象,具有这些属性 {isMounted, enqueueForceUpdate, enqueueReplaceState, enqueueSetState }
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
// 1
// new ComponentDummy()
// function ComponentDummy() {}
// ComponentDummy.prototype = Component.prototype;
// 寄生原型链继承,这样 ( pureComponent实例 ) 的 ( 原型对象上 ) 是没有任何属性的,但是原型对象的原型链上的属性仍然可以继承
// 2
// Component.prototype
// - 具有以下属性
// - isReactComponent
// - setState
// - forceUpdate
pureComponentPrototype.constructor = PureComponent;
// 2
// 修改prototype对象时,一定要修改prototype.constructor,防止引用出错
// 如果不修改,prototype.constructor将指向 ComponentDummy
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
// 合并属性
pureComponentPrototype.isPureReactComponent = true;
// 3
// isPureReactComponent 用来判断是不是纯组件 PureComponent
- checkShouldComponentUpdate
- 文件位置:
packages/react-reconciler/src/ReactFiberClassComponent.old.js checkShouldComponentUpdate检查是否需要更新时,对新旧props和新旧state做了一层钱比较
- 文件位置:
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
...
// PureComponent2
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
// 浅比较 props 和 state
);
}
...
}
(三) createRef
(3.1) React.createRef() 源码
export function createRef(): RefObject {
const refObject = { current: null }; // ref实例上具有 current 属性
if (__DEV__) {
Object.seal(refObject); // ---------- current 属性不能添加和删除,只能修改,在开发环境时
}
return refObject;
}
(3.2) React.createRef() 使用案例
class TestCreateRef extends React.Component {
inputRef = React.createRef();
componentDidMount() { this.inputRef.current.focus() }
render() {
return <input type="text" ref={this.inputRef} />
}
}
(四) ReactDOM.render()
(4.1) 搭建react源码调试环境
1. 通过create-react-app 创建一个react项目
2. yarn run eject 弹出配置
3. 克隆react源码 git clone git@github.com:facebook/react.git
4. 将克隆下来的 ( 源码的packages文件夹 ) 放入创建好的react项目的 ( src/REACT_SOURCE_CODE_ANALYSIS ) 文件夹下
5. 修改webpack.config.js中的resolve中的alias配置
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
// + 添加如下代码
'react': path.resolve(__dirname, '../src/REACT_SOURCE_CODE_ANALYSIS/packages/react'),
'react-dom': path.resolve(__dirname, '../src/REACT_SOURCE_CODE_ANALYSIS/packages/react-dom'),
'shared': path.resolve(__dirname, '../src/REACT_SOURCE_CODE_ANALYSIS/packages/shared'),
'react-reconciler': path.resolve(__dirname, '../src/REACT_SOURCE_CODE_ANALYSIS/packages/react-reconciler'),
}
6. 修改 config/env.js
const stringified = {
__DEV__: true,
__PROFILE__: true,
__UMD__: true,
__EXPERIMENTAL__: true, // 和 .eslintrc.json中的globals一一对应
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
7. react项目根目录创建 .eslintrc.json
{
"extends": "react-app",
"globals": {
"__DEV__": true,
"__PROFILE__": true,
"__UMD__": true,
"__EXPERIMENTAL__": true,
}
}
8. 删除 webpack.config.js 中的 eslint 检查
// !disableESLintPlugin &&
// new ESLintPlugin({
// // Plugin options
// extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
// formatter: require.resolve('react-dev-utils/eslintFormatter'),
// eslintPath: require.resolve('eslint'),
// failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
// context: paths.appSrc,
// cache: true,
// cacheLocation: path.resolve(
// paths.appNodeModules,
// '.cache/.eslintcache'
// ),
// // ESLint class options
// cwd: paths.appPath,
// resolvePluginsRelativeTo: __dirname,
// baseConfig: {
// extends: [require.resolve('eslint-config-react-app/base')],
// rules: {
// ...(!hasJsxRuntime && {
// 'react/react-in-jsx-scope': 'error',
// }),
// },
// },
// }),
9. src/REACT_SOURCE_CODE_ANALYSIS/packages/react-reconciler/src/ReactFiberHostConfig.js
// 注释掉
// import invariant from 'shared/invariant';
// invariant(false, 'This module must be shimmed by a specific renderer.');
// 添加此行
// export * from "./forks/ReactFiberHostConfig.dom";
10. src/REACT_SOURCE_CODE_ANALYSIS/packages/shared/ReactSharedInternals.js
// 注释掉
// import * as React from 'react';
// 添加此行
import ReactSharedInternals from '../react/src/ReactSharedInternals';
// 注释掉
// ReactSharedInternals
// const ReactSharedInternals =
// React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
export default ReactSharedInternals;
11. src/REACT_SOURCE_CODE_ANALYSIS/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."
);
}
12. 在react中项目的入口文件index.js中更新引入react和reactDOM的方法
// import React from 'react';
// import ReactDOM from 'react-dom';
import * as React from 'react'; // 这里能够引用到,是因为在webpack.config.js中的resolve.alias中指定了别名的引用路径
import * as ReactDOM from 'react-dom';
13. 在vscode中下载 Debugger for Chrome 插件,并在launch.json中做如下配置
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
(4.2) ReactDOM.render() 基础知识
ReactDOM.render(element, container[, callback])- 作用
- 初次渲染:在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回null)
- 更新:如果React元素之前已经在container里渲染过,这将会对其执行 ( 更新操作 ),并仅会在必要时改变DOM以映射最新的React元素
- 如果提供了第三个参数,表示在 ( 初次渲染或者更新 ) 完成后执行的 ( 回调函数 )
- 作用
- 参数中的element是什么?
- element是一个 (react元素)
-
- react组件的返回值就是一个react元素 ,组件由元素构成
-
- react元素可以通过 React.createElement(type, config, children)来创建
-
- ( react元素 ) 本质上就是一个 ( javascript对象 )
-
- React.createElement(type, config, children) 源码
- element是一个 (react元素)
- ReactDOM.render()分为 ( 三个阶段 )
init初始化阶段- 主要负责生成fiber树的基础实体对象,比如fiberRoot,rootFiber
render阶段- 负责fiber树的创建,调度,调和等
commit阶段- 根据virtualDOM,渲染真实DOM
(4.3) ReactDOM.render() 源码
- ReactDOM.render 返回 legacyRenderSubtreeIntoContainer()
// ----------------------------------------------------------------------------------------------------------- ReactDOM.render
// 【 起点 】render
// 1
// ReactDOM.render(element, container[, callback])
// - 1. 作用:
// - 1. 初始渲染:在提供的 container 里面渲染一个React元素,并返回对该组件的引用,( 或者针对无状态组件返回null )
// - 2. 更新:-- 如果React元素之前已经在 container 里渲染过,这将会对其执行 ( 更新操作 ),并仅仅会在必要时改变DOM以映射最新的React元素
// - 2. 参数
// - element
// - element表示的是一个react元素,react组件由element元素组成,element元素本质上是一个javascript对象,可以通过React.createElement生成
// - React.createElement(type, config, children) => ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props) => element
// - element对象具有
// - 基本属性:type,key,ref,props,_owner, $$typeof
// - DEV环境多了:__store, __self, __source 属性
// - container
// - 属性,即组件或者element上的属性
// - callback: 可选,在 ( 渲染或更新 ) 完成后,执行的回调函数
// - 3. 返回值
// - legacyRenderSubtreeIntoContainer
// - legacyRenderSubtreeIntoContainer( null, element, container, false, callback ) 调用的返回值就是render()函数的返回值
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
if (__DEV__) {
console.error(
'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
'instead. Until you switch to the new API, your app will behave as ' +
"if it's running React 17. Learn " +
'more: https://reactjs.org/link/switch-to-createroot',
);
// createRoot
// - React 18中不再支持ReactDOM.render,请使用 createRoot() 代替
}
invariant(
isValidContainerLegacy(container), // 合法的节点
'Target container is not a DOM element.',
);
// export default function invariant(condition, format, a, b, c, d, e, f) {
// throw new Error(
// 'Internal React error: invariant() is meant to be replaced at compile ' +
// 'time. There is no runtime version.',
// );
// }
if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
// 1
// isContainerMarkedAsRoot
// export function isContainerMarkedAsRoot(node: Container): boolean {
// return !!node[internalContainerInstanceKey];
// }
// 2
// internalContainerInstanceKey
// const internalContainerInstanceKey = '__reactContainer$' + randomKey;
// 3
// randomKey
// const randomKey = Math.random()
// .toString(36)
// .slice(2);
if (isModernRoot) {
console.error(
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.createRoot(). This is not supported. ' +
'Did you mean to call root.render(element)?',
// 您正在对以前传递给 ReactDOM.createRoot() 的容器使用 ReactDOM.render(),不支持这样做,你是想调用root.render(element)?
);
}
}
// 1
// legacyRenderSubtreeIntoContainer()
// render() 函数最终返回值 legacyRenderSubtreeIntoContainer()
// legacy : 遗产 + render: 渲染 + subtree: 子树 + into: 到 + container: 容器
// 2
// react一共有三种模式
// legacy
// blocking
// concurrent 以后react18中将默认开启并发模式
return legacyRenderSubtreeIntoContainer(
null, // parentComponent 父组件
element, // children
container, // container
false, // forceHydrate 服务端渲染的标志
callback, // callback
);
}
- legacyRenderSubtreeIntoContainer
- 主要做了以下事情
- 创建fiberRoot 和 rootFiber,并相互引用
- 关联 continer 和 rootFiber.current
- 事件相关
- updateContainer()
- 主要做了以下事情
// ----------------------------------------------------------------------------------------------------------- legacyRenderSubtreeIntoContainer
// 【0】 legacyRenderSubtreeIntoContainer
// 1
// 初次渲染 init mount
// - 1. 初次渲染 - 是没有 (老的虚拟DOM节点的 )
// - 2. 即初次渲染生成一个插入一个,但如果是更新阶段则要考虑很多情况了,比如移动,更新,插件等等
// 2
// 如何判断是不是初次渲染?
// - 因为初次渲染是没有老的虚拟DOM节点的,所以可以通过 ( root ) 来判断,即 ( let root = container._reactRootContainer )
// 2
// 初始化调用ReactDOM.render() 时的 ( 参数 ) 如下
// - legacyRenderSubtreeIntoContainer( null, element, container, false, callback, );
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // init => null
children: ReactNodeList, // render 第一个参数 element
container: Container, // render 第二个参数 container
forceHydrate: boolean, // 服务器端渲染标识,初始化是false, init => false
callback: ?Function, // 初次渲染或者更新后需要执行的回调,可选,基本不会使用
) {
if (__DEV__) {
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}
let root = container._reactRootContainer;
// root
// 1. 声明 root
// 2. 在container上生声明了_reactRootContainer属性
// 3. init时,_reactRootContainer属性不存在,则 root = undefined
// 4. container
// - 1
// - container
// - container是调用 reactDOM.render()时传入的第二个参数,即jsx挂载的容器,是真实的DOM
// - 下面生成fiberRoot的时候, root = container._reactRootContainer = legacyCreateRootFromDOMContainer(),
// - 即在容器上挂载了 fiberRoot
// - 2
// - _reactRootContainer
// - container._reactRootContainer
// - react的项目中,可以通过 document.querySelector('#root')._reactRootContainer 来读取 _reactRootContainer
// - 可以看到:( fiberRoot的构造函 ) 数是 ( FiberRootNode(containerInfo, tag, hydrate) )
// - 3
// - _internalRoot
// - container._reactRootContainer._internalRoot 对应这 ( fiberRoot )
// - ( _internalRoot === fiberRoot )
// - internal 是内部的意思
// 5. 先总结一下
// - root 就是 fiberRoot,因为后面会赋值给 fiberRoot 变量
// - fiberRoot 同样挂在了 container DOM节点上
// - fiberRoot对象 就是整个fiber树的 根节点 ( 其实每个DOM节点一定对应着一个fiber对象,所以DOM树和fiber数一一对应 )
let fiberRoot: FiberRoot; // 缓存 old virtual DOM,用于对比
// fiberRoot
// 1. 注意区分 fiberRoot 和 rootFiber
// - fiberRoot 只有一个
// - rootFiber 可以有多个,就是一个普通的fiber节点
// 2. 两者的关系
// - rootFiber.stateNode = fiberRoot
// - fiberRoot.current = rootFiber
// - 两者循环引用
if (!root) {
// Initial mount
// ------------------------------------------------------------ 初始化mount阶段,即初次渲染,root不存在
// 初次渲染 root 是不存在的,所以要创建生成一个root
// 1
// container
// container._reactRootContainer.
// container._reactRootContainer._internalRoot 指向的就是 fiberRoot
// 2
// fiberRoot === fiberRootNode对象
// 3
// 再次复习 fiberRoot 和 rootFiber
// - fiberRoot
// - fiberRoot 关联的是真实的DOM容器节点
// - rootFiber
// - 是虚拟DOM的根节点
// - fiberRoot.current === rootFiber
// - rootFiber.stateNode === fiberRoot
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate, // init => false
);
fiberRoot = root; // 缓存 old virtual DOM,用于对比
if (typeof callback === 'function') { // 一般都不会指定callback,即一般不会进入if
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 初次渲染是非批量更新,可以保证 ( 更新效率与用户体验 )
// 比如初次渲染希望更快的速速让用户看到 ui
flushSyncWithoutWarningIfAlreadyRendering(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
// flushSyncWithoutWarningIfAlreadyRendering
// - 函数签名:flushSyncWithoutWarningIfAlreadyRendering(fn) => fn()
} else {
// ------------------------------------------------------------ 更新阶段
fiberRoot = root;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
// 批量更新 batched update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
- updateContainer()
- 主要负责
- 获取当前 fiber 节点的 lane优先级,lane越小,优先级越高
- 创建context
- 创建 update对象,并入队
- 调度当前节点的 rootFiber
- 主要负责
// ----------------------------------------------------------------------------------------------------------- updateContainer
// 【1】updateContainer
// updateContainer(children, fiberRoot, parentComponent, callback);
// 1
// updateContainer主要做的事情
// 1. 获取当前 fiber 节点的 lane 优先级
// 2. 结合lane优先级,创建当前fiber节点的update对象,并将其入队
// 3. 调度当前节点 rootFiber
// 2
// 调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot, // fiberRoot
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
if (__DEV__) {
onScheduleRoot(container, element);
}
const current = container.current; // container.current = fiberRoot.current = rootFiber; 这里rootFiber=3时,说明是 hostRoot
const eventTime = requestEventTime(); // 541304.0999999642
const lane = requestUpdateLane(current); // 1
// lane
// - lane 表示优先级
// - lane越小,表示优先级越高
// - lane为二进制存储,一共31位,每个位为一个车道
// performance.now()
// - 返回:当前网页自从performance.timing.navigationStart到当前时间之间的毫秒数
if (enableSchedulingProfiler) {
// export const enableSchedulingProfiler = __PROFILE__ && __EXPERIMENTAL__;
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent); // mount => 返回一个空对象
if (container.context === null) { // mount => 容器上的context是空
container.context = context; // 给 container.context = {}
} else {
container.pendingContext = context;
}
if (__DEV__) {
if (
ReactCurrentFiberIsRendering &&
ReactCurrentFiberCurrent !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
console.error(
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
);
}
}
const update = createUpdate(eventTime, lane); // 返回update对象,tag=0, lane=1
// Caution: React DevTools currently depends on this property
// being called "element".
// 警告:React DevTools当前依赖于名为“element”的此属性。
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
if (__DEV__) {
if (typeof callback !== 'function') {
console.error(
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
}
update.callback = callback;
}
enqueueUpdate(current, update, lane); // 将update对象入队,形成环状链表
// enqueueUpdate
// enqueueUpdate主要用来添加一个共享队列sharedQueue,该队列可以和workInProgress和FiberRoot进行共享队列
const root = scheduleUpdateOnFiber(current, lane, eventTime); // 调度 fiber 节点的挂载
if (root !== null) {
entangleTransitions(root, current, lane);
}
return lane; // 返回 ( 当前节点 即fiberRoot ) 的优先级
}
(五) 源码分析仓库地址
资料
- react调试源码教程1 juejin.cn/post/694268…
- react调试源码教程2 zhuanlan.zhihu.com/p/336933386
- fiber相关的概念汇总 tech.tuya.com/react-fiber…
- enqueueUpdate juejin.cn/post/695158…