React 完全指南

相关文档:TypeScript完全指南(/typescript-wan-quan-zhi-nan/) TanStack Query 完全指南(/tanstack-query-wan-quan-zhi-nan/) TanStack Router 完全指南(/tanstack-router-wan-quan-zhi-nan/) React 是 Meta 开发的用于构建用户界面的 JavaScript 库,核心思想是声明式(描述 UI 应该是什么样子,而不是如何操作 DOM)和组件化(将 UI 拆分为可复用的独立部分)。 JSX 是 JavaScript 的语

分享

官方文档:https://react.dev/
适用版本:React 19(2026-05-07 核实)

相关文档:TypeScript完全指南 TanStack Query 完全指南 TanStack Router 完全指南


1. 基础概念

React 是什么

React 是 Meta 开发的用于构建用户界面的 JavaScript 库,核心思想是声明式(描述 UI 应该是什么样子,而不是如何操作 DOM)和组件化(将 UI 拆分为可复用的独立部分)。

React vs Vue3

特性 React Vue 3
范式 函数式、JSX 模板语法(可选 JSX)
状态管理 useState + useReducer ref + reactive
响应式 手动触发(setState) 自动追踪
生态 极其丰富 丰富
学习曲线 中等(JSX 思维转变) 低(模板更直观)
TypeScript 天然友好 良好

创建项目

# Vite(推荐)
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run dev

# Next.js(SSR/SSG 首选)
npx create-next-app@latest my-app --typescript --tailwind

2. JSX

JSX 是 JavaScript 的语法扩展,允许在 JS 中写类 HTML 的标记:

function Greeting({ name }: { name: string }) {
  const isLoggedIn = true;
  const items = ["Apple", "Banana", "Cherry"];

  return (
    <div className="container">    {/* class 写成 className */}
      <h1>Hello, {name}!</h1>      {/* {} 内写 JS 表达式 */}

      {/* 条件渲染 */}
      {isLoggedIn ? <span>已登录</span> : <span>未登录</span>}
      {isLoggedIn && <button>退出</button>}

      {/* 列表渲染(key 必须是唯一稳定的值) */}
      <ul>
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>

      {/* 内联样式 */}
      <p style={{ color: "red", fontSize: 16 }}>警告信息</p>
    </div>
  );
}

3. 组件

函数组件

// 最简单的函数组件
function Button({ label, onClick }: { label: string; onClick: () => void }) {
  return <button onClick={onClick}>{label}</button>;
}

// 带 children 的组件
function Card({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  );
}

// 使用
<Card title="用户信息">
  <p>这是卡片内容</p>
</Card>

Props 默认值

interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  onClick?: () => void;
}

function Button({ label, variant = "primary", disabled = false, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

4. Hooks

useState — 状态

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState<{ name: string; age: number } | null>(null);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      {/* 函数更新(依赖旧值时推荐) */}
      <button onClick={() => setCount(prev => prev + 1)}>安全 +1</button>

      <button onClick={() => setUser({ name: "Alice", age: 25 })}>设置用户</button>
      {/* 更新对象:必须展开旧值 */}
      <button onClick={() => setUser(prev => prev ? { ...prev, age: 26 } : null)}>
        更新年龄
      </button>
    </div>
  );
}

useEffect — 副作用

import { useEffect, useState } from "react";

function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 副作用:数据获取(建议用 TanStack Query 替代)
    let cancelled = false;

    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    // 清理函数:组件卸载或 userId 变化时执行
    return () => { cancelled = true; };
  }, [userId]); // 依赖数组:userId 变化时重新执行

  // 依赖数组为 []:只在组件挂载时执行一次
  useEffect(() => {
    document.title = "用户信息";
    return () => { document.title = "My App"; };
  }, []);

  return <div>{user?.name}</div>;
}

useRef — 引用

import { useRef, useEffect } from "react";

function VideoPlayer() {
  // DOM 引用
  const videoRef = useRef<HTMLVideoElement>(null);

  // 普通值引用(不触发重渲染,适合存定时器 ID 等)
  const timerRef = useRef<number | null>(null);

  useEffect(() => {
    videoRef.current?.play();

    timerRef.current = setInterval(() => {
      console.log("tick");
    }, 1000);

    return () => {
      if (timerRef.current) clearInterval(timerRef.current);
    };
  }, []);

  return <video ref={videoRef} src="/video.mp4" />;
}

