React 基础

涵盖:组件、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 三大方向

  1. 减少渲染次数:memo、useMemo、useCallback、key
  2. 减少渲染内容:分页、虚拟列表、懒加载
  3. 减少单次渲染工作: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 如何避免不必要的渲染?

  1. memo 组件
  2. useMemo 缓存对象/数组
  3. 状态最小化(细粒度 Context)
  4. selector 模式(zustand/jotai)
  5. 拆分组件,按需重渲染

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
  • use API(读 Promise/Context)
  • 资源预加载(<link rel="preload">)

想深入手写底层,可参考《09-手写代码专项.md》。

相关推荐
ZoeLandia1 小时前
TypeScript 02:interface、type 与类型收窄
前端·typescript
Bode_20021 小时前
构建制造业知识库
前端·智能制造系统·业务流程再造
JAVA老架构AI智能化1 小时前
多图叠加比对
前端·javascript·chrome
ShineWinsu1 小时前
对于HTML:HTML常见标签的解析—上
前端·面试·html·浏览器·标签·图片·超链接
百度一下吧2 小时前
HTML5 新增标签与 Web Components 使用指南
前端·html·html5
志尊宝2 小时前
Vue3 零基础每日笔记(081):富文本编辑器集成 wangEditor 5——封装
前端·javascript·vue.js·笔记·信息可视化·echarts
deli0072 小时前
纯向量检索漏掉的召回,BM25 加向量混合能补回来吗?5 组实测
前端
志尊宝2 小时前
Vue3 零基础每日笔记(080):Vue3 + ECharts 数据可视化——封装可复用图表组件
前端·javascript·vue.js·笔记·信息可视化·echarts
阿狗童鞋2 小时前
前端工程化实战指南
前端