React 基础

20 阅读7分钟

涵盖:组件、JSX、Props/State、生命周期、Hooks、状态管理、路由、SSR、性能优化、并发模式。


一、JSX 与组件

4.1 JSX 是什么?

JSX 是 JS 的语法扩展,React.createElement(component, props, ...children) 的语法糖。

const el = <div className="box">{name}</div>;
// 编译后:
React.createElement('div', { className: 'box' }, name);

TypeScript 中:const el: React.ReactNode。

4.2 函数组件 vs 类组件

维度函数组件类组件
状态Hooksthis.setState
生命周期useEffectcomponentDidMount 等
性能轻需要手动绑定
推荐React 18 强推维护老代码

4.3 受控与非受控组件

// 受控
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路由间
// 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 等价
constructoruseState initializer
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [deps])
componentWillUnmountuseEffect 的 cleanup
getDerivedStateFromProps在 render 中用 props 派生
shouldComponentUpdateReact.memo / useMemo
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

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

useEffectuseLayoutEffect
时机渲染后异步执行DOM 更新后同步执行
阻塞渲染否是
典型数据请求、订阅DOM 测量、布局
useEffect(() => { /* paint 之后 */ }, []);
useLayoutEffect(() => { /* paint 之前 */ }, []);

4.9 useRef 的三种用法

const domRef = useRef<HTMLDivElement>(null);
const timerRef = useRef<number | null>(null);
const prevCount = useRef(count); // 跨渲染保存值

4.10 useMemo / useCallback

const memo = useMemo(() => expensive(list), [list]);
const cb = useCallback(() => do(id), [id]);
  • 缓存计算结果 / 函数引用。
  • 不要过度使用:useMemo 本身有比较成本。
  • 配合 React.memo 子组件避免无谓渲染。

4.11 useReducer

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 范式

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 实现原理(手写)

// 简化版:链表存 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 把它改成可中断、可恢复的工作单元。

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
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

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

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(原子化)

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原子化派生
MobXOOP 复杂业务
React Query / SWR服务端缓存

八、React Router 6

4.25 基础用法

<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

const router = createBrowserRouter([
  {
    path: '/user/:id',
    element: <User />,
    loader: ({ params }) => fetch(`/api/user/${params.id}`),
  },
]);

function User() {
  const user = useLoaderData();
}

4.27 编程式跳转

const navigate = useNavigate();
navigate('/home');
navigate(-1);
navigate('/search', { state: { q: 'react' } });

九、React Query / TanStack Query

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,
  });
}

支持自动重试、轮询、分页、乐观更新:

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

const Child = memo(function Child({ value }: { value: number }) {
  console.log('child render');
  return <div>{value}</div>;
});

4.30 虚拟列表

import { FixedSizeList } from 'react-window';
<FixedSizeList height={400} itemCount={100000} itemSize={36}>
  {({ index, style }) => <div style={style}>{data[index]}</div>}
</FixedSizeList>

4.31 拆分代码 / 懒加载

const Heavy = React.lazy(() => import('./Heavy'));
<Suspense fallback={<Loading />}>
  <Heavy />
</Suspense>

4.32 避免 inline 对象/函数

// 不好:每次新对象,子组件无意义的渲染
<Child style={{ color: 'red' }} onClick={() => fn()} />

// 好:提出来 / 缓存
const style = useMemo(() => ({ color: 'red' }), []);
const cb = useCallback(fn, []);

4.33 useImperativeHandle / forwardRef

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
  }));
  return <input ref={inputRef} />;
});

4.34 Profiler API

<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

// 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

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 常见操作

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 泛型

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 严格类型

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 自动批处理

// 之前:定时器内 2 次 setState 会渲染 2 次
// 现在:只渲染 1 次

4.46 HOC vs Hooks

  • HOC 是组件包装(withXxx(Component))
  • Hooks 是函数式复用(更优)
  • HOC 会增加层级、命名冲突、调试难

4.47 React 与 Vue 思想差异

维度ReactVue
模板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"
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》。