useMemo — 缓存计算结果

import { useMemo } from "react";

function ProductList({ products, keyword }: { products: Product[]; keyword: string }) {
  // 只有 products 或 keyword 变化时才重新过滤
  const filtered = useMemo(
    () => products.filter(p => p.name.includes(keyword)),
    [products, keyword]
  );

  return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

useCallback — 缓存函数引用

import { useCallback } from "react";

function Parent() {
  const [count, setCount] = useState(0);

  // 如果不用 useCallback,每次 Parent 重渲染都会创建新函数
  // 导致传入 Child 的 onClick prop 变化,Child 也会重渲染
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []); // 空依赖:函数永远不变

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
      <Child onClick={handleClick} />
    </div>
  );
}

// 只有 onClick 变化时才重渲染
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
  console.log("Child rendered");
  return <button onClick={onClick}>子按钮</button>;
});

useReducer — 复杂状态管理

import { useReducer } from "react";

type State = { count: number; step: number };
type Action =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset" }
  | { type: "setStep"; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "increment": return { ...state, count: state.count + state.step };
    case "decrement": return { ...state, count: state.count - state.step };
    case "reset": return { ...state, count: 0 };
    case "setStep": return { ...state, step: action.payload };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
  return (
    <div>
      <p>count: {state.count}, step: {state.step}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "setStep", payload: 5 })}>步长=5</button>
    </div>
  );
}

useContext — 跨组件共享状态

import { createContext, useContext, useState } from "react";

interface AuthContextType {
  user: User | null;
  login: (user: User) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

// Provider
function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  return (
    <AuthContext.Provider value={{
      user,
      login: (u) => setUser(u),
      logout: () => setUser(null),
    }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义 Hook(封装 useContext,提供类型安全)
function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used within AuthProvider");
  return ctx;
}

// 使用
function Header() {
  const { user, logout } = useAuth();
  return <div>{user ? <button onClick={logout}>退出</button> : "未登录"}</div>;
}

5. 自定义 Hook

自定义 Hook 是以 use 开头的函数,封装可复用的状态逻辑:

// useLocalStorage
import { useState } from "react";

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setStoredValue = (newValue: T) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  };

  return [value, setStoredValue] as const;
}

// useDebounce
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// 使用
function SearchInput() {
  const [keyword, setKeyword] = useState("");
  const debouncedKeyword = useDebounce(keyword, 300);

  // 用 debouncedKeyword 发起请求,避免每次输入都请求
  const { data } = useQuery({
    queryKey: ["search", debouncedKeyword],
    queryFn: () => search(debouncedKeyword),
    enabled: debouncedKeyword.length > 0,
  });
}

6. React.memo 与性能优化

// React.memo:props 不变时跳过重渲染
const ExpensiveComponent = React.memo(({ data }: { data: string[] }) => {
  return <ul>{data.map(d => <li key={d}>{d}</li>)}</ul>;
});

// 自定义比较函数
const Component = React.memo(MyComponent, (prevProps, nextProps) => {
  return prevProps.id === nextProps.id;  // 返回 true 则不重渲染
});

7. 常用代码段

受控表单

function LoginForm() {
  const [form, setForm] = useState({ email: "", password: "" });
  const [error, setError] = useState("");

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await login(form);
    } catch (err) {
      setError("登录失败");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" value={form.email} onChange={handleChange} />
      <input name="password" type="password" value={form.password} onChange={handleChange} />
      {error && <p>{error}</p>}
      <button type="submit">登录</button>
    </form>
  );
}

错误边界(Error Boundary)

import { Component, ErrorInfo, ReactNode } from "react";

class ErrorBoundary extends Component<
  { children: ReactNode; fallback?: ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    console.error(error, info);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <div>出错了,请刷新页面</div>;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary fallback={<div>加载失败</div>}>
  <UserList />
</ErrorBoundary>

8. 最佳实践

状态提升

当多个组件需要共享状态时,将状态提升到最近的公共父组件。避免过度使用 Context(Context 变化会导致所有消费者重渲染)。

服务端数据用 TanStack Query,客户端状态用 useState

// 服务端数据(远程状态)→ TanStack Query
const { data: users } = useQuery({ queryKey: ["users"], queryFn: fetchUsers });

// 客户端状态(UI 状态)→ useState
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedId, setSelectedId] = useState<number | null>(null);

避免 useEffect 滥用

useEffect 常见的错误用法:

