> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# React 完全指南
- URL: https://blog.vercanti.com/react-wan-quan-zhi-nan/
- Published: 2026-08-28T14:35:17.000Z
- Updated: 2026-08-28T14:58:31.000Z
- Description: 相关文档：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 的语
- Author: yellowdog
- Tags: 前端开发, React生态

> 官方文档：<https://react.dev/>  
> 适用版本：React 19（2026-05-07 核实）

相关文档：[TypeScript完全指南](https://blog.vercanti.com/typescript-wan-quan-zhi-nan/) [TanStack Query 完全指南](https://blog.vercanti.com/tanstack-query-wan-quan-zhi-nan/) [TanStack Router 完全指南](https://blog.vercanti.com/tanstack-router-wan-quan-zhi-nan/)

---

## 1\. 基础概念

### React 是什么

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

### React vs Vue3

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

### 创建项目

```bash
# 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 的标记：

```tsx
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\. 组件

### 函数组件

```tsx
// 最简单的函数组件
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 默认值

```tsx
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 — 状态

```tsx
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 — 副作用

```tsx
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 — 引用

```tsx
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 — 缓存计算结果

```tsx
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 — 缓存函数引用

```tsx
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 — 复杂状态管理

```tsx
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 — 跨组件共享状态

```tsx
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` 开头的函数，封装可复用的状态逻辑：

```tsx
// 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 与性能优化

```tsx
// 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\. 常用代码段

### 受控表单

```tsx
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）

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

```tsx
// 服务端数据（远程状态）→ 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 常见的错误用法：

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

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

```

---

## 9\. 踩坑与注意事项

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

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

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

```

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

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

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

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

```

### 列表的 key 不要用 index

```tsx
// 不推荐：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）；缺少清理函数会导致组件卸载后定时器 / 事件监听仍在运行，产生内存泄漏。

```tsx
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 认为是同一元素，状态不重置。

**解决：** 始终用数据的唯一标识符（`id`、`uuid`）作为 key。

```tsx
// 错误
{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)`。

```tsx
// 问题：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` 中添加的副作用必须在返回函数中清理。

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

```

---

## 参见

- [TypeScript完全指南](https://blog.vercanti.com/typescript-wan-quan-zhi-nan/)
- [TanStack Query 完全指南](https://blog.vercanti.com/tanstack-query-wan-quan-zhi-nan/)
- [TanStack Router 完全指南](https://blog.vercanti.com/tanstack-router-wan-quan-zhi-nan/)
- [Zustand完全指南](https://blog.vercanti.com/zustand-wan-quan-zhi-nan/)
- [Next.js完全指南](https://blog.vercanti.com/next-js-wan-quan-zhi-nan/)

```

```