最近觉得得开始找工作了,虽然现在好像都不咋招人了,但还是得复习一下react基本原理和相关技术栈,再着重看看部分源码,以前看的时候没有记录需要的时候找不着,这回得作个文档留存:
拉代码: https://github.com/facebook/react
demo: https://github.com/bajiu/blog_code/tree/master/react
项目结构
目录结构
react 采用的使monorepo的项目结构,这种结构下有先找ES-lint,因为一般为了代码的统一都会在最外层做统一的lint来保证代码一致性
这次先重点关注三个包:
react: 包含核心API,如useState、useEffect等。react-dom: 包含与浏览器相关的DOM操作代码。scheduler: 负责调度更新的模块,使react调度的核心。
然后是剩下的一些相关包:
shared:包含一些公共的工具和类型定义,其他模块也会引用。react-reconciler:这是 React 的核心调和器模块,管理 React 树的对比和更新。jest和react-test-renderer:包含测试框架 Jest 的配置和一些自定义匹配器。允许在不依赖 DOM 的情况下渲染和测试 React 组件。react-noop-renderer: 主要用于测试和调试 Fiber 架构。eslint-plugin-react-hooks:这是React Hooks的ESLint插件,用于检查Hooks的使用规则,如依赖项数组和调用顺序。fixtures:包含一些小型应用和示例代码,用于演示和测试不同的React功能。scripts:包含构建、测试和发布React的脚本和配置。flow:React使用了Flow类型检查,该文件夹包含Flow配置和类型声明。
接下来的阅读顺序大概如下:
- 从
react/src/React.js的API出发,逐步分析createElement和Component等关键实现。 - 在
react-dom/src/ReactDOM.js中,研究React是如何将虚拟DOM转换为真实 DOM。 - 通过
scheduler理解任务调度,并在react-reconciler了解Fiber树的调度和更新机制。
在这之前,我们先了解一下JSX。
React核心API和hooks
先介绍一下核心api,这里就直接列出来了,之后分析源码时候再挨个来
React.createElement(type, [props], [...children]):创建一个虚拟 DOM.React.Component和React.PureComponent:定义一个 React 组件的基类。React.PureComponent是Component的子类,自动实现shouldComponentUpdate,通过浅比较来决定是否重新渲染。React.Fragment:包装多个元素,避免创建额外的 DOM 节点。ReactDOM.render(element, container):将 React 元素渲染到实际的 DOM 中(一般用于项目的根节点)。ReactDOM.createPortal(child, container):将子元素渲染到指定的 DOM 节点,而不在父组件的 DOM 层级内。实现模态框等浮层。React.useState(initialState):在函数组件中定义一个状态变量。React.useEffect(callback, [dependencies]):useEffect(callback, [dependencies])React.useMemo(callback, [dependencies]):优化性能,避免不必要的计算。useMemo会记住一个计算的值,只有当依赖项发生变化时才会重新计算,否则会返回上次的计算结果。React.useCallback(callback, [dependencies])React.useRef(initialValue)React.useLayoutEffect(callback, [dependencies]):与useEffect类似,useLayoutEffect在 DOM 更新之后、浏览器绘制之前同步执行,这意味着它会阻塞浏览器的渲染。dan同时能保证布局更新发生后立即执行,而不会让浏览器在布局更新时渲染不一致的内容。React.createContext(defaultValue):创建上下文对象,允许组件通过上下文共享状态而不必手动传递 props。React.useContext(Context):在函数组件中订阅上下文变化,避免多层传递属性。React.useReducer(reducer, initialArg, init?):管理那些状态变更逻辑复杂、状态依赖其他状态值的场景。React.useImperativeHandle(ref, createHandle, [dependencies])React.memo(Component, [areEqual]):是用于组件的优化,通过缓存组件的渲染结果来避免不必要的重新渲染。React.forwardRef((props, ref) => <Component {...props} ref={ref} />):使函数组件支持ref属性,便于父组件访问子组件的 DOM 节点或实例。React.lazy(() => import(‘./MyComponent’)):实现组件的动态加载,按需加载组件以优化性能。React.Suspense:与React.lazy一起使用,设置异步加载组件的占位内容。React.StrictMode: 在开发模式下启用严格模式(没用过)。React.useId: 差点儿拉下了这个,生成唯一ID的
JSX详解
JSX(JavaScript XML)是 React 提供的一种语法扩展,允许我们在 JavaScript 中直接编写类似 HTML 的标签,从而更加直观地描述 UI 结构。JSX 最终会被编译成纯 JavaScript 代码,并通过 React.createElement 函数生成虚拟 DOM。
1.基本语法
jsx的基本语法类似于HTML,但实际上是在js中嵌入 XML 语法。
比如这段代码
const element = <h1>Hello, world!</h1>;
会被Babel编译成
const element = React.createElement('h1', null, 'Hello, world!');
也可以嵌入表达式:
const name = "summer889";
const element = <h1>Hello, {name}!</h1>;
// 编译后
const element = React.createElement('h1', null, `Hello, ${name}!`);
对于属性的处理与HTML类似,但是属性名需要使用驼峰命名
子元素嵌套、条件、数组、内联样式的渲染:
// 子元素嵌套
const element0 = (
<div>
<h1>Hello, React!</h1>
<p>This is a paragraph.</p>
</div>
);
// 编译结果
const element0 = React.createElement(
'div',
null,
React.createElement('h1', null, 'Hello, World!'),
React.createElement('p', null, 'run p tag')
);
// 条件渲染
const isLoggedIn = true;
const element1 = (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
// 编译结果
const element1 = React.createElement(
'div',
null,
isLoggedIn
? React.createElement('h1', null, 'Welcome back!')
: React.createElement('h1', null, 'Please sign in.')
);
// 数组渲染
const items = ['Apple', 'Banana', 'Cherry'];
const element2 = (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
// 内联样式渲染
const divStyle = { color: 'blue', fontSize: '20px' };
const element3 = <div style={divStyle}>Styled Text</div>;
// 编译结果
const element3 = React.createElement('div', { style: { color: 'blue', fontSize: '20px' } }, 'Styled Text');
使用 JSX 可以减少 React.createElement 的调用次数,使代码更接近 HTML 结构。
工作原理
要把大象装冰箱,总共分三步:
1.JSX 编译:React 使用 Babel 将 JSX 编译为 React.createElement 调用。
2.生成虚拟 DOM:React.createElement 会返回一个 JavaScript 对象,描述这个元素的类型、属性和子元素。这个对象就是虚拟 DOM(VNode)。
3.渲染到真实 DOM:React 会根据虚拟 DOM 的变化,通过调和(reconciliation)算法将更改反映到实际的 DOM 中。
Babel编译JSX主要也分为三个部分:
- 类型识别:判断
JSX标签的类型,例如HTML标签(h1、div)或 React 组件。 - 属性解析:将
JSX属性转换为对象形式传递给React.createElement。 - 子元素解析:嵌套的
JSX标签会递归转换为React.createElement的嵌套调用。
使用Babel插件 @babel/preset-react
Babel 使用 @babel/preset-react 这个预设(preset)来识别和转换 JSX。,先安装:
yarn add @babel/core @babel/cli @babel/preset-react -D
配置 .babelrc
{
"presets": ["@babel/preset-react"]
}
JSX 到 React.createElement 的转换
const element = (
<div className="container">
<h1>Hello, World!</h1>
<p>This is summmer889.</p>
</div>
);
添加编译: "jsx": "babel --presets @babel/preset-react src/jsx/example1.jsx --out-dir dist" 然后我们就可以在dist/example1.js中看到编译后的代码:
const element = /*#__PURE__*/React.createElement("div", {
className: "container"
}, /*#__PURE__*/React.createElement("h1", null, "Hello, World!"), /*#__PURE__*/React.createElement("p", null, "JSX \u8F6C\u6362"));
console.log(element);
我们可以看到汉字被转成了unicode编码,react里面应该有在做CharCodeAt方法。然后加载进界面就能看到了。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSX with Babel</title>
<!-- 引入 React 和 ReactDOM -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div> <!-- React 渲染容器 -->
<script src="../../dist/example1.js"></script> <!-- 编译后的 JavaScript -->
</body>
</html>
然后就能水灵灵看到界面了:
并且在日志里可以看到如下信息:
JSX编译选项
Babel 在 @babel/preset-react 中提供了以下几个选项,可以用来控制 JSX 的编译行为:
pragma:指定用于创建元素的函数,默认为React.createElement。pragmaFrag:指定用于创建 Fragment 的函数,默认为React.Fragment。throwIfNamespace:当使用XML命名空间(例如 SVG)时,是否抛出错误。默认为true。runtime:决定运行时编译方式,有两个可选值:"classic":传统的编译方式,将JSX转换为React.createElement。"automatic":自动引入运行时,避免手动导入React。
React 17 引入了自动化运行时,即可以使用
runtime: "automatic"的新编译模式。在这个模式下,不再需要手动导入React。
手动调用babel进行JSX转换
当然,我们也可以在项目中通过手动的方式进行对JSX的转换
const babel = require('@babel/core');
const jsxCode = `<h1>Hello, world!</h1>`;
const result = babel.transform(jsxCode, {
presets: ['@babel/preset-react']
});
console.log(result.code);
通过 Babel 插件扩展 JSX
Babel 插件可以帮助我们进一步扩展 JSX 的功能。例如,可以添加自定义属性、样式前缀或自动优化等。
在每个 JSX 元素上自动添加 data-summer 属性,首先我们要创建一个自定义 Babel 插件:
module.exports = function ({ types: t }) {
return {
visitor: {
JSXOpeningElement(path) {
path.node.attributes.push(
t.jsxAttribute(t.jsxIdentifier('data-summer'), t.stringLiteral('auto-id'))
);
}
}
};
};
- Babel 提供的
t(即 Babel 的AST工具)来生成JSX属性。 - 在
JSXOpeningElement访问器中,给每个JSX元素添加data-summer="auto-id"属性。 - 当 Babel 处理
JSX时,每个JSX元素都会自动添加data-summer属性。
在.babelrc文件中添加插件的配置
{
"presets": ["@babel/preset-react"],
"plugins": [
"./src/babelplugin"
]
}
然后继续刚才的操作,就有了

要是有前来学习的小伙伴儿迷茫的,具体babel相关的看webpack那部分去~