// 错误:用 useEffect 同步状态(应该直接在渲染中计算)
const [fullName, setFullName] = useState("");
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

// 正确:直接计算
const fullName = `${firstName} ${lastName}`;

9. 踩坑与注意事项

不能在条件/循环中使用 Hook

// 错误
if (condition) {
  const [state, setState] = useState(0); // 违反 Hook 规则
}

// 正确:Hook 必须在组件顶层无条件调用
const [state, setState] = useState(0);
if (condition) { /* 使用 state */ }

state 更新是异步的,不能立即读取新值

const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1);
  console.log(count); // 还是旧值!React 批量处理更新
};

// 如果下次更新依赖当前值,用函数式更新
setCount(prev => prev + 1);

列表的 key 不要用 index

// 不推荐:key=index 在列表顺序变化时会导致状态错乱
{items.map((item, index) => <Item key={index} {...item} />)}

// 推荐:使用稳定的唯一 ID
{items.map((item) => <Item key={item.id} {...item} />)}

---

## 最佳实践

**组件保持小而专注(Single Responsibility)**:一个组件只做一件事。超过 200 行的组件通常需要拆分,提取独立的 hook 或子组件。大组件难以测试、复用和理解。

**用自定义 Hook 封装可复用逻辑**:将 `useState + useEffect + 业务逻辑` 提取为 `useXxx` 函数,组件只关心渲染,逻辑可在多个组件间复用和单独测试。

```tsx
function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
  useEffect(() => {
    const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight })
    window.addEventListener('resize', handler)
    return () => window.removeEventListener('resize', handler)
  }, [])
  return size
}

useEffect 依赖数组要完整,且清理副作用:缺少依赖会导致闭包过期(stale closure);缺少清理函数会导致组件卸载后定时器 / 事件监听仍在运行,产生内存泄漏。

useEffect(() => {
  const timer = setInterval(() => setCount(c => c + 1), 1000)
  return () => clearInterval(timer)   // 清理函数
}, [])   // 空依赖 = 只执行一次

服务端状态用 TanStack Query,客户端 UI 状态用 useState:网络请求数据(loading / error / data / cache / refetch)是服务端状态,用 TanStack Query 管理比手写 useState + useEffect 少写 80% 代码,且自动缓存、去重、后台刷新。

避免在渲染函数中直接修改 state:在 JSX 渲染期间调用 setState 会导致无限渲染循环。所有 state 更新放在事件处理函数或 useEffect 中。


常见陷阱

陷阱:列表 key 用 index 导致状态错乱

现象: 删除列表中间的某项后,其他项的输入框内容错位、复选框勾选状态混乱。

原因: React 用 key 识别哪些元素发生了变化。用 index 作为 key 时,删除第 2 项后,原来第 3 项的 key 变成了 2(与原第 2 项相同),React 认为是同一元素,状态不重置。

解决: 始终用数据的唯一标识符(iduuid)作为 key。

// 错误
{items.map((item, i) => <Input key={i} value={item.name} />)}

// 正确
{items.map(item => <Input key={item.id} value={item.name} />)}

陷阱:闭包过期(Stale Closure)

现象:useEffect 中的定时器或事件回调中读取 state,始终是初始值(如 0),不是最新值。

原因: 函数在创建时捕获了当时的变量值(闭包),即使 state 已更新,旧的函数仍引用旧值。

解决:useEffect 依赖数组中加入 state 变量,或使用函数式更新 setCount(prev => prev + 1)

// 问题:count 永远是 0
useEffect(() => {
  setInterval(() => console.log(count), 1000)
}, [])

// 正确:用函数式更新
useEffect(() => {
  setInterval(() => setCount(c => c + 1), 1000)
  return () => clearInterval(timer)
}, [])

陷阱:useEffect 缺少清理导致内存泄漏

现象: 组件卸载后控制台报 Can't perform a React state update on an unmounted component,或事件监听在组件销毁后仍触发。

原因: useEffect 中添加了事件监听、定时器、WebSocket 等副作用,但没有返回清理函数,组件卸载后这些副作用仍在运行。

解决: 所有 useEffect 中添加的副作用必须在返回函数中清理。

useEffect(() => {
  const ws = new WebSocket(url)
  ws.onmessage = (e) => setMessages(m => [...m, e.data])
  return () => ws.close()   // 清理:组件卸载时关闭 WebSocket
}, [url])

参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog