涵盖:组件、JSX、Props/State、生命周期、Hooks、状态管理、路由、SSR、性能优化、并发模式。
一、JSX 与组件
4.1 JSX 是什么?
JSX 是 JS 的语法扩展,React.createElement(component, props, ...children) 的语法糖。
jsx
const el = <div className="box">{name}</div>;
// 编译后:
React.createElement('div', { className: 'box' }, name);
TypeScript 中:const el: React.ReactNode。
4.2 函数组件 vs 类组件
| 维度 | 函数组件 | 类组件 |
|---|---|---|
| 状态 | Hooks | this.setState |
| 生命周期 | useEffect | componentDidMount 等 |
| 性能 | 轻 | 需要手动绑定 |
| 推荐 | React 18 强推 | 维护老代码 |
4.3 受控与非受控组件
tsx
// 受控
const [v, setV] = useState('');
<input value={v} onChange={(e) => setV(e.target.value)} />
// 非受控
const ref = useRef<HTMLInputElement>(null);
<input ref={ref} defaultValue="" />
const v = ref.current?.value;
业务中:表单通常受控,便于校验;文件上传、视频流等用非受控。
二、组件通信
4.4 通信方式
| 方式 | 适用 |
|---|---|
| props | 父→子 |
| children / render prop | 父→子 |
| forwardRef + useImperativeHandle | 父→子命令式 |
| Context | 跨级 |
| Redux/Zustand/Jotai | 全局 |
| EventBus / EventEmitter | 任意 |
| URL search params | 路由间 |
tsx
// Context 实战
const ThemeContext = createContext<{ theme: string; toggle: () => void } | null>(null);
function Provider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, toggle: () => setTheme((p) => p === 'light' ? 'dark' : 'light') }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used inside Provider');
return ctx;
}
三、生命周期(类组件视角,便于对比)
4.5 类组件生命周期与 Hooks 对应
| 类组件 | Hooks 等价 |
|---|---|
| constructor | useState initializer |
| componentDidMount | useEffect(() => {}, \[\]) |
| componentDidUpdate | useEffect(() => {}, deps) |
| componentWillUnmount | useEffect 的 cleanup |
| getDerivedStateFromProps | 在 render 中用 props 派生 |
| shouldComponentUpdate | React.memo / useMemo |
jsx
componentDidMount() { this.doA(); } // 等价 useEffect(() => { this.doA(); }, []);
componentDidUpdate(prev) { if (prev.id !== this.props.id) this.fetch(); }
// 等价
useEffect(() => { fetch(id); }, [id]);
四、Hooks 深入
4.6 Hooks 规则
- 只能在函数顶层调用
- 不能在条件/循环中
- 只能在 React 函数中调用
4.7 useState
tsx
const [count, setCount] = useState(0);
setCount(1); // 直接覆盖
setCount((p) => p + 1); // 函数式获取最新值
// 惰性初始化
const [list] = useState(() => heavyInit());
// 对象式 setState
const [s, setS] = useState({ a: 1 });
setS((prev) => ({ ...prev, a: 2 })); // 必须返回新对象
4.8 useEffect vs useLayoutEffect
| useEffect | useLayoutEffect | |
|---|---|---|
| 时机 | 渲染后异步执行 | DOM 更新后同步执行 |
| 阻塞渲染 | 否 | 是 |
| 典型 | 数据请求、订阅 | DOM 测量、布局 |
tsx
useEffect(() => { /* paint 之后 */ }, []);
useLayoutEffect(() => { /* paint 之前 */ }, []);
4.9 useRef 的三种用法
tsx
const domRef = useRef<HTMLDivElement>(null);
const timerRef = useRef<number | null>(null);
const prevCount = useRef(count); // 跨渲染保存值
4.10 useMemo / useCallback
tsx
const memo = useMemo(() => expensive(list), [list]);
const cb = useCallback(() => do(id), [id]);
- 缓存计算结果 / 函数引用。
- 不要过度使用:
useMemo本身有比较成本。 - 配合
React.memo子组件避免无谓渲染。
4.11 useReducer
tsx
function reducer(s: State, a: Action): State {
switch (a.type) { case 'inc': Increment; }
}
const [state, dispatch] = useReducer(reducer, initialState);
4.12 useContext 性能问题
Context 变化会引起所有消费者重渲染(如果不 memo)。可拆 Context(按数据域) 或配合 use-context-selector。
4.13 自定义 Hooks 范式
tsx
function useDebounce<T>(value: T, delay = 300) {
const [v, setV] = useState(value);
useEffect(() => {
const t = setTimeout(() => setV(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return v;
}
function usePrevious<T>(value: T) {
const ref = useRef<T>();
useEffect(() => { ref.current = value; });
return ref.current;
}
4.14 Hooks 实现原理(手写)
js
// 简化版:链表存 hooks state
let workInProgressHook;
let currentHook;
function useState(initial) {
const hook = currentHook.next ?? createHook();
if (hook.state === undefined && initial !== undefined) hook.state = initial;
hook.queue ??= { pending: null, dispatch: null };
hook.queue.dispatch = (action) => {
hook.queue.pending = action;
scheduleUpdate(); // 触发 reconcile
};
if (hook.queue.pending !== null) {
hook.state = typeof hook.queue.pending === 'function'
? hook.queue.pending(hook.state) : hook.queue.pending;
hook.queue.pending = null;
}
currentHook = currentHook.next;
return [hook.state, hook.queue.dispatch];
}
五、React 渲染机制
4.15 虚拟 DOM 与 Diff
- React 同层比较,按 type 复用;type 不同直接重建。
- 列表用 key 对齐,相同 key 复用。
- Diff 算法假设 key 稳定。
4.16 Fiber 架构
为什么需要 Fiber?React 15 的栈式 reconcile 是递归同步,会阻塞主线程。Fiber 把它改成可中断、可恢复的工作单元。
erlang
Render 阶段:beginWork → completeWork →...
Commit 阶段:DOM 更新
- 每个 fiber 有
return / child / sibling链表。 - render 阶段可中断(
workLoopConcurrent); - 中断条件:时间切片(默认 5ms);满足后让位给浏览器。
4.17 双缓冲 + Commit
current tree 与 workInProgress tree 交替更新,commit 时切换。
4.18 React 18 并发模式
createRoot启用并发useTransition/useDeferredValue- 自动批处理(无论同步 / 异步事件)
- Suspense 与 Streaming SSR
tsx
const [isPending, startTransition] = useTransition();
function onChange(e) {
setInput(e.target.value);
startTransition(() => setQuery(e.target.value));
}
const deferred = useDeferredValue(query);
4.19 useTransition vs useDeferredValue
useTransition:把更新标记为 transition,并返回 isPending。useDeferredValue:延迟某个值的更新时机。
七、状态管理方案
4.20 Redux Toolkit
ts
import { configureSlice, createSlice } from '@reduxjs/toolkit';
const counter = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
inc: (s) => { s.value += 1; },
incBy: (s, a: PayloadAction<number>) => { s.value += a.payload; },
},
});
export const { inc, decideBy } = counter.actions;
export default counter.reducer;
// createAsyncThunk
const fetchUser = createAsyncThunk('user/fetch', async (id: string) => {
return await api.user(id);
});
4.21 Zustand
ts
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const useStore = create(immer((set, get) => ({
count: 0,
inc: () => set((s) => { s.count++; }),
reset: () => set({ count: 0 }),
})));
const count = useStore((s) => s.count);
4.22 Jotai / Recoil(原子化)
ts
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubleAtom = atom((get) => get(countAtom) * 2);
const [count, setCount] = useAtom(countAtom);
4.23 Mobx
观察者模式、Proxy 透明化:makeAutoObservable(this)。
4.24 状态管理选型
| 状态方案 | 适用 |
|---|---|
| useState / useReducer | 局部 |
| Context | 低频变化主题/配置 |
| Redux | 中大型工程,强 schema |
| Zustand | 简洁、轻量 |
| Jotai/Recoil | 原子化派生 |
| MobX | OOP 复杂业务 |
| React Query / SWR | 服务端缓存 |
八、React Router 6
4.25 基础用法
tsx
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/:id" element={<User />} />
<Route element={<Layout />}>
<Route index element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
4.26 数据加载 Loader
tsx
const router = createBrowserRouter([
{
path: '/user/:id',
element: <User />,
loader: ({ params }) => fetch(`/api/user/${params.id}`),
},
]);
function User() {
const user = useLoaderData();
}
4.27 编程式跳转
tsx
const navigate = useNavigate();
navigate('/home');
navigate(-1);
navigate('/search', { state: { q: 'react' } });
九、React Query / TanStack Query
ts
import { useQuery } from '@tanstack/react-query';
function User({ id }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/user/${id}`).then(r => r.json()),
staleTime: 60_000,
});
}
支持自动重试、轮询、分页、乐观更新:
ts
useMutation({
mutationFn: update,
onMutate: async (v) => { await qc.cancelQueries(); const prev = qc.getQueryData(['user']); qc.setQueryData(['user'], (old) => ({ ...old, ...v })); return { prev }; },
onError: (_e, _v, ctx) => qc.setQueryData(['user'], ctx.prev),
});
十、性能优化
4.28 三大方向
- 减少渲染次数:memo、useMemo、useCallback、key
- 减少渲染内容:分页、虚拟列表、懒加载
- 减少单次渲染工作:web worker、并发渲染(transition)
4.29 React.memo
tsx
const Child = memo(function Child({ value }: { value: number }) {
console.log('child render');
return <div>{value}</div>;
});
4.30 虚拟列表
tsx
import { FixedSizeList } from 'react-window';
<FixedSizeList height={400} itemCount={100000} itemSize={36}>
{({ index, style }) => <div style={style}>{data[index]}</div>}
</FixedSizeList>
4.31 拆分代码 / 懒加载
tsx
const Heavy = React.lazy(() => import('./Heavy'));
<Suspense fallback={<Loading />}>
<Heavy />
</Suspense>
4.32 避免 inline 对象/函数
tsx
// 不好:每次新对象,子组件无意义的渲染
<Child style={{ color: 'red' }} onClick={() => fn()} />
// 好:提出来 / 缓存
const style = useMemo(() => ({ color: 'red' }), []);
const cb = useCallback(fn, []);
4.33 useImperativeHandle / forwardRef
tsx
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
}));
return <input ref={inputRef} />;
});
4.34 Profiler API
tsx
<Profiler id="List" onRender={(id, phase, actualDuration) => log(id, actualDuration)}>
<List />
</Profiler>
十一、SSR / Next.js
4.35 渲染模式
| 模式 | 说明 |
|---|---|
| CSR | 客户端渲染(默认) |
| SSR | 服务端渲染,水合 |
| SSG | 构建时静态生成 |
| ISR | 增量静态生成 |
| Streaming | 边渲染边流回浏览器 |
4.36 Next.js 13+ App Router
tsx
// app/user/[id]/page.tsx
export default async function Page({ params }: { params: { id: string } }) {
const data = await fetch(`https://api/user/${params.id}`).then(r => r.json());
return <User data={data} />;
}
- Server Component 默认
'use client'声明客户端组件loading.tsx、error.tsx、not-found.tsx约定文件
4.37 注意事项
- hydration mismatch:服务端 HTML 与首次渲染不一致
- Context 在 server component 中不可用
- 客户端组件引入到 server 组件会变成客户端组件
十二、错误边界
4.38 ErrorBoundary
tsx
class ErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error, info: ErrorInfo) { report(error, info); }
render() { return this.state.hasError ? <Fallback /> : this.props.children; }
}
注:函数组件不支持 getDerivedStateFromError,需要 class。可用 react-error-boundary 库。
十三、Refs / DOM
4.39 常见操作
tsx
const ref = useRef<HTMLDivElement>(null);
useEffect(() => { ref.current?.focus(); }, []);
// forwardRef
const Child = forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
));
// flushSync:立刻同步更新 state
import { flushSync } from 'react-dom';
flushSync(() => setCount(c => c + 1));
十四、TypeScript 与 React
4.40 组件 Props 泛型
tsx
interface ListProps<T> { items: T[]; render: (item: T) => React.ReactNode }
function List<T extends { id: string }>({ items, render }: ListProps<T>) {
return <>{items.map(render)}</>;
}
// forwardRef 泛型
const Input = forwardRef<HTMLInputElement, { value: string }>((props, ref) => <input ref={ref} value={props.value} />);
4.41 useRef 严格类型
tsx
const ref = useRef<HTMLInputElement>(null);
// ref.current 为 HTMLInputElement | null
ref.current?.focus();
十五、面试高频速答
4.42 setState 是同步还是异步?
- 在 React 18 自动批处理后,几乎所有 setState 都是异步(包括 Promise/setTimeout 内)。
- 想强制同步:
flushSync。 - 函数式 setState 一定能拿到最新值。
4.43 为什么列表要有 key?
- diff 性能 + DOM 复用;
- 不要用 index(增删会引起重渲染错位);用稳定唯一 id。
4.44 受控 vs 非受控?
- 受控:状态由 React 管理,便于校验;
- 非受控:状态由 DOM 管理,性能略好,集成第三方控件常用。
4.45 React 18 自动批处理
tsx
// 之前:定时器内 2 次 setState 会渲染 2 次
// 现在:只渲染 1 次
4.46 HOC vs Hooks
- HOC 是组件包装(
withXxx(Component)) - Hooks 是函数式复用(更优)
- HOC 会增加层级、命名冲突、调试难
4.47 React 与 Vue 思想差异
| 维度 | React | Vue |
|---|---|---|
| 模板 | JSX | 模板(可选 JSX) |
| 响应式 | 单向数据流 + 手动 setState | 自动依赖追踪 |
| 心智模型 | UI = f(state) | 数据变化自动重渲染 |
| 生态 | 多套状态管理 | Pinia 统一 |
4.48 React.memo 与 useMemo 区别
React.memo缓存组件useMemo缓存计算结果
4.49 如何避免不必要的渲染?
- memo 组件
- useMemo 缓存对象/数组
- 状态最小化(细粒度 Context)
- selector 模式(zustand/jotai)
- 拆分组件,按需重渲染
4.50 useEffect 清理函数的常见错误
- 没在 cleanup 里取消订阅/计时器 → 内存泄漏
- 异步回调里修改 state → 警告"在已卸载组件上 setState"
tsx
useEffect(() => {
let cancelled = false;
fetch().then((d) => { if (!cancelled) setData(d); });
return () => { cancelled = true; };
}, []);
十六、React 19 预览(如果面试问到)
- Actions(async transition)
useFormStatus、useFormState- Server Actions
useAPI(读 Promise/Context)- 资源预加载(
<link rel="preload">)
想深入手写底层,可参考《09-手写代码专项.md》。