react 🔗
react 是一个 js 的库, 简化 html 和 js 操作 dom 的开发过程。 核心是管理组件 make components,管理状态 manage state。
react 中引入Flow 是一个静态类型检查器,用于在 JavaScript 中引入静态类型。https://github.com/facebook/react
- react 核心概念快速入门
- JQuery 是命令式库(需要指明每一步的命令), React 是声明式库。
- UI = f(data), data = props + state.
- Fiber 结构
- 实现异步可中断的找出变化的组件。
- VirtualDOM
- 优势: 不用具体考虑 DOM 操作,可以用申明式的方式写代码;减少页面渲染次数;更好的跨平台能力。
- Diff 算法
- Hooks
- useState 让函数组件具有维持状态的能力。
实际开发过程中,都是在写组件。所以如何提取公共组件就很重要,因为可以服用组件,少写很多代码。
- 识别重复的 UI 部分
- 定义 props 接口,明确组件接收的参数,类型的约束
- 实现组件, 将 UI+逻辑封装到组件内部,通过 props 暴露参数
- 导出和使用公共组件
React Developer Tools 🔗
Browser extension , react dev tools
核心概念 🔗
component 组件是 js 定义的首字母大写的函数,函数返回一组 hmtl 标签(JSX)。
JSX 是 js+html,属性名称为camelCase, 不是HTML attributes, 一种更自然的方式创建 react element。
jsx 会 babel 被翻译为React.createElement函数,返回 js 对象( VDOM)。
babel可以将 JSX 转译为等价的 js 代码。
html 为<button class="btn"> , jsx 为<button className="btn">
/*
<Greeting text={'yo'} />
*/
function Greeting(props) {
return <h1>{props.text}</h1>;
}
/*
<Parent>
<Child /> // passed as "children"
</Parent>
*/
function Parent(props) {
return <div>{props.children}</div>;
}
组件的内容是“插槽”。 例如 Vue.js。在 React 中,通常称其为 "children"。都是指将某些内容或元素插入到子组件的指定位置。
组件的属性,状态,事件。
组件通讯:
- 通过组件的属性 props, 单向数据流(pass data into another component)。
- 父->子,props, 将 jsx 传给子组件(插槽)
- 子->父,子调父传进来的函数
- context hook 多组件共享状态
React Hooks: useState, useContext, useRef, useEffect。
状态管理。
渲染流程。
避免重复渲染。
<div>Hello</div>这段 jsx,被Babel转换为React.createElement('div', null, 'Hello')函数,该函数返回一个对象
react element 的 type 种类: REACT_ELEMENT_TYPE, REACT_PROOTAL_TYPE(将节点渲染到父组件之外的 dom 节点上),
REACT_FRAGMENT_TYPE(无需添加额外的父节点,将子组件放一起返回),
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE(profiler 测量),
REACT_CONSUMER_TYPE(<MyContext.Consumer>创建的虚拟 DOM 对象的类型),REACT_CONTEXT_TYPE (React.createContext(1)返回的对象的类型),
REACT_FORWARD_REF_TYPE (React.forwardRef 创建的组件的类型),
REACT_SUSPENSE_TYPE(暂停组件的渲染, 直到某些操作的完成, 与 React.lazy 配置使用实现代码分割和懒加载),
REACT_LAZY_TYPE(React.azy())
REACT_MEMO_TYPE(React.memo()创建的组件,记忆组件渲染结果来避免不必要的重渲染),
REACT_SUSPENSE_LIST_TYPE(控制多个 Suspense 组件的加载顺序),
REACT_SCOPE_TYPE,REACT_ACTIVITY_TYPE与 React 18 的 useTransition 等特性的实现有关。
REACT_LEGACY_HIDDEN_TYPE,
REACT_TRACING_MAKER_TYPE,
{
$$typeof: REACT_ELEMENT_TYPE,
type: 'div',
props: {children: 'Hello'},
key: null,
ref: null,
...
}
react hooks 🔗
hook 是 react 16.8 的新增特性。
每个组件有一个 js 对象(fiber 对象)里存 hook 的链表。
//react/packages/react-reconciler/src/ReactFiberHooks.js
export type Hook = {
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: any,
next: Hook | null,
};
hooks 的实现原理 🔗
let _state;
const myUseState = (initialValue) => {
_state = _state === undefined ? initialValue : _state;
const setState = (newValue) => {
_state = newValue;
render();
};
return [_state, setState];
};
hooks 利用 js 的闭包实现。
let _state: any = [];
// 实际不能定义在全局,其他组件怎么办? 给每个组件创建一个_state数组和index, 并放在组件对应的Fiber Node上。
let index = 0;
const myUseState = (initialState: any) => {
const currentIndex = index;
_state[currentIndex] =
_state[currentIndex] === undefined ? initialState : _state[currentIndex];
const setState = (newValue: any) => {
_state[currentIndex] = newValue;
render(); // 重置index为0,
};
index += 1;
return [_state[currentIndex], setState];
};
为什么 react hooks 的使用规则中不能再循环,条件中调用 hook 函数了? 因为 hooks 函数的存储方式是数组(实际是链表), 加入控制流后,需要通过 index 获取对应的 state, setState 就关系就错了。
fiber 对象实现了状态更新机制,支持任务不同优先级,可中断与恢复。每个任务更新单元为 react element 对应的 fiber 节点。
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
) {
// 静态属性
this.tag = tag;
this.key = key;
this.stateNode = null; // 对应的DOM节点。
this.memoizedState = null; // 对应的hooks链表。
// 连接其他fiber节点形成Fiber树
this.return = null; // 父
this.child = null; // 子
this.sibling = null; // 兄
this.index = 0;
this.ref = null;
// 动态属性
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
// 保存更新时造成的DOM操作
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
// 更新时对应的fiber, 双缓存。 更新时指向workInProgress Fiber 节点。
this.alternate = null;
}
state management 🔗
useState(常用), useReducer, useSyncExternalStore
useState 如何更新状态? 为什么 setState 是异步的? 批量处理,延迟执行的异步机制。
effect hooks 🔗
useEffect(常用), useLayoutEffect, useInsertionEffect。
三种调用时机, 副作用清理, 常见bug,死循环
useEffect三种调用时机,取决于依赖数组的写法。 useEffect(fn) 首次和每次组件更新时调用, useEffect(fn, [])仅首次渲染时调用, useEffect(fn, [a, b]) 首次+依赖项变化时调用。
import React, {useEffect, useState} rom 'react';
import {Link} from 'react-router-dom';
function Demo() {
const [count, setCount] = useState(0);
useEffect(()=>{
console.log('1', count);
return () => {
console.log('2');
}
});
useEffect(() => {
console.log('3', count);
return () => {
console.log('4');
}
}, []);
useEffect(() => {
console.log('5', count);
return () => {
console.log('6');
}
}, [count]);
return (
<div>
<p>count is: {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<Link to="/management">About</Link>
</div>
);
}
export default Demo;
初始化时执行所有 useEffect 的主体函数。 状态更新时: 先执行依赖变化的 useEffect 的清理函数, 再执行主体函数。 组件卸载时: 执行清理函数。
初始化渲染(组件挂载) 10 30 50 点击 add 按钮 2 6 11 51 点击跳转按钮 2 4 6
ref hooks 🔗
useRef, useImperativeHandle
performance hooks 🔗
unnecessary in react 19 . useMemo, useCallback
context hooks 🔗
props 在使用时爷传父,父传子时,每一层都要写 props 中转一下。
useContext 则不用层层传递,可以让任意深度的子组件直接获取数据。
transition hooks 🔗
useTransition, useDeferredValue
random hooks 🔗
useDebugValue, useId
react 19 hooks 🔗
useFormStatus, useFormState, useOptimistic, use
单步调试 react 的入口 🔗
import App from "./App";
import { createRoot } from "react-dom/client";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);
cmd+shift+d 打开 vs code 调试面板,创建 launch.json 文件。
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "针对 localhost 启动 Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true,
"skipFiles": ["<node_internals>/**"]
}
]
}
运行和调试“针对 localhost 启动 Chrome”,即可 debug 程序。
原理 🔗
性能优化。
react compiler 🔗
2024 年在开源实验中。 学习一下 compiler 的设计与实现。
JSX, tsx 如何变成 DOM 的? 🔗
TSX ↓ 编译(TS → JSX → JS) React.createElement() ↓ Virtual DOM(纯 JS 对象) ↓ 构建 Fiber Tree(Render Phase) ↓ 找出差异、标记变化(effect list) ↓ 提交到真实 DOM(Commit Phase) ↓ 页面展示
react 与 vue 的对比 🔗
vue 中的 v-model 双向绑定很好用,react 需要 value + onChange 去编写。
props 部分 react 更加直观,就是 function 的参数而已。vue 需要额外定义。
子组件与父组件互相传数据。 props 向下传,事件向上传。
fiber 如何实现了异步可中断更新? 🔗
- 可中断可恢复
- 拆分更新任务为「单位工作单元」(unit of work)
- 有优先级(先渲染重要的任务)
- 并发调度(让多个任务排队执行)
while (nextUnitOfWork && shouldYield() === false) {
// shouldYield():是否让出主线程
// 拆分更新任务为「单位工作单元」(unit of work)
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
// 可中断、可恢复的渲染过程
从 Vite 启动到 performUnitOfWork 的路径 🔗
npm run dev → Vite 启动
↓
main.jsx 中执行 ReactDOM.createRoot(...).render(<App />)
↓
→ updateContainer
→ scheduleUpdateOnFiber
→ ensureRootIsScheduled
→ performConcurrentWorkOnRoot
→ renderRootConcurrent
→ workLoopConcurrent
→ performUnitOfWork(...)
Procomponents 🔗
npm i @ant-design/pro-components --save
shadcn 🔗
shadcn/ui, 基于 Tailwind CSS + Radix UI 的 React 组件库。
安装和使用 🔗
安装 tailwind css, 配置 Tailwind, npx tailwindcss init -p, 在 tailwind.config.js 里配置,
安装 shadcn/ui CLI, npx shadcn-ui@latest init, 默认会在 components/ui/ 下生成组件。
例如安装 button 组件。 npx shadcn-ui@latest add button, 把 Button.tsx 放到 components/ui/button.tsx 里。
问题 🔗
react 19 新特性 🔗
服务器组件。允许组件在 sever 端渲染,不用发送 js 到客户端。
状态管理。 合并 useState 批量处理。
react compiler,通过静态分析代码,自动实现类似 useMemo, useCallback 的缓存效果,减少不必要的重渲染。
reacthook 闭包陷阱 🔗
由于 javascript 的闭包特性,导致回调函数捕获了旧的状态 states,属性 props,而没有获取到最新值的现象。
解决办法, 明确依赖的数据,让 useEffect, useCallback 在依赖变化时,回调函数能重新创建,捕获到最新的数据。
react 中怎么给 children 添加额外的属性 🔗
React.cloneElement
import React from 'react';
function ParentComponent({children}) {
const enhancedChild = React.cloneElement(children, {
newProp: 'new prop',
onClick: () => console.log("子元素被点了");
className: 'enhanced-class', // 覆盖old value
});
return <div>{enhancedChild}</div>
}
function ChildComponent({newProp, ...props}) {
return <div {...props}>{newProp}</div>
}
<ParentComponent>
<ChildComponent className="orignal-class" />
</ParentComponent>
Fiber 怎么是 reac 性能的一个飞跃, fiber 架构的工作原理, fiber 如何实现更新过程可控 🔗
react 的 fiber 如何实现时间切片的? 🔗
react 的 reconciler 为什么采用 fiber 架构? 🔗
react 是否支持给标签设置自定义的属性,比如给 video 标签设置 webkit-playsinline? 🔗
自定义属性的效果取决于浏览器, React 仅将该属性传递到 DOM,不保证其功能生效。
react render 阶段的执行过程 🔗
react commit 阶段的执行过程 🔗
react 中的路由懒加载是什么?原理? 🔗
在访问特定路由时加载对应的组件代码,减少初始加载的 js 体积,提升加载速度。
动态 import, 动态导入语法,在运行时异步加载模块,返回一个 promise。
React.lazy, React 提供的高阶函数,接收一个返回 promise 的动态导入调用,返回一个可被 React 渲染的懒加载组件。
webpack 进行代码分割。
Suspense 配合 React.layz 使用, 处理加载状态,在懒加载组件加载完成之前显示占位内容。
react 中为什不直接使用 requestIdleCallback? 🔗
requestIdleCallback 是浏览器提供的 API,在空闲时间执行低优先级任务,执行时机不确定。React 的核心需求是可预测的调度。
react 组件之间怎么通讯? 🔗
react 组件之间通讯方式根据组件关系,父子,跨层级,兄弟等有所不同。
父子组件通讯 🔗
最基础的通讯方式, 通过 props 传递数据和回调函数传递事件实现。
父传子通过 props 传递数据
function Parent() {
const [message, setMessage] = useState("a message from parent");
return <Child content={message} />;
}
function Child({ content }) {
return <div>{content}</div>;
}
子传父:通过回调函数传递事件
function Parent() {
const handleChildClick = (data) => {
console.log("子组件传递的数据", data);
};
return <Child onSendData={handleChildClick} />;
}
function Child({ onSendData }) {
const sendData = () => {
onSendData("a message from child");
};
return <button onClick={sendData}>发送数据</button>;
}
跨层级组件通讯(爷孙,深层级)。用 Context API。
const ThemeContext = React.createContext();
function Grandparent() {
const [theme, setTheme] = useState("light");
return (
// 顶层组件提供数据
<ThemeContext.Provider value={{ theme, setTheme }}>
<Parent />
</ThemeContext.Provider>
);
}
function Parent() {
// 中间组件无需传递
return <Child />;
}
function Child() {
// 深层子组件消费数据
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<p> 当前主题: {theme} </p>
<button onClick={() => setTheme("dark")}> 切换主题</button>
</div>
);
}
兄弟组件通讯。通过共同父组件中转。
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<BrotherA onIncrement={() => setCount(count + 1)} />
<BrotherB count={count} />
</div>
);
}
function BrotherA({ onIncrement }) {
return <button onClick={onIncrement}>增加</button>;
}
function BrotherB({ count }) {
return <div> 当前值: {count} </div>;
}
非关联组件通讯(全局状态管理)。 zustand。
react 和 vue 在技术层面上有哪些区别? 🔗
UI=f(state)。
| 维度 | React | Vue |
|---|---|---|
| 设计理念 | 单向数据流 | 渐进式,响应式 |
| 模版语法 | JSX = JS 中写 html | 独立模版+指令(html 中写逻辑) |
| 响应式原理 | 手动触发更新 | 数据劫持 Proxy+依赖收集 |
子组件是一个 protal, 发生点击事件能冒泡到父组件里吗? 🔗
useState 是如何实现的? 🔗
react 中 diff 算法 🔗
如何让 useEffect 支持 async,await? 🔗
react 中懒加载的实现原理是? 🔗
React.lazy(),
组件懒加载,
路由懒加载,
图片懒加载,
数据懒加载。
react 中怎么实现状态自动保存 keepalive? 🔗
表单填了一半,切换到其他页面再回来,输入的内容还在。
一,利用父组件+条件渲染, 将子组件的状态提升到父组件中。
二,路由场景时,使用 react router + 状态持久化, react-activation, 专门设计的 keepalive 库。
三,持久化到 localStorage
四,全局状态管理
react 有哪些性能优化的方法? 🔗
减少不必要的渲染 🔗
使用 React.memo 缓存组件,useMemo 缓存计算结果,useCallback 缓存函数引用。
拆分成独立小组件,使得状态变化仅影响局部。
优化列表渲染 🔗
给列表项添加唯一 key, 减少 react 虚拟 dom 的对比开销。
使用虚拟列表只渲染可视区域。
状态管理优化
事件与副作用优化 🔗
防抖,节流高频事件,清理副作用(清理 timer 防止内存泄漏)
资源加载优化 🔗
通过 React.lazy, Suspense 实现按需加载,减少初始加载体积。
react 中 Virtual DOM 一定提高性能吗? 🔗
当组件状态变化时,先构建新的 virtual dom 树,通过 diff 算法对比新旧 virtual dom 树,计算出最小变更,将变更批量应用到真实 dom 上。
react 的事件代理机制? 🔗
react 会在根节点统一注册事件监听器, 事件冒泡到根节点,根据事件源和组件树找到组件和事件处理函数并执行。
reack hooks 当中的 useEffect 是如何区分声明周期钩子的 🔗
通过依赖数组来模拟不同的生命周期行为,统一了类组件中的 componentDidMount, componentDidUpdate, componentWillUnmount 三个生命周期方法。
无依赖数组,组件挂载后执行一次,后续每次组件更新时重新执行。模拟了 componentDidMount, componentDidUpdate。
空依赖数据(空数组[]), 组件只在挂载后执行一次。模拟了 componentDidMount。
有值的依赖数组, 组件挂载后执行一次,依赖数据更新后执行一次。 模拟了 componentDidMount, componentDidUpdate。
清理函数, useEffect( return () => { return ()=>{...} }, [deps] ) 模拟 componentWillUnmount, 清理函数在组件卸载前,或者下次 effect 执行前触发,清理定时器,事件监听等。
一个 api 覆盖了类组件中的多个生命周期的功能, 让副作用逻辑的组织更清晰。
useEffect 的闭包陷阱。 依赖数组必须包含 effect 中使用的外部变量, state, props, 函数。遗漏依赖导致了读到久值。使用 ESLint 规则自动检测声明的依赖。
react 中怎么实现组件间的过渡动画? 🔗
react-transition-group 库。
react 服务端渲染怎么做 🔗
SSR 是在服务器端将 React 组件渲染成 HTML 字符串,发送给客户端的技术。 服务端使用 ReactDOMServer.renderToString(), renderToPipeableStream() 将组件渲染为 html。 客户端使用 ReactDOM.hydrateRoot()激活 HTML,使其变为可交互的 React 应用。
Next.js, SSR 框架。Remix,全栈 react 框架。
什么情况下使用 useMemo,usecallback 🔗
useMemo 缓存计算结果。 useCallback 缓存函数引用。
react 的 memo 和 memoize 函数的区别 🔗
react 的 memo 对于纯展示的组件(仅依赖 props 渲染, 无内部状态和副作用),减少渲染。
const MemoizedComponent = React.memo(({ name, age }) => {
return (
<div>
{name}-{age}
</div>
);
});
useRef, ref, forwardsRef 的区别? 🔗
ref 是一个属性用于获取 DOM 元素的引用。
useRef 是 React Hook, 访问 DOM 元素。特点是存储的值发生变化不会导致组件重新渲染。
forwardsRef 是函数,用于在函数组件间转发引用。 将父组件的 ref 转发到子组件中的 dom 元素上,解决函数组件不能直接接收 ref 属性的问题,使得父组件能够访问子组件内部的 dom 元素。 forwardsRef 必须配合 ref 属性使用。父组件传递的 ref 可以是 useRef 创建的 ref 对象,也可以是回调形式的 ref(如 (el)=> {...})。
回调式 ref 自动执行。
const Child = forwardsRef((props, ref)=>{
return <input ref={ref} />;
});
const Parent() {
const handleRef = (el) => {
if (el) el.focus(); // 直接操作DOM
};
return <Child ref={handleRef} />; //当子组件的dom元素被挂载到页面时,将该dom元素作为el传入,并执行。
}
最常见的是 forwardsRef 与 useRef 一起配合使用。组件挂载之后 ref.current 被赋值为 DOM 元素。
const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);
function ParentComponent() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<>
<FancyInput ref={intupRef} />
<button onClick={handleClick}> Focus Input </button>
</>
);
}
对 immutable 的理解,如何应用到项目上 🔗
实现 useTimeout 的 hook 🔗
怎么获取函数组件的实例? 🔗
函数组件本质是纯函数,react 不会创建其实例。
- forwardRef 让函数组件能接受父组件传递的 ref
- useImperativeHandle 自定义通过 ref 暴露的内容。
import { useRef, useImperativeHandle, forwardRef } from "react";
const childComponent = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const inputRef = useRef(null);
const increment = () => {
setCount((prev) => prev + 1);
};
useImperativeHandle(
ref,
() => ({
getCount: () => count,
triggerIncrement: increment,
focusInput: () => inputRef.current.focus(),
}),
[count]
);
return (
<div>
<p>计数:{count}</p>
<input ref={inputRef} />
</div>
);
});
const ParentComponent = () => {
const childRef = useRef(null);
const handleClick = () => {
if (childRef.cuurent) {
childRef.current.triggerIncrement();
console.log("当前计数:", childRef.current.getCount());
childRef.current.focusInput();
}
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>操作子组件</button>
</div>
);
};
react 项目如何捕获错误? 🔗
分层捕获, 局部错误 try, catch。组件树错误用 ErrorBoundary。全局错误 unhandledrejection。
错误边界 error boundaries 可以捕获子组件树中的 js 错误,展示降级 UI(fallback UI),不让整个 UI 组件树崩溃。
受保护的路由 protected route 🔗
检查用户是否满足特定条件(如登录),才允许访问其下的组件和页面。
react 中引入 css 的方式有几种? 🔗
全局 css。创建 global.css 文件,在入口文件中引入 index.js,在组件中使用。
模块化 css。将 css 文件命名为文件名.module.css,使样式仅作用于引入它的组件。
Sass, SCSS 预处理器。
内联样式 style 属性上定义样式。
CSS 框架 Tailwind CSS。
单应用如何提高加载速度? 🔗
资源体积优化。 🔗
代码分割(code splitting), 将代码按路由或组件拆分,只加载当前页面所需的代码。原理: webpack 等构建工具会将动态导入的模块拆分为独立 chunk,访问对应路由时才加载。
压缩与混淆。jss,css 压缩, tree shaking,图片 webp,avif。
首屏渲染优化 🔗
SSR 通过 next.js 在服务端生成 HTML,首屏直接返回渲染好的内容。
运行时优化 🔗
避免长任务阻塞主线程。将复杂计算拆分到 web worker 中, 使用 requestIdleCallback 处理非紧急任务。
虚拟列表。当渲染大量数据(1000 条+列表),只渲染可视区域内的。
import { FixedSizeList } from "react-window";
function BigList({ items }) {
return (
<FixedSizeList
height={500}
width="100%"
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => <div style={style}> {items[index].name} </div>}
</FixedSizeList>
);
}
能否直接将 props 的值复制给 state? 🔗
可以将 props 的值复制给 state。
如果需要将 props 复制到 state 且保持同步, 需要用 useEffect 监听 props 变化并更新 state。
react-router 中的 link 和 a 标签的区别? 🔗
都用于导航。
| 特点 | link 标签 | a 标签 |
|---|---|---|
| 导航方式 | 不刷新页面 | 会刷新页面 |
| URL 变化处理 | html5 history api 改 url | 触发页面跳转,发送新 http 请求 |
单页面应用 SPA 的核心是在一个页面中完成所有交互, link 标签为此而设计。 a 标签更适合外部链接,非 SPA 场景。
react 的 JSx 转换真实 DOM 的过程 🔗
react 的事件代理机制和原生事件绑定的区别? 🔗
react 中 Element, component,Node, Instance 🔗
React 元素是一个不可变的 javascript 对象,格式为{type, props: {attribute: "x", children: ""}, key, ref}
React 组件是函数,封装了 UI 逻辑和状态, 接收 props 并返回 React 元素。
React Node 更宽泛, 是渲染中的任何值,可以是 react 元素,字符串,数字, 数组等。 react 的 render 方法返回了 react node。
React Instance 类组件实例化后的对象,函数组件无实例。
全局状态管理方案 🔗
context是 react 内置的,但是 context 值变化时,所有消费者组件会重新渲染。
Redux, Zustand 的 api 简单上手快。
状态提升 🔗
虚拟列表的实现 🔗
代码分割在 react 中的实现方式 🔗
题目 🔗
react 的合成事件 🔗
React 为了屏蔽不同浏览器之间事件系统的差异,自己实现了一套“事件系统”, 在这套系统中,所有事件对象都不是浏览器原生事件对象,而是 React 封装的 合成事件对象 —— SyntheticEvent。
例如 在 JSX 里写 onClick={handleClick} 时,React 实际上不是直接绑定到 button 的原生 click 事件。
React 不在每个 DOM 上都绑定事件,而是把所有事件绑定在根节点(如 document 或 React Root)上,通过事件冒泡统一调度。
function App() {
function handleClick(e) {
console.log(e); // SyntheticEvent
console.log(e.nativeEvent); // 原生事件
}
return <button onClick={handleClick}>点我</button>;
}
事件池。
填充 type, target, nativeEvent, preventDefault() 等属性;
react 源码 🔗
下载源码,查看 package.json,查看构建的脚本。
源码包 🔗
packages/react, packages/react-dom, packages/react-reconciler, packages/schduler
jsx 的编译 🔗
@babel/plugin-transform-react-jsx 编译为普通 js 代码, 如React.createElement的代码。
createElement
串流程 🔗
初始化流程, ReactDOM.render, createRoot
ReactElement 上有 ower 属性去指向 fiber
数据更新流程,setState,
scheduler 中的 minheap 实现优点队列的调度。
实现简单版的 reactfiber 🔗
const taskQueue = [];
const rootFiber = null;
const currentFiber = null;
// 创建第一个fiber
function createFiber(element, parentFiber) {
return {
element,
parent: parentFiber,
child: null,
sibing: null,
};
}
//
function updateFiberTree() {
if (!rootFiber) {
rootFiber = createFiber("app", null);
currentFiber = rootFiber;
}
while (taskQueue.length > 0) {
const task = taskQueu.shift();
}
}
function performUnitOfWork(fiber) {
// 执行当前任务
const elements = fiber.element;
if (elements && elements.length > 0) {
let child = null;
elements.forEach((element, index) => {
const newFiber = createFiber(element, fiber);
if (index == 0) {
fiber.child = newFiber;
} else {
child.sibing = newFiber;
}
child = newFiber;
});
}
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibing) {
return nextFiber.sibing;
}
nextFiber = nextFiber.parent;
}
return null;
// 返回下一个任务
}
function commitRoot(finishWork: Fiber) void {
commitWork(rootFiber.child);
currentFiber = null;
}
function commitWork(fiber) void {
if(!fiber) {
return;
}
// 渲染
console.log(fiber.element);
commitWork(fiber.child);
commitWork(fiber.sibing);
}
// 将任务添加到任务队列
function schduler(task) {
taskQueue.push(task);
}
// 模拟任务
const exampleTask = {
element: ["div", "p", "span"],
};
// 启动
schduler(exampleTask);
updateFiberTree();
从项目启动过程到状态更新过程(useState) 🔗
从零开始的 React 项目 Demo 🔗
src/
main.jsx
App.jsx
main.jxs 如下:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
App.jsx 如下:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
React 项目启动过程(从加载 → Fiber 构建 → 首次渲染) 🔗
// 管理对象(FiberRoot)
const fiberRoot = {
containerInfo: document.getElementById('root'), // React 挂载的 DOM 容器
current: rootFiber, // 指向当前的根 Fiber(两者互相引用)
pendingLanes: 0, // 调度相关的元信息(lane)
callbackNode: null, // 当前调度回调的引用
finishedWork: null, // render 完成后要 commit 的 fiber 链表
// ...还有其他调度/队列字段
};
// 伪代码:Fiber 节点(简化)
const rootFiber = {
tag: "HostRoot", // 根节点类型
child: ..., // 子 Fiber 链表
sibling: null,
return: null,
stateNode: null, // <-- 会指向 FiberRoot(管理对象)
updateQueue: {...}, // 根节点的更新队列(也可以放在 FiberRoot)
...otherFiberFields
};
// 两者互相关联
rootFiber.stateNode = fiberRoot;
fiberRoot.current = rootFiber;
下面按真实 React 内部流程来走,包括 performWork, beginWork, completeWork。
- 浏览器加载 main.jsx → 执行 ReactDOM.createRoot()
createRoot创建 RootFiber
- root.render(
) → 创建 Update 对象,放入 UpdateQueue
render(performConcurrentWorkOnRoot。
- 进入 Fiber 构建(渲染阶段) render phase(可中断),构建 Fiber Tree(虚拟 DOM 的 Fiber 树)。 commit phase(不可中断)把 Fiber 变成真实 DOM。
render 阶段 — performUnitOfWork
根 Fiber 开始:
performUnitOfWork(rootFiber)
→ beginWork
→ return child (App Fiber)
performUnitOfWork(AppFiber)
→ beginWork
→ return child (div Fiber)
performUnitOfWork(divFiber)
→ beginWork
→ return child (p Fiber)
...
beginWork 做什么?根据 props/状态 生成子 Fiber。 beginWork(AppFiber)
a, 执行 App()
b,进入 React hooks dispatcher
c,useState(0) 产生:hook 对象(链表),初始 state = 0
d,返回 React Element 结构:
<div>
<p>0</p>
<button>add</button>
</div>
f,React 根据 element 生成子 Fiber
completeWork 做什么?每个 Fiber 的 completeWork 会处理:创建 DOM 节点(首次渲染),收集 effectList(要执行的 DOM 操作)。
最终 rootFiber 会带着一串 DOM effectList 进入 commit 阶段。
commit 阶段(不可中断,也是唯一操作 DOM 的地方) 🔗
三个小阶段:before mutation(ref 清理等),mutation(把 DOM 插入页面!最关键),layout(调用 useLayoutEffect)。
最终浏览器看到页面中的:
0
[add]
React Hooks 的核心实现机制 🔗
ReactFiberHooks.js
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
所有 useState、useEffect、useMemo 等 hook 都是通过ReactSharedInternals.H来调用的。
每个函数组件 Fiber 节点上都有:fiber.memoizedState, 存放了类似useState → useEffect → useMemo → useRef → ...。
hook 节点如下:
{
memoizedState: 0, // 对 useState = 初始值
queue: {...}, // 对 useState = updateQueue
next: HookNode, // 下一个 hook
baseState: any,
baseQueue: Update<any, any> | null,
}
React 为什么“只能在顶层调用 hook”, 每次 render 为什么所有 hook 必须按顺序调用?
✔ 初次渲染(HooksDispatcherOnMount)
- 为每个 hook 创建
fiber.memoizedState节点(hook 链表) - 为 useState 创建新的 state、queue
- 为 useEffect 建立 effect 链表
- 为每个 hook 分配一个“链表节点”
✔ 更新渲染(HooksDispatcherOnUpdate)
- 不再创建新节点,而是“按顺序取出”旧的 hook 节点
- useState 取出旧 state 并应用 update 队列
- useEffect diff 依赖并决定是否重新执行 effect
- mount / update 时为什么有两个不同的 dispatcher
current.memoizedState 是怎样保存 useState 状态链表的
React 状态更新过程(useState)原理 🔗
当点击onClick={() => setCount(count + 1)}时,触发 setCount 的内部流程。
- setState 不是立即更新 State,它创建 Update 对象。
setCount(newValue) 实际上做的是:
- 创建更新对象 { payload: newValue }
- 加入 Fiber.updateQueue
- 调度 render 任务
状态并没有立即变。
- 调度器(Scheduler)开始一次新的渲染任务 scheduler 再次启动:
performConcurrentWorkOnRoot
→ render phase(beginWork → completeWork)
→ commit phase
更新阶段与首次渲染的区别 🔗
首次渲染 beginWork(FunctionComponent)
- 运行 App()
- 创建 hook 链表
- 记录 initialState
更新时 beginWork(FunctionComponent)
- 运行 App()
- 遍历 hook 链表
- useState 会:oldState + updateQueue → newState. 每次 setState 都会让 updateQueue 参与计算。
commitRootImpl 🔗
整个渲染流程的“最后一步”—— Commit 阶段的核心入口。要完成所有 DOM 更新、同步/异步 Effect、生命周期回调、commit 事件等。
x
完整总结流程 🔗
首次启动:
createRoot → 创建 rootFiber
root.render() → 为 rootFiber 创建 update
Scheduler 调度任务
render phase, beginWork:执行 App(),构建 Fiber, completeWork:创建 DOM
commit phase:把 DOM 插入页面
状态更新:
setState → 创建 update → 放进 updateQueue
Scheduler 调度 render
render phase, beginWork:重新执行 App(), useState 读取旧 state + update 得到 new state, 生成新的 Fiber
commit phase, 对比 fiber → 更新 DOM(只更新变化的地方)
react 中涉及到的核心算法和设计 🔗
Diff 算法在reconcileChildrenArray, 只进行同层对比,不同类型的元素产生不同的树reconcileSingleElement,通过 key 稳定标识mapRemainingChildren。
在工程上对 web 应用的观察发现: 很少出现 DOM 元素跨层级移动;如果改变了组件类型,UI 的内容发生根本性变化,没必要细比下去。
Fiber 架构,模拟任务切片的堆栈。 把 react 元素变为工作单元(就是一个个 Fiber), 支持中断和恢复, 可以执行 5ms 的渲染逻辑,之后把控制权还给浏览器。
双缓存机制,同时存在 current tree 和 workInProgress tree, 所有的 diff 和计算在 workInProgress 上完成,之后一次替换到屏幕上。
参考 🔗
react 的官网 doc