71、React组件返回值
、在 React 中,组件的返回值取决于组件的类型,通常有以下几种情况:
1. 函数组件的返回值
函数组件返回的是 JSX 或 null
:
const MyComponent = () => {return <div>Hello, React!</div>;
};
如果需要条件渲染,可以返回 null
:
const MyComponent = ({ show }) => {return show ? <div>Visible</div> : null;
};
2. 类组件的返回值
类组件的 render
方法返回 JSX:
class MyComponent extends React.Component {render() {return <div>Hello, Class Component!</div>;}
}
同样可以返回 null
:
class MyComponent extends React.Component {render() {return null;}
}
3. Fragment(片段)
React 允许返回多个元素而不需要额外的 DOM 包裹:
const MyComponent = () => {return (<><h1>Title</h1><p>Description</p></>);
};
<></>
是 React.Fragment
的简写。
4. 数组(Fragments 之外的多元素返回)
可以返回数组来渲染多个元素:
const MyComponent = () => {return [<li key="1">Item 1</li>,<li key="2">Item 2</li>,<li key="3">Item 3</li>,];
};
5. React.createElement
JSX 最终会被转换成 React.createElement
:
const MyComponent = () => {return React.createElement('div', null, 'Hello, React!');
};
6. Portal
如果需要将组件渲染到 document.body
之外的 DOM 节点,可以返回 ReactDOM.createPortal
:
import ReactDOM from 'react-dom';const MyComponent = () => {return ReactDOM.createPortal(<div>Portal Content</div>,document.getElementById('portal-root'));
};
7. Suspense
如果组件使用 React.Suspense
进行懒加载,它的返回值可能是 Suspense
组件:
import React, { Suspense, lazy } from 'react';const LazyComponent = lazy(() => import('./LazyComponent'));const MyComponent = () => {return (<Suspense fallback={<div>Loading...</div>}><LazyComponent /></Suspense>);
};
总结
React 组件的返回值通常是:
- JSX 元素(
<div>...</div>
) - React.Fragment(
<></>
或React.Fragment
) - 数组(必须包含
key
) - null(用于条件渲染)
- React.createElement(底层实现)
- Portal(使用
ReactDOM.createPortal
) - Suspense 组件(用于懒加载)
这些返回值决定了组件如何在 React 组件树中渲染。
72.monorepo是什么用来做什么的
Monorepo是“monolithic repository”的缩写,也就是将多个项目或模块放在同一个代码仓库中。这和传统的每个项目一个仓库的方式不同。
为什么需要monorepo
大型项目、多包管理的库(如Babel、React)、需要跨团队协作的情况。
对比传统的多仓库方式,比如代码重复、依赖管理复杂、协作效率低等问题。Monorepo如何解决这些问题,比如共享代码、统一构建工具、简化依赖管理等。
Monorepo(单一代码仓库)是一种将多个项目或模块存储在同一个代码仓库中的代码管理策略。它的核心目标是简化跨项目协作、共享代码和统一工程化流程,尤其适合中大型团队或复杂项目。以下是其核心概念和用途的详细解析:
一、Monorepo 的核心特点
-
统一代码库:
-
所有相关项目(如前端、后端、工具库)存放在同一仓库中。
-
示例目录结构:
monorepo/├── apps/│ ├── web-app/ # 前端应用│ └── mobile-app/ # 移动端应用├── packages/│ ├── utils/ # 公共工具库│ └── ui-kit/ # 共享 UI 组件库└── package.json # 根目录配置
-
-
共享依赖与代码:
- 模块间可直接引用本地代码,无需发布到包管理器(如 npm)。
- 避免代码重复,例如多个项目共用工具函数或 UI 组件。
-
统一工具链:
- 集中管理构建、测试、代码规范等配置(如 ESLint、TypeScript、Jest)。
- 一键执行跨项目操作(如同时构建所有应用)。
-
原子化提交(Atomic Commits) :
- 跨项目的代码变更可在一次提交中完成,确保版本一致性。
- 便于追踪代码变更的影响范围。
二、Monorepo 的典型使用场景
-
多应用共享代码:
- 例如:Web 端、移动端、后台管理系统共用同一套业务逻辑或组件库。
-
微前端架构:
- 多个子应用(Micro Frontends)在同一个仓库中开发,便于协调和集成。
-
多包管理(Monorepo 的经典场景) :
- 开源项目如 Babel、React、Vue 3 使用 Monorepo 管理核心库、插件、文档等。
- 示例:React 仓库包含
react
、react-dom
、scheduler
等包。
-
全栈项目:
- 前后端代码同仓,方便接口联调和数据模型同步。
三、Monorepo 的优势
优势 | 说明 |
---|---|
代码复用 | 跨项目共享代码,减少重复开发(如工具函数、组件、配置)。 |
依赖管理简化 | 统一管理第三方依赖版本,避免版本冲突。 |
跨项目重构 | 修改公共代码后,可立即验证所有依赖项目,降低破坏性变更风险。 |
统一 CI/CD | 集中配置构建、测试、部署流程,提升自动化效率。 |
协作效率提升 | 开发者更容易理解项目全貌,减少跨仓库沟通成本。 |
四、Monorepo 的挑战与解决方案
挑战 | 解决方案 |
---|---|
仓库体积膨胀 | 使用 Git 稀疏检出(Sparse Checkout)或按需克隆。 |
构建性能 | 增量构建(如 Turborepo)、缓存优化、任务并行化。 |
权限管理 | 通过目录级权限控制(如 Git Submodules 或自定义工具)。 |
工具链复杂度 | 使用成熟 Monorepo 工具(如 Nx、Turborepo、Rush)。 |
五、主流 Monorepo 工具
-
Lerna:
- 传统 Monorepo 工具,适合多包发布(如 Babel 使用 Lerna)。
- 需配合 Yarn Workspaces 或 npm Workspaces 使用。
-
Nx:
- 功能强大,支持增量构建、依赖图可视化、分布式任务执行。
- 适合大型企业级项目。
-
Turborepo:
- 高性能构建缓存,无缝集成主流框架(Next.js、React Native)。
- 配置简单,适合中小型项目。
-
Yarn/PNPM Workspaces:
- 内置的轻量级 Monorepo 支持,管理依赖和跨包链接。
六、何时选择 Monorepo?
-
适合场景:
- 项目间高度耦合,需要频繁共享代码。
- 团队规模较大,需统一开发规范。
- 希望简化依赖管理和跨项目协作。
-
不适合场景:
- 小型独立项目,无需代码共享。
- 团队技术栈差异大,难以统一工具链。
七、示例:React 的 Monorepo 结构
React 的仓库是经典案例,其结构如下:
react/├── packages/│ ├── react/ # React 核心│ ├── react-dom/ # DOM 渲染器│ ├── scheduler/ # 调度器│ └── ... # 其他子包├── scripts/ # 构建脚本└── package.json # 根目录配置
通过 Monorepo,React 团队可以同步更新核心库与相关模块,确保版本一致性。
总结
Monorepo 通过集中化管理和代码共享,解决了多仓库协作的碎片化问题,尤其适合复杂项目或需要高度协作的团队。选择时需权衡项目规模、工具链支持与团队能力,合理使用工具(如 Turborepo 或 Nx)可大幅降低维护成本。
73.React中什么叫并发渲染具体表现是什么
并发渲染(Concurrent Rendering)是 React 18 引入的核心特性,它通过可中断的异步渲染机制,让 React 应用在处理复杂更新时保持高响应性。以下是其核心原理与具体表现:
并发渲染的本质是让 React 具备多任务协作能力,
并发渲染通过时间切片,任务 优先级调度 ,可中断渲染,实现react的多任务协作能力,将多个任务实习切片,并按照优先级渲染,在处理高优先级人物时可以中断渲染
一、并发渲染的核心机制
-
时间切片(Time Slicing)
- 将渲染任务拆分为多个可中断的微任务块(通常每块 5ms 左右)。
- 浏览器主线程在每块任务之间可以处理更高优先级的操作(如用户点击、动画)。
-
任务优先级调度
- 通过 Lane 模型 区分更新优先级(如用户输入 > 数据加载 > 非紧急渲染)。
- 高优先级任务可“插队”低优先级任务,避免界面卡顿。
-
可中断的渲染流程
- 渲染过程(Render Phase)可被中断,React 会暂存中间状态,后续恢复时继续处理。
- 提交阶段(Commit Phase)保持同步,确保 DOM 更新原子性。
二、具体表现
1. 更流畅的交互体验
-
输入响应优先
当用户在输入框打字时,React 会优先处理输入事件,延迟其他低优先级渲染(如搜索结果列表的更新),确保输入不卡顿。 -
示例:
function SearchBox() {const [query, setQuery] = useState('');const deferredQuery = useDeferredValue(query); // 延迟更新return (<><input value={query} onChange={(e) => setQuery(e.target.value)} /><Results query={deferredQuery} /> // 低优先级渲染</>); }
2. 后台预渲染
-
提前准备 UI
在用户执行可能触发渲染的操作前(如悬停按钮),React 可提前在后台渲染内容,提升后续操作的响应速度。 -
示例:
function App() {const [tab, setTab] = useState('home');return (<div><button onMouseOver={() => startTransition(() => setTab('about'))}>About</button><Suspense fallback={<Loader />}><Content tab={tab} /></Suspense></div>); }
3. 批量非紧急更新
-
合并低优先级更新
通过startTransition
或useDeferredValue
将非紧急更新(如搜索建议、图表重绘)标记为“可延迟”,避免阻塞关键渲染。 -
示例:
const [input, setInput] = useState(''); const [search, setSearch] = useState('');const handleInput = (text) => {setInput(text); // 高优先级:立即更新输入框startTransition(() => {setSearch(text); // 低优先级:延迟更新搜索结果}); };
4. 更智能的 Suspense 配合
- 流式加载与错误边界
结合Suspense
,并发渲染可实现逐步加载组件或数据,优先显示已就绪内容(如骨架屏),同时处理加载状态和错误。
三、与传统同步渲染的对比
场景 | 同步渲染 | 并发渲染 |
---|---|---|
用户输入时触发渲染 | 输入卡顿,渲染阻塞事件循环 | 输入流畅,渲染任务被中断或延迟 |
同时处理多个更新 | 按顺序执行,可能产生界面冻结 | 按优先级调度,高优先级任务优先 |
复杂组件树渲染 | 长时间占用主线程,导致掉帧 | 分片执行,保持主线程可响应 |
四、开发者如何利用并发渲染
-
使用并发特性 API
startTransition
:标记非紧急更新。useTransition
:获取过渡状态(如加载指示器)。useDeferredValue
:延迟派生值的更新。
-
优化组件性能
- 通过
React.memo
或useMemo
减少不必要的渲染。 - 拆分大型组件树,利用
Suspense
分块加载。
- 通过
-
避免阻塞渲染
- 避免在渲染阶段执行耗时操作(如复杂计算、同步网络请求)。
五、底层实现:Fiber 架构
- 链表结构:Fiber 节点构成双向链表,支持渲染过程的暂停与恢复。
- 工作循环:通过
requestIdleCallback
(或 polyfill)在浏览器空闲时段执行任务。 - 优先级标记:使用 31 位的 Lane 模型精细控制任务调度。
总结
并发渲染的本质是让 React 具备多任务协作能力,通过智能调度渲染任务,在保持界面流畅的同时处理复杂更新。其表现包括:
- 用户交互无卡顿
- 高优先级任务即时响应
- 后台预加载与延迟渲染
- 更平滑的过渡效果
74.React Reconciler 协调器
React Reconciler是React内部的一个核心部分,负责协调(reconciliation)过程。协调是React用来比较新旧虚拟DOM树,找出差异并高效更新实际DOM的算法。React的协调器不仅仅处理虚拟DOM,还涉及到组件的生命周期、状态更新等。
Reconciler的主要职责。可能包括:
- 虚拟DOM的创建和更新:当组件的状态或props变化时,Reconciler会生成新的虚拟DOM树,并与旧的进行比较,找出需要更新的部分。
- Diff算法:这是协调过程中的关键,用来高效地找出差异。React的Diff算法基于两个假设:不同类型的元素会产生不同的树,以及通过key属性来识别稳定的子元素。
- 调度更新:React可能会将更新分成多个小任务,使用时间分片(time slicing)和优先级调度(如并发模式中的Suspense)来避免阻塞主线程,保持应用的响应性。
- 副作用处理:比如处理组件的生命周期方法(componentDidMount, componentDidUpdate等),或者函数组件中的useEffect钩子。
- 当setState被调用时,Reconciler会创建一个新的虚拟DOM树,然后与旧的进行比较,找出需要更新的节点,生成一个副作用列表(effect list),然后提交(commit)这些变更到实际的DOM中。
- 调度器(Scheduler)和Reconciler。调度器负责安排任务的优先级和执行时机,而Reconciler负责处理组件的更新和协调。
- React Reconciler 是 React 的核心模块之一,负责协调(Reconciliation)过程,即高效更新 UI 的关键机制。以下是其核心要点:
1. 核心职责
-
虚拟 DOM 的协调:当组件状态或 props 变化时,Reconciler 生成新的虚拟 DOM 树,并与旧树对比(Diff 算法),找出最小变更集。
-
Diff 算法优化:基于两个假设:
- 类型差异:不同类型的元素(如
<div>
变为<span>
)会销毁旧子树并重建。 - Key 稳定性:通过
key
标识子元素,减少不必要的节点复用错误。
- 类型差异:不同类型的元素(如
-
调度与优先级:在并发模式下,将更新拆分为可中断的“任务”,优先处理高优先级更新(如用户交互),避免阻塞主线程。
-
副作用管理:处理生命周期(如
componentDidUpdate
)和 Hook 副作用(如useEffect
),生成并提交变更到真实 DOM。
2. Fiber 架构
React 16 引入的 Fiber 重构了 Reconciler,使其支持:
- 增量渲染:将渲染拆分为多个可中断/恢复的“工作单元”(Fiber 节点),提升大型应用响应速度。
- 并发模式:通过时间分片(Time Slicing)和任务优先级(Lane 模型)实现异步可中断更新。
- 双向链表结构:Fiber 节点构成链表,支持深度优先遍历的暂停与回溯,优化协调过程。
3. 协调流程
-
Render 阶段(可中断):
- 对比新旧 Fiber 树,生成副作用列表(如插入、更新、删除)。
- 使用 Diff 算法标记变更,不直接操作 DOM。
-
Commit 阶段(不可中断):
- 遍历副作用列表,批量更新真实 DOM。
- 触发生命周期和 Hook 的副作用(如
useLayoutEffect
)。
4. 与渲染器解耦
- Reconciler 是平台无关的抽象层,与具体渲染器(如 React DOM、React Native)分离。
- 渲染器负责将 Reconciler 的指令转换为平台特定操作(如 DOM 修改或原生组件更新)。
5. 性能优化
- Key 的合理使用:列表渲染时,唯一且稳定的
key
减少不必要的元素重建。 - 批量更新:合并多次
setState
,避免重复渲染。 - 组件优化:通过
React.memo
、shouldComponentUpdate
跳过无关组件的协调。
示例:Key 的重要性
// 无 key:列表顺序变化可能导致状态错乱(如输入框内容)
{items.map(item => <div>{item.text}</div>)}// 有 key:React 正确识别元素,优化复用
{items.map(item => <div key={item.id}>{item.text}</div>)}
总结
React Reconciler 通过虚拟 DOM 的智能对比、Fiber 的异步调度机制,以及跨平台渲染的抽象,实现了高效、灵活的 UI 更新。理解其原理有助于编写高性能 React 应用(如合理使用 key
、优化渲染逻辑)。
75.React——Scheduler调度器
在 React 中,Scheduler 是一个用于协调任务调度的库,它管理不同优先级的任务,以确保 React 在主线程上的渲染不会阻塞关键用户交互。它是 React 并发模式(Concurrent Mode)的核心部分。
1. Scheduler 的作用
Scheduler 主要用于任务优先级管理,它可以:
- 让高优先级任务(如用户输入)优先执行
- 让低优先级任务(如页面渲染)延后执行
- 让任务分批执行,防止长时间阻塞主线程
2. Scheduler API
React 通过 scheduler
包提供了一些 API 来控制任务的调度:
(1)scheduleCallback
用于调度一个任务,并根据优先级决定何时执行:
import { unstable_scheduleCallback, unstable_NormalPriority } from 'scheduler';unstable_scheduleCallback(unstable_NormalPriority, () => {console.log('执行一个普通优先级的任务');
});
可选优先级:
unstable_ImmediatePriority
(最高,立即执行)unstable_UserBlockingPriority
(次高,用户交互)unstable_NormalPriority
(默认)unstable_LowPriority
(低)unstable_IdlePriority
(最低,浏览器空闲时执行)
(2)shouldYield
用于检查是否应该让出控制权,避免长时间占用主线程:
import { unstable_shouldYield } from 'scheduler';function heavyTask() {while (true) {if (unstable_shouldYield()) {console.log('主线程需要执行更高优先级任务,暂停当前任务');break;}// 执行计算任务...}
}
(3)getCurrentPriorityLevel
获取当前执行任务的优先级:
import { unstable_getCurrentPriorityLevel } from 'scheduler';console.log(unstable_getCurrentPriorityLevel()); // 返回当前任务的优先级
3. Scheduler 在 React 中的应用
(1)React 并发模式(Concurrent Mode)
Scheduler 主要用于 React 并发模式,以支持时间切片(Time Slicing) ,使得 React 渲染不会长时间阻塞主线程。例如:
function handleClick() {React.startTransition(() => {setState(expensiveComputation());});
}
在 startTransition
内部,React 可能会使用 Scheduler 让任务分批执行。
(2)提高用户体验
Scheduler 可以让高优先级任务(如输入响应)优先执行,避免 UI 卡顿。例如:
import { unstable_scheduleCallback, unstable_UserBlockingPriority } from 'scheduler';input.addEventListener('input', (event) => {unstable_scheduleCallback(unstable_UserBlockingPriority, () => {console.log('处理输入事件');});
});
4. 总结
- Scheduler 负责 React 任务调度,优化渲染任务的执行顺序。
- 它提供任务优先级管理,如
ImmediatePriority
和UserBlockingPriority
。 - 它支持时间切片(Time Slicing) ,避免长时间阻塞主线程,提高 UI 流畅度。
- Scheduler 在 React 并发模式中发挥重要作用,比如
startTransition
内部使用了它。
如果面试官深入问,你可以补充 Scheduler 的工作原理、如何配合 requestIdleCallback
以及它在 React 18 并发渲染中的作用。
76.React18新特性——Concurrent Mode 并发模式 多个任务交替进行
React 18 的 Concurrent Mode(并发模式) 是 React 核心架构的重大升级,旨在通过可中断、优先级驱动的渲染机制,提升复杂应用的响应速度和用户体验。以下是其核心要点与面试回答方向:
一、核心目标
- 解决阻塞渲染问题:传统同步渲染会阻塞主线程,导致用户交互卡顿(如输入延迟)。并发模式通过时间切片(Time Slicing) 将渲染任务拆分为可中断的微任务,优先响应用户操作。
- 智能调度更新:根据任务优先级(如用户输入 > 数据加载 > 动画)动态调整渲染顺序,确保高优先级任务即时处理。
- 支持后台预渲染:提前准备即将显示的 UI(如悬停时预加载组件),减少用户感知的等待时间。
二、关键特性与 API
1. 自动批处理(Automatic Batching)
-
机制:React 18 默认将多个状态更新合并为单一渲染(包括 Promise、setTimeout 等异步操作),减少不必要的重复渲染。
-
示例:
// React 17:setTimeout 内更新会触发两次渲染 setTimeout(() => {setCount(1);setFlag(true); }, 1000);// React 18:自动批处理,仅一次渲染
2. 过渡更新(Transitions)
-
API:
startTransition
、useTransition
-
作用:区分紧急更新(如输入反馈)与非紧急更新(如搜索结果),延迟后者以避免阻塞交互。
-
示例:
const [isPending, startTransition] = useTransition();const handleSearch = (query) => {// 紧急:立即更新输入框setInput(query);// 非紧急:延迟更新搜索结果startTransition(() => {setSearchQuery(query);}); };return <div>{isPending ? 'Loading...' : <Results />}</div>;
3. 延迟值(Deferred Values)
-
API:
useDeferredValue
-
作用:派生值的“降级”更新,保持界面响应。适用于耗时计算或大型列表渲染。
-
示例:
const deferredList = useDeferredValue(heavyList); return <List items={deferredList} />; // 延迟渲染,允许高优先级任务插队
4. Suspense 增强
-
服务端渲染(SSR)支持:结合
lazy
和Suspense
实现流式 HTML 传输,逐步加载组件并优先显示已就绪内容。 -
示例:
<Suspense fallback={<Skeleton />}><Comments /> // 异步加载的评论组件 </Suspense>
5. 新的 Root API
-
createRoot
:替代ReactDOM.render
,启用并发特性。// React 17 ReactDOM.render(<App />, document.getElementById('root'));// React 18 const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
三、底层原理
- Fiber 架构升级:利用双向链表结构(Fiber 节点)实现可中断渲染,支持回溯和任务恢复。
- Lane 模型:通过 31 位二进制位表示任务优先级,精细控制调度逻辑。
- 协作式多任务:在浏览器空闲时段执行渲染任务(类似
requestIdleCallback
),避免主线程阻塞。
四、性能优化场景
- 输入防抖:用户连续输入时,优先渲染输入框内容,延迟处理搜索请求。
- 页面切换预加载:鼠标悬停在导航按钮时,后台预加载目标页面的组件或数据。
- 大型列表渲染:使用
useDeferredValue
分块渲染,避免界面冻结。
五、迁移与注意事项
- 渐进式采用:并发特性可逐步集成,无需重写整个应用。
- 副作用处理:注意
useEffect
和生命周期钩子可能因渲染中断而多次执行,需确保幂等性。 - 严格模式:React 18 的严格模式会故意双渲染组件,帮助发现副作用问题。
六、与传统模式对比
场景 | 传统模式 | 并发模式 |
---|---|---|
用户输入响应 | 可能被长任务阻塞 | 高优先级更新优先处理,输入流畅 |
多个更新竞争 | 顺序执行,可能导致卡顿 | 按优先级调度,动态插队 |
复杂组件树渲染 | 长时间占用主线程 | 分片渲染,保持界面可交互 |
总结
React 18 的 Concurrent Mode 通过可中断渲染和智能调度,使应用具备“多任务处理”能力,显著提升用户体验。开发者应重点掌握:
- 核心 API:
startTransition
、useDeferredValue
、Suspense
- 优化场景:区分紧急/非紧急更新,预加载关键资源
- 底层机制:Fiber 架构与 Lane 模型的作用
理解并发模式不仅有助于应对面试,更能为构建高性能 React 应用提供坚实基础。
77.React18新特性——自动批处理 Automatic Batchin
React 18 新特性 —— 自动批处理(Automatic Batching)
自动批处理(Automatic Batching) 是 React 18 引入的一项优化,它可以自动地将多个状态更新合并,减少渲染次数,提高性能。
1. 什么是批处理(Batching)?
在 React 18 之前,批处理只发生在React 事件处理函数内部:
import { useState } from 'react';function App() {const [count, setCount] = useState(0);const [flag, setFlag] = useState(false);const handleClick = () => {setCount(count + 1);setFlag(!flag);// 在 React 17 及之前:只触发 1 次重新渲染(批处理)};console.log('组件渲染'); // 只会执行一次return <button onClick={handleClick}>{count.toString()}</button>;
}
但在异步任务(如 setTimeout
、Promise
)中,React 17 及之前不会进行批处理:
setTimeout(() => {setCount(count + 1);setFlag(!flag);// 在 React 17 及之前:触发 2 次重新渲染
}, 1000);
2. React 18 自动批处理(Automatic Batching)
在 React 18 中,所有的状态更新都会自动批处理,无论它们在哪个上下文中触发!
包括异步代码(setTimeout
、Promise
、fetch
等)。
示例:React 18 自动批处理
import { useState } from 'react';function App() {const [count, setCount] = useState(0);const [flag, setFlag] = useState(false);setTimeout(() => {setCount((prev) => prev + 1);setFlag((prev) => !prev);// React 18:只会触发 1 次渲染}, 1000);console.log('组件渲染'); // React 18 只执行 1 次return <div>{count.toString()}</div>;
}
在 React 17 及之前:触发 2 次渲染
在 React 18:触发 1 次渲染
3. 取消自动批处理
如果在某些情况下你不想让 React 自动合并更新,可以使用 flushSync
强制立即更新:
import { flushSync } from 'react-dom';function handleClick() {flushSync(() => {setCount((c) => c + 1);});flushSync(() => {setFlag((f) => !f);});// 这里会触发 2 次渲染
}
flushSync
适用于:
- 需要在某个状态更新后立即获取最新 DOM 的情况
- 需要精确控制渲染顺序的情况
4. 自动批处理的优势
✅ 减少不必要的渲染,提高性能
✅ 提升开发体验,减少状态更新导致的 UI 闪烁
✅ 统一行为,在同步和异步任务中都适用
5. 总结
- 批处理(Batching) :React 会合并多个状态更新,减少渲染次数。
- React 18 以前:仅在 React 事件处理函数中批处理,异步任务不会批处理。
- React 18 自动批处理:所有任务(同步 & 异步)都批处理,减少不必要的渲染。
flushSync
:如果需要强制立即更新,可以使用flushSync
。
这个特性让 React 代码更高效,避免不必要的重渲染,是 React 18 重要的优化之一!
78.React18新特性——流式 SSR
React 18 新特性 —— 流式 SSR(Streaming Server-Side Rendering)
流式 SSR(Streaming Server-Side Rendering) 是 React 18 提供的一种优化的服务端渲染(SSR)方式,它允许服务器在数据准备好后逐步发送 HTML,而不是等待所有数据加载完再返回完整页面。
1. 传统 SSR vs. 流式 SSR
(1)传统 SSR 的问题
在 React 18 之前,SSR 采用的是阻塞式渲染:
- 服务器需要等所有数据加载完成,然后一次性返回完整 HTML。
- 如果某个组件的数据请求较慢,会导致整个页面的加载延迟。
- 用户在收到 HTML 之前,会一直看到空白页面。
示例(React 17 SSR):
import { renderToString } from 'react-dom/server';
import App from './App';const html = renderToString(<App />);
res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
renderToString
阻塞执行,直到App
及其所有子组件的内容完全准备好。
(2)React 18 的流式 SSR
React 18 提供了非阻塞的流式渲染:
- HTML 按块(chunks)发送,优先展示可用的 UI。
- 可以提前渲染静态内容,等数据准备好再填充动态内容。
- 提高首次内容可见(FCP) ,减少白屏时间。
示例(React 18 流式 SSR):
import { renderToPipeableStream } from 'react-dom/server';
import App from './App';export default function handler(req, res) {const { pipe } = renderToPipeableStream(<App />, {onShellReady() {res.setHeader('Content-Type', 'text/html');pipe(res); // 立即开始流式传输 HTML},onError(err) {console.error(err);res.status(500).send('Internal Server Error');}});
}
renderToPipeableStream
替代了renderToString
,支持逐步发送 HTML。onShellReady
事件触发时,就可以开始向客户端流式发送内容。
2. 关键优化点
(1)更快的 FCP(First Contentful Paint)
- 静态内容可以立即渲染,减少白屏时间。
- 服务器端可优先返回非阻塞组件,提高用户体验。
(2)Suspense 支持流式 SSR
- React 18 允许在 SSR 中使用
<Suspense>
,未加载的数据部分可以稍后填充,不会影响整个页面的渲染。
示例:
import { Suspense } from 'react';function SlowComponent() {const data = fetchData(); // 假设这里是一个耗时请求return <div>{data}</div>;
}export default function App() {return (<div><h1>My App</h1><Suspense fallback={<p>Loading...</p>}><SlowComponent /></Suspense></div>);
}
在 React 18 SSR:
- 服务器会先返回
<h1>My App</h1>
和<p>Loading...</p>
。 - 当
SlowComponent
数据加载完,再填充真实内容。
3. 流式 SSR vs. 传统 SSR vs. CSR
方式 | 传统 SSR | 流式 SSR | CSR(客户端渲染) |
---|---|---|---|
页面渲染时机 | 数据加载完成后一次性返回 | 逐步返回,先渲染可用部分 | HTML 先返回,JS 再渲染 |
性能 | 可能导致服务器阻塞 | 更快响应用户,减少白屏 | 依赖 JS 解析,SEO 不佳 |
SEO 友好 | ✅ | ✅ | ❌ |
适用场景 | 适用于小型 SSR 项目 | 适用于高并发、内容较多的 SSR 项目 | 适用于动态交互强的 SPA |
4. 总结
✅ 流式 SSR 允许服务器逐步发送 HTML,提升页面加载速度。
✅ 支持 <Suspense>
进行流式渲染,未加载的数据不会阻塞整个页面。
✅ 减少 TTFB(首字节时间) ,提高用户体验。
✅ 适用于高并发和需要快速响应的应用,如新闻站点、电商页面等。
React 18 的流式 SSR 使得服务端渲染更加高效,结合 Suspense
还能提供更好的用户体验,是前端优化的重要特性之一!
79.React18新特性——Server Component
React 18 的 Server Components(服务端组件) 是 React 生态中的一项革新,旨在通过服务端与客户端的协同渲染,优化性能并提升开发体验。以下是其核心要点与面试回答方向:
一、核心概念
- 服务端专属:Server Components 仅在服务器端运行,不参与客户端交互,代码不会发送到浏览器,减少客户端包体积。
- 动态与静态结合:允许在服务端动态生成组件树,并与客户端组件(Client Components)无缝集成,形成混合渲染模式。
- 直接访问后端资源:可直接连接数据库、读取文件或调用内部 API,避免客户端到服务端的额外请求。
二、核心特性
1. 减少客户端代码
-
零客户端代码:Server Components 的代码(包括依赖)不打包到客户端,显著降低首屏加载体积。
-
示例:
// ServerComponent.server.js (服务端组件) import db from 'server-db'; // 仅在服务端运行export default function ProductDetails({ id }) {const product = db.query('SELECT * FROM products WHERE id = ?', id);return <div>{product.name}</div>; // 静态内容 }
2. 流式渲染(Streaming SSR)
-
分块传输:服务端将渲染结果拆分为多个数据块,通过 HTTP 流(Stream)逐步发送到客户端,加速首屏显示。
-
结合 Suspense:与
Suspense
配合,优先渲染关键内容,延迟非关键部分。// 客户端组件 import ProductDetails from './ProductDetails.server';function App() {return (<Suspense fallback={<Loading />}><ProductDetails id={1} /></Suspense>); }
3. 数据获取优化
- 无客户端数据请求:服务端组件直接访问数据源,避免客户端通过
fetch
二次请求,减少网络延迟。 - 自动序列化:服务端组件向客户端传递的 props 会自动序列化为 JSON,支持基础类型、JSX 元素及客户端组件引用。
4. 与客户端组件协作
-
混合渲染:通过文件后缀(如
.server.js
、.client.js
)区分组件类型,服务端组件可嵌套客户端组件,反之不行。// ProductPage.server.js import AddToCart from './AddToCart.client'; // 导入客户端组件export default function ProductPage() {return (<div><ProductDetails id={1} /><AddToCart productId={1} /> // 交互逻辑由客户端处理</div>); }
三、与传统 SSR 的区别
特性 | 传统 SSR | Server Components |
---|---|---|
代码体积 | 服务端生成 HTML,但客户端仍需加载完整 JS 包 | 服务端组件代码不发送到客户端 |
数据获取 | 需在客户端通过 getServerSideProps 等获取 | 直接访问服务端资源,无额外请求 |
交互性 | 需水合(Hydration)后交互 | 需结合客户端组件处理交互 |
渲染粒度 | 整页渲染 | 按组件级动态流式渲染 |
四、适用场景
- 内容型页面:博客、文档、商品详情页等以静态内容为主的场景。
- 数据敏感操作:直接访问数据库或内部 API,避免暴露敏感逻辑到客户端。
- 性能敏感应用:需减少客户端代码体积,提升低端设备或慢网络下的体验。
五、限制与注意事项
- 无交互能力:不能使用
useState
、useEffect
或浏览器 API(如localStorage
)。 - 序列化约束:传递的 props 需可序列化,避免包含函数或复杂对象。
- 框架依赖:需配合支持 Server Components 的框架(如 Next.js)或自定义服务端环境。
六、示例:混合渲染流程
-
服务端渲染:
- 运行
ProductPage.server.js
,获取数据库数据。 - 将静态内容序列化为 HTML,动态部分标记为占位符。
- 运行
-
流式传输:
- 客户端逐步接收 HTML 和客户端组件代码。
AddToCart.client.js
被加载并水合,启用交互。
-
最终结果:
- 用户立即看到商品信息,按钮交互由客户端处理。
七、总结
React Server Components 通过服务端与客户端的职责分离,解决了传统 SSR 的代码冗余和数据请求问题,尤其适合内容优先的场景。其核心优势包括:
- 更小的客户端包体积
- 直接服务端数据访问
- 流式渲染加速首屏
开发者需注意其限制,合理划分服务端与客户端组件,结合框架能力(如 Next.js)最大化性能收益。这一特性标志着 React 向全栈开发迈出了重要一步。
80.React18新特性——OffScreen
大白话:支持保持组件的状态,而删除组件的UI 部分,很方面的实现与渲染,或者keep-alive
React 18 的 Offscreen(或实验性 API unstable_Offscreen
)是一个旨在优化渲染性能的特性,主要用于管理不可见组件的渲染行为,减少不必要的计算和 DOM 操作。以下是其核心要点与回答方向:
一、核心目标
- 保留组件状态与 DOM:当组件暂时不可见时(如标签页切换、弹窗关闭),保持其 DOM 结构和状态,避免重复挂载/卸载带来的性能损耗。
- 后台预渲染:结合并发模式,在后台静默渲染隐藏的组件,为后续显示提前准备。
- 资源优化:暂停不可见组件的副作用(如动画、数据订阅),减少 CPU 和内存占用。
二、核心特性
1. 状态保留与快速恢复
-
避免重新挂载:组件隐藏时保留其状态(如滚动位置、表单输入),再次显示时无需重新初始化。
-
示例:
import { unstable_Offscreen as Offscreen } from 'react';function App() {const [show, setShow] = useState(true);return (<div><button onClick={() => setShow(!show)}>Toggle</button><Offscreen mode={show ? "visible" : "hidden"}><ExpensiveComponent /> // 隐藏时保留 DOM 和状态</Offscreen></div>); }
2. 与并发模式协同
- 后台渲染:在组件隐藏期间,React 可异步完成其未完成的渲染任务(如数据加载),待显示时直接提交结果。
- 优先级控制:隐藏组件的更新被标记为低优先级,避免阻塞用户交互。
3. 副作用管理
- 自动暂停/恢复:隐藏时暂停
useEffect
、动画等副作用,显示时自动恢复。 - 资源释放:若组件长期隐藏,可选择彻底卸载以释放资源(需开发者配置)。
三、与传统处理方式的对比
场景 | 传统方式 | Offscreen |
---|---|---|
组件隐藏 | 卸载组件,丢失状态 | 保留 DOM 和状态,快速恢复 |
复杂组件渲染 | 每次显示需重新渲染,可能卡顿 | 后台预渲染,显示时无延迟 |
副作用管理 | 需手动取消订阅或清理 | 自动暂停/恢复,减少内存泄漏风险 |
四、适用场景
- 标签页/路由切换:保留非活动标签页的状态,提升切换流畅度。
- 弹窗/抽屉组件:避免重复渲染弹窗内容,快速响应用户操作。
- 虚拟列表优化:对屏幕外列表项使用 Offscreen,减少渲染压力。
五、实现原理
- DOM 隐藏而非卸载:通过 CSS
display: none
或visibility: hidden
隐藏组件,保持 DOM 树结构。 - Fiber 节点标记:在协调阶段跳过隐藏组件的更新检查,除非强制刷新。
- 副作用调度:通过 React 调度器暂停隐藏组件的副作用执行。
六、注意事项
- 实验性 API:截至 React 18.2,
unstable_Offscreen
仍为实验性功能,API 可能变更。 - 内存占用:长期保留大量隐藏组件可能增加内存消耗,需权衡使用。
- 兼容性:需配合 Concurrent Mode 使用,传统同步渲染不生效。
七、示例:路由切换优化
import { unstable_Offscreen as Offscreen } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';function App() {const location = useLocation();return (<Routes location={location}><Route path="/" element={<Home />} /><Route path="/dashboard" element={<Offscreen mode="visible"><Dashboard /> // 显示时优先渲染</Offscreen>} /><Route path="/settings" element={<Offscreen mode="hidden"><Settings /> // 隐藏时保留状态</Offscreen>} /></Routes>);
}
总结
React 18 的 Offscreen 特性通过智能管理不可见组件的生命周期,显著提升了复杂应用的渲染效率与用户体验。其核心价值在于:
- 状态保留:避免重复初始化带来的性能损耗。
- 后台渲染:利用空闲资源预计算,减少用户等待时间。
- 副作用控制:自动化资源管理,降低开发复杂度。
尽管目前仍处于实验阶段,Offscreen 展现了 React 在性能优化方向的持续探索,值得开发者关注其未来正式发布后的应用场景。
81.React18新特性——卸载组件时的更新状态警告
React 18 新特性 —— 卸载组件时的更新状态警告(Updating a component while unmounting)
在 React 18 之前,如果组件在卸载(unmount)时仍然尝试更新状态(setState
或 useState
),React 会抛出**“Can’t perform a React state update on an unmounted component”** 警告。这通常是由于异步操作(如 fetch
、setTimeout
或 useEffect
副作用)未在组件卸载时正确清理导致的。
React 18 的改进
在 React 18 之后,React 变得更聪明了:
- 自动检测并忽略卸载组件的更新,不会再抛出警告。
- 提高应用稳定性,避免无效的状态更新影响其他组件。
🔹 示例:React 18 之前
function Example() {const [data, setData] = useState(null);useEffect(() => {fetch('/api/data').then(res => res.json()).then(result => setData(result));// 🚨 组件卸载时,没有清理请求,可能导致警告}, []);return <div>{data}</div>;
}
如果用户在数据请求完成之前离开当前页面或卸载组件,组件仍然尝试 setData(result)
,这会触发 React 17 及之前版本的状态更新警告。
🔹 React 18 之后
function Example() {const [data, setData] = useState(null);useEffect(() => {let isMounted = true;fetch('/api/data').then(res => res.json()).then(result => {if (isMounted) setData(result); // ✅ React 18 处理得更好});return () => {isMounted = false; // 🔹 组件卸载时标记};}, []);return <div>{data}</div>;
}
在 React 18,即使不手动清理副作用,React 也会自动忽略组件卸载后的更新,从而避免警告。
为什么要优化这个问题?
- 避免开发者手动清理副作用(比如
isMounted
变量)。 - 解决了异步更新导致的潜在 Bug。
- 提升应用稳定性,避免不必要的错误提示。
总结
✅ React 18 自动忽略卸载组件的更新,避免不必要的警告。
✅ 减少开发者手动处理的负担,代码更简洁。
✅ 增强应用稳定性,避免潜在的内存泄漏和状态错误。
虽然 React 18 进行了优化,但良好的副作用清理习惯仍然是最佳实践,比如在 useEffect
的 return
语句中清理定时器、取消请求等。
82.什么是服务端渲染什么是客户端渲染
服务端渲染(SSR) vs. 客户端渲染(CSR)
在 Web 开发中,页面的渲染方式主要有两种:
- 服务端渲染(SSR - Server-Side Rendering)
- 客户端渲染(CSR - Client-Side Rendering)
它们的区别在于HTML 生成的时机:
- SSR:HTML 在服务器端生成,然后返回完整的页面给浏览器。
- CSR:HTML 在客户端(浏览器)生成,服务器只返回 JavaScript 代码,由浏览器执行后动态渲染页面。
1. 什么是服务端渲染(SSR)?
概念
服务端渲染(SSR)是指服务器在接收到请求后,直接返回完整的 HTML 页面。
浏览器接收到 HTML 后,可以立即渲染页面,无需等待 JavaScript 加载和执行。
工作流程
- 浏览器发送请求给服务器(如
example.com
)。 - 服务器执行 React/Vue/Next.js 代码,渲染出完整的 HTML 页面。
- 返回完整 HTML 页面给浏览器,用户可以立刻看到页面内容。
- 客户端加载 JavaScript,让页面变得可交互(称为水合(Hydration) )。
示例(React SSR)
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';const app = express();app.get('*', (req, res) => {const html = renderToString(<App />);res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});app.listen(3000, () => console.log('Server is running on port 3000'));
这里 renderToString()
会在服务器端生成 HTML 并返回给浏览器。
SSR 的优点
✅ SEO 友好:搜索引擎可以直接解析 HTML,提高排名。
✅ 首屏加载快:页面内容在服务器上渲染好后直接返回,减少白屏时间。
✅ 适合内容型网站(如博客、新闻站点、电商网站)。
SSR 的缺点
❌ 服务器压力大:每个请求都需要服务器计算,可能影响并发性能。
❌ 交互需要水合(Hydration) :页面渲染后,仍需 JavaScript 绑定事件,使页面可交互。
2. 什么是客户端渲染(CSR)?
概念
客户端渲染(CSR)是指服务器只返回一个基本的 HTML 结构,页面内容由 JavaScript 在浏览器中动态渲染。
工作流程
- 服务器返回一个基本 HTML(通常只包含一个
div#root
)。 - 浏览器下载 JavaScript,并在浏览器中执行 React/Vue 代码。
- 前端框架(如 React、Vue)动态渲染页面。
- 用户可以看到页面内容并进行交互。
示例(React CSR)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';const root = document.getElementById('root');
ReactDOM.createRoot(root).render(<App />);
这里 ReactDOM.createRoot().render()
会在浏览器端动态生成页面内容。
CSR 的优点
✅ 前后端分离,适合 SPA(单页面应用) 。
✅ 用户交互体验更流畅(页面切换更快)。
✅ 服务器压力小,因为页面渲染由客户端完成。
CSR 的缺点
❌ 首屏加载慢,需要先下载 JavaScript,再渲染页面。
❌ SEO 不友好(搜索引擎爬取的是空 HTML)。
❌ 依赖 JavaScript,如果禁用 JavaScript,页面可能无法正常加载。
3. SSR vs. CSR 对比
特性 | SSR(服务端渲染) | CSR(客户端渲染) |
---|---|---|
首屏渲染速度 | 快(服务器返回完整 HTML) | 慢(等待 JS 解析并渲染) |
SEO 友好 | 好(搜索引擎可直接爬取 HTML) | 差(搜索引擎可能爬不到内容) |
服务器压力 | 高(每次请求都要计算) | 低(服务器只提供 API 数据) |
用户交互体验 | 需要 Hydration,使页面可交互 | 交互流畅 |
适用场景 | 适用于 SEO 重要的网站,如博客、新闻、电商 | 适用于交互性强的应用,如管理后台、社交平台 |
4. SSR 和 CSR 的结合
方案 1:同构渲染(Universal Rendering)
- 服务器先进行 SSR,返回 HTML 让用户立即看到内容。
- 然后客户端进行 Hydration,绑定事件,使页面可交互。
示例(Next.js 实现同构渲染):
export async function getServerSideProps() {const data = await fetchData(); // 在服务器端获取数据return { props: { data } };
}export default function Page({ data }) {return <div>{data}</div>;
}
方案 2:静态生成 + CSR(Next.js SSG + CSR)
- 静态页面(SSG) :提前在构建时生成 HTML,提高性能。
- 交互部分(CSR) :通过 React 加载数据,使页面可交互。
示例(Next.js SSG + CSR):
export async function getStaticProps() {const data = await fetchData();return { props: { data } };
}export default function Page({ data }) {return <div>{data}</div>;
}
5. 总结
服务端渲染(SSR) | 客户端渲染(CSR) | |
---|---|---|
渲染位置 | 服务器 | 浏览器 |
首屏速度 | 快(HTML 直接返回) | 慢(需要下载 JS 并执行) |
SEO 友好 | ✅ 是 | ❌ 不是 |
服务器压力 | 高 | 低 |
适用场景 | 适合 SEO 需求,如博客、电商、新闻 | 适合交互复杂的 SPA,如后台管理系统 |
什么时候用 SSR?
- SEO 需求高(博客、新闻、电商等)。
- 需要更快的首屏加载。
什么时候用 CSR?
- 交互复杂的应用(如管理后台、社交平台)。
- 用户多次切换页面(如 SPA)。
最佳方案:结合 SSR 和 CSR
- Next.js、Nuxt.js 等框架支持同构渲染,结合 SSR 和 CSR 的优势。
- 静态生成(SSG) + CSR 是性能最优解。
React 18 进一步优化了 SSR,引入了流式 SSR(Streaming SSR) ,提升了 SSR 的性能,使其更适用于大规模应用。
83.React18新特性——Strict Mode
React 18 中的 Strict Mode 是一个开发环境下的工具,旨在帮助开发者提前发现潜在问题,尤其是为 并发渲染(Concurrent Rendering) 和未来 React 特性铺平道路。以下是其核心特性和作用的详细解析:
一、Strict Mode 的核心作用
-
检测不安全的副作用:
- 重复调用 Effects:在开发模式下,React 会故意多次挂载和卸载组件,导致
useEffect
和useLayoutEffect
的清理函数与执行函数被调用两次。 - 目的:暴露未正确清理的副作用(如未取消订阅事件、未关闭网络请求),确保组件能安全应对并发渲染下的多次渲染。
- 重复调用 Effects:在开发模式下,React 会故意多次挂载和卸载组件,导致
-
识别废弃的 API 和模式:
- 警告使用过时的 API(如
findDOMNode
、旧版context API
)。 - 检测不安全的生命周期方法(如
UNSAFE_componentWillMount
)。
- 警告使用过时的 API(如
-
验证并发模式兼容性:
- 模拟并发渲染的极端场景(如渲染中断、任务优先级变化),提前暴露竞态条件(Race Conditions)或状态同步问题。
二、React 18 中 Strict Mode 的新行为
1. 组件双重渲染(Double Invocation)
-
现象:在开发模式下,组件的函数体、
useState
初始化、useEffect
等会被连续调用两次。 -
示例:
function MyComponent() {console.log("Render"); // 输出两次useEffect(() => {console.log("Effect"); // 输出两次,且 cleanup 在两次之间执行return () => console.log("Cleanup");}, []);return <div>Test</div>; }
-
目的:确保副作用逻辑的幂等性(Idempotent),即多次执行不会产生错误。
2. 模拟卸载后重新挂载
-
行为:组件首次挂载后立即卸载,再重新挂载。
-
验证点:
- 组件是否能正确处理瞬时挂载(如快速导航导致的组件频繁加载/卸载)。
- 资源(如定时器、订阅)是否在
useEffect
的 cleanup 函数中被正确释放。
3. 并发渲染压力测试
-
行为:在并发模式下,React 可能中断渲染过程以处理更高优先级任务,Strict Mode 会模拟此类场景。
-
验证点:
- 组件状态更新是否具备一致性(如避免部分状态更新导致 UI 撕裂)。
- 副作用逻辑是否对渲染中断具备容错性。
三、Strict Mode 解决的问题
-
副作用管理:
- 避免因未清理的副作用导致内存泄漏或数据不一致。
-
并发模式兼容性:
- 确保组件在渲染可中断、任务批处理等并发特性下表现稳定。
-
代码质量提升:
- 强制开发者遵循 React 最佳实践,减少技术债务。
四、如何适配 Strict Mode
-
副作用幂等化:
- 确保
useEffect
中的逻辑可重复执行而不出错(如通过唯一标识符避免重复请求)。
- 确保
-
正确清理资源:
- 在
useEffect
的 cleanup 函数中释放所有资源(如取消订阅、关闭连接)。
- 在
-
避免依赖渲染次数:
- 不假设组件只渲染一次(如避免在渲染函数中直接修改外部状态)。
五、示例:修复双重渲染问题
问题代码:
let initialized = false;function MyComponent() {useEffect(() => {if (!initialized) {initialized = true; // 依赖外部变量,导致多次执行异常fetchData();}}, []);// ...
}
修复方案:
function MyComponent() {useEffect(() => {const controller = new AbortController();fetchData({ signal: controller.signal });return () => controller.abort(); // 正确清理}, []);// ...
}
六、总结
React 18 的 Strict Mode 通过模拟极端场景(如双重渲染、并发中断),强制开发者编写更健壮的代码,为并发渲染和未来特性提供兼容性保障。其核心价值在于:
- 提前暴露问题:在开发阶段发现并发模式下的潜在缺陷。
- 推动最佳实践:引导开发者遵循 React 的副作用管理和状态更新规范。
84.React18新特性——Suspense 不再需要 fallback
在 React 18 中,Suspense 不再强制要求提供 fallback
属性,这一变化是为了提升开发灵活性并更好地支持并发渲染(Concurrent Rendering)和嵌套 Suspense 边界的场景。以下是其核心原理和影响的详细解析:
一、行为变化:从强制到可选
1. React 17 及之前
-
强制要求:每个
<Suspense>
必须提供fallback
属性,否则会抛出警告或错误。 -
示例:
// React 17:必须提供 fallback <Suspense fallback={<Loading />}><AsyncComponent /> </Suspense>
2. React 18 的改进
-
fallback
变为可选:可以省略fallback
,此时若子组件处于挂起状态,React 会向上查找最近的父级 Suspense 边界并使用其fallback
。 -
示例:
// React 18:允许省略 fallback <Suspense> {/* 无 fallback */}<AsyncComponent /> </Suspense>
二、设计动机
1. 支持嵌套 Suspense 边界
- 场景:在复杂组件树中,可能存在多层 Suspense 嵌套(如全局加载层 + 局部加载层)。
- 问题:强制每个 Suspense 提供
fallback
会导致冗余代码(如重复的加载动画)。 - 解决方案:子级 Suspense 可省略
fallback
,直接继承父级的加载状态,实现状态冒泡。
2. 适配并发渲染
- 并发特性:在并发模式下,React 可以中断渲染并优先处理高优先级更新。
- 行为变化:若子组件挂起且无
fallback
,React 会保持当前 UI 不变,直到异步操作完成,避免频繁切换加载状态(如闪烁)。
3. 简化代码
- 减少冗余:对于无需独立加载状态的场景(如静默加载),省略
fallback
使代码更简洁。
三、具体行为与规则
1. 无 fallback
时的挂起行为
- 向上冒泡:子组件挂起时,React 会向父级 Suspense 边界传递挂起状态,直到找到有
fallback
的祖先。 - 无父级 Suspense:若整个组件树均无
fallback
,React 会抛出错误(需至少一个 Suspense 提供fallback
)。
2. 与过渡(Transitions)结合
- 示例:使用
useTransition
标记非紧急更新,即使子 Suspense 无fallback
,也能避免界面突然切换。
const [isPending, startTransition] = useTransition();
startTransition(() => {// 触发异步更新
});
return (<div>{isPending && <GlobalLoading />}<Suspense> {/* 无 fallback */}<AsyncComponent /></Suspense></div>
);
四、使用场景与最佳实践
1. 场景 1:嵌套 Suspense 的全局加载
// 外层:全局加载层
<Suspense fallback={<AppLoading />}><Layout>{/* 内层:局部组件无需独立加载状态 */}<Suspense> {/* 无 fallback */}<UserProfile /></Suspense></Layout>
</Suspense>
2. 场景 2:静默加载(无视觉反馈)
// 组件挂起时不显示任何加载状态,直到数据就绪
<Suspense> {/* 无 fallback */}<CommentsSection /> // 静默加载评论
</Suspense>
3. 场景 3:服务端流式渲染(SSR)
- 行为:在流式 SSR 中,未就绪的 Suspense 区块可先发送 HTML 片段,后续补充内容,无需强制
fallback
。
五、注意事项
- 至少一个 Suspense 需有
fallback
:整个组件树中必须存在至少一个 Suspense 提供fallback
,否则会报错。 - 避免无限挂起:若异步操作永远不完成且无
fallback
,界面可能卡死,需结合错误边界(Error Boundaries)处理异常。
六、总结
React 18 中 Suspense 不再强制要求 fallback
,这一改进使得:
- 嵌套 Suspense 更灵活:子边界可冒泡到父级加载状态,减少冗余代码。
- 并发渲染更友好:支持静默加载和优先级调度,提升用户体验。
- 代码更简洁:简化无需独立加载状态的场景。
开发者需根据场景选择是否省略 fallback
,并确保组件树中至少有一个 Suspense 提供回退 UI,以兼容异步渲染流程。
85.React——super()和super(props)有什么区别
在 React 类组件中,super()
和 super(props)
的区别在于构造函数中是否能够访问 this.props
,具体分析如下:
一、核心区别
调用方式 | super() | super(props) |
---|---|---|
参数传递 | 不传递 props 给父类构造函数 | 传递 props 给父类构造函数 |
this.props 访问时机 | 构造函数内 this.props 为 undefined | 构造函数内可直接访问 this.props |
使用场景 | 构造函数中不需要 props | 构造函数中需要依赖 props |
二、详细解释
1. super(props)
的作用
-
传递
props
到父类:React.Component
的构造函数需要接收props
参数,调用super(props)
会将props
传递给父类,完成初始化。 -
构造函数内访问
this.props
:只有调用super(props)
后,才能在构造函数中直接使用this.props
。class MyComponent extends React.Component {constructor(props) {super(props); // 传递 propsconsole.log(this.props); // 正确输出 props} }
2. super()
的局限性
-
不传递
props
:若省略props
参数,父类构造函数无法初始化this.props
。 -
构造函数内
this.props
为undefined
:class MyComponent extends React.Component {constructor(props) {super(); // 未传递 propsconsole.log(this.props); // undefined} }
-
其他生命周期方法不受影响:即使不传递
props
,在render()
、componentDidMount()
等方法中,this.props
仍可正常访问(React 会在构造函数执行后自动注入props
)。
三、为什么需要关注这一点?
1. 初始化状态依赖 props
若组件的初始状态(this.state
)需要基于 props
计算,则必须通过 super(props)
确保 this.props
可用:
class MyComponent extends React.Component {constructor(props) {super(props);this.state = { value: props.initialValue }; // 需要访问 props}
}
2. 历史代码兼容性
在 React 17 之前,context
的传递也依赖 super(props)
。若未传递 props
,this.context
在构造函数中可能异常(React 17+ 已修复此问题)。
四、最佳实践
-
始终传递
super(props)
:- 即使构造函数中暂时不需要
props
,显式传递可避免未来扩展时遗漏。 - 保持代码一致性,减少潜在错误。
- 即使构造函数中暂时不需要
-
无构造函数时无需处理:
- 若未定义构造函数,React 会自动调用
super(props)
。
class MyComponent extends React.Component {// 无构造函数,自动调用 super(props)render() {return <div>{this.props.text}</div>;} }
- 若未定义构造函数,React 会自动调用
五、总结
super(props)
:确保构造函数内可访问this.props
,适用于需要基于props
初始化状态或执行其他操作的场景。super()
:省略props
传递,构造函数内this.props
不可用,但其他生命周期方法不受影响。- 推荐做法:统一使用
super(props)
,避免因遗漏导致的隐蔽问题。
86.React面试题——说说对React中类组件和函数组组件的理解?有什么
在 React 中,组件是构建 UI 的基本单元,主要分为 类组件(Class Components) 和 函数组件(Function Components) 。两者在 React 16.8 之前存在较大区别,但随着 Hooks 的引入,函数组件的能力得到了增强,逐渐成为主流。
1. 类组件(Class Component)
定义:
类组件是基于 ES6 class
语法的组件,需要继承 React.Component
或 React.PureComponent
,并实现 render
方法返回 JSX。
特点:
- 有自己的
state
和生命周期方法(如componentDidMount
、shouldComponentUpdate
等)。 - 需要手动绑定
this
(如事件处理函数)。 - 支持
ref
直接访问 DOM。
示例:
class ClassComponent extends React.Component {constructor(props) {super(props);this.state = { count: 0 };}handleClick = () => {this.setState({ count: this.state.count + 1 });};render() {return (<div><p>Count: {this.state.count}</p><button onClick={this.handleClick}>Increment</button></div>);}
}
2. 函数组件(Function Component)
定义: 函数组件是基于普通 JavaScript 函数的组件,接收 props
作为参数,并返回 JSX 结构。
特点:
- 没有
this
,更加简洁。 - 通过
useState
、useEffect
等 Hooks 处理状态和生命周期。 - 更符合函数式编程思想,易于复用和测试。
示例:
import { useState } from "react";const FunctionComponent = () => {const [count, setCount] = useState(0);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
};
3. 类组件 vs 函数组件
比较项 | 类组件 (Class Component) | 函数组件 (Function Component) |
---|---|---|
语法风格 | 需要 class 继承 React.Component | 直接定义为普通函数 |
状态管理 | 通过 this.state 和 this.setState | 通过 useState Hook |
生命周期 | componentDidMount 、componentDidUpdate 、componentWillUnmount | useEffect 代替生命周期 |
this 绑定 | 需要手动绑定 this | 没有 this ,避免绑定问题 |
代码复用 | 通过 HOC(高阶组件)、Render Props | 通过 Hooks 使代码更清晰 |
性能优化 | 需要 shouldComponentUpdate 或 PureComponent | React.memo 和 useCallback 进行优化 |
可读性 | 代码较冗长,复杂度较高 | 更简洁,易读易维护 |
4. 为什么函数组件逐渐替代类组件?
- 代码更简洁:不用管理
this
,不需要冗长的生命周期方法。 - 性能优化:函数组件默认不会创建实例,减少内存占用,结合 React 18 并发模式,渲染更流畅。
- Hooks 让函数组件更强大:
useState
、useEffect
、useReducer
等让函数组件可以处理复杂逻辑。 - 未来趋势:React 官方推荐使用函数组件,社区库也都优先支持 Hooks。
5. 什么时候仍然使用类组件?
尽管函数组件已经成为主流,但某些情况下仍可能使用类组件:
- 需要使用
componentDidCatch
进行错误边界处理(但 React 18 引入useErrorBoundary
)。 - 维护老项目时,已有大量类组件,不便于重构。
6. 总结
- React 早期主要使用类组件,但 Hooks 引入后,函数组件成为主流。
- 类组件有
state
和生命周期方法,适用于旧版本 React 项目。 - 函数组件结合 Hooks,更加简洁、高效,并且是未来趋势。
- 除非有特殊需求(如错误边界),否则应优先使用函数组件。