1、React.lazy 与 Suspense 如何实现组件懒加载?
答:React.lazy + Suspense是React内置的代码分割 & 懒加载方案
React.lazy:让你把组件按需异步加载,而不是一次性打包进主bundle;本质上是import()的语法糖。React.Suspense:用来指定 加载中状态(fallback) 的占位UI
import React, { Suspense } from 'react';
// 懒加载组件
const About = React.lazy(() => import('./About'));
function App(){
return(
<div>
<h1>首页</h1>
<Suspense fallback={<div>加载中...</div>}>
<About />
</Suspense>
</div>
)
}
执行流程:
- 首次渲染时,
About组件不会打包进主bundle; - 当
React渲染到<About />时,触发import("./About")→ 异步加载 - 加载过程中,
Suspense显示fallback - 加载完成后,
React渲染About组件
原理:
React.lazy接收一个函数,这个函数返回一个Promise(动态import模块)React内部会把这个异步组件包装成一个“占位符”- 当
Promise pending→ 渲染Suspense.fallback - 当
Promise resolved→ 渲染真实组件 - 当
Promise rejected→ 可以配合ErrorBoundary兜底
注:
- 必须在
Suspense里使用,否则报错 - 不支持命名导出 → 懒加载的模块必须是默认导出
export default function About() { ... } ✅
export function About() { ... } ❌
SSR(Next.js)不能直接用,需要框架自己的动态导入方案(next/dynamic)
2、自定义 hook 如何实现?有哪些使用场景?
答:自定义Hook是React提供的一种逻辑复用机制,它不会产生新的语法或生命周期,只是让我们把stateful逻辑抽离出来。它的优势是复用、解耦、增强可维护性,常用于数据请求、事件监听、表单处理等场景。
- 自定义 Hook:就是把多个组件中复用的状态逻辑(state、副作用、计算等) 抽离成一个函数,以
use开头,内部可以使用其他hooks。 - 使用场景:逻辑复用(如表单处理、数据请求、事件监听)、代码解耦、提升可维护性。
React官方规定 自定义Hook必须以use开头,这样才能让React正确跟踪hook调用顺序。- 自定义
Hook其实就是一个函数,里面可以调用useState、useEffect、useMemo等。 - 它不会改变 React 的渲染逻辑,只是复用逻辑而非复用状态。
3、React 中如何实现组件间通信?(Context、props、event、bus)
答:React组件通信常见方式包括:props(父子)、props+回调(子父)、Context(跨层级)、事件总线(任意组件)、以及全局状态管理库(复杂场景)。通常小型项目用props/Context就够了,大型项目推荐使用Redux、Zustand 或 Recoil来管理状态。
props(父 → 子):父组件通过props传递数据和回调给子组件。- 优点:简单直观,类型安全(配合
TS)。 - 缺点:多层组件传递(prop drilling)会导致冗余。
- 优点:简单直观,类型安全(配合
props + 回调(子 → 父):父组件传递一个回调函数给子组件,子组件触发回调传值给父组件。- 优点:逻辑清晰。
- 缺点:仍然受限于父子关系,兄弟组件需要提升到共同父级。
Context(跨层级):通过React.createContext创建全局上下文,避免props drilling。- 优点:适合全局配置(主题、语言、用户信息)。
- 缺点:Context 值更新时,所有消费它的组件都会重新渲染(需配合
use-context-selector或分片Context优化)。
事件总线(Event Bus)(任意组件间):实现一个简单的发布-订阅系统,非父子关系也能通信。- 优点:解耦,组件间关系不受限制。
- 缺点:全局副作用,不利于维护和调试;适合轻量场景,不推荐大规模使用。
// eventBus.ts
import mitt from 'mitt';
export const bus = mitt();
// A.tsx
bus.emit('send', 'Hello B');
// B.tsx
bus.on('send', msg => console.log(msg));
状态管理库(Redux / Zustand / Recoil):将状态提升到全局Store,组件通过hooks订阅和更新。- 优点:大规模项目首选,支持调试、持久化、异步请求。
- 缺点:增加学习和引入成本。
4、如何在 React 中优雅地处理请求状态?(如 loading / error)
- 本地状态管理:用
useState来管理请求的三个状态- 优点:简单直观
- 缺点:每个组件都要写一遍
loading/errorw,重复代码多
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
- 封装自定义 Hook
- 优点:逻辑复用,简洁
- 缺点:功能有限,需要自己维护缓存 / 重试
function useFetch(url){
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(()=>{
let active = true;
setLoading(true);
fetch(url)
.then(res => res.json())
.then(data => active && setData(data))
.catch(err => active && setError(err))
.finally(() => active && setLoading(false));
return () => {
active = false;
} // 避免竞态更新
},[url])
return { data, loading, error };
}
// 使用
const { data, loading, error } = useFetch(`/api/user/${userId}`);
- 第三方库
React Query / SWR / Apollo Client- 缓存、去重、自动重试
- 状态管理(
loading/error/success) - 数据过期和重新请求
- 优点:几乎不用操心请求状态,专注渲染
- 缺点:引入库,学习成本
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, isError } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/user/${userId}`).then(res => res.json())
})
if (isLoading) return <p>加载中...</p>;
if (isError) return <p>请求失败</p>;
return <div>{data.name}</div>;
}