React 实战指南
React 是前端两大框架之一,也是 CSDN 前端板块流量最大的话题。本文从核心思想讲起,覆盖 JSX、组件、props/state、Hooks(useState/useEffect/useMemo/useCallback)、组件通信、React Router、状态管理、性能优化、与 Vue 对比,全程带可运行代码。
一、React 核心思想
1.1 声明式 UI
命令式(传统 DOM 操作):
js
// 手动操作 DOM:增删改都要自己写
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
document.getElementById('count').textContent = '1';
});
声明式(React):
jsx
// 只描述"界面应该长什么样",React 负责更新
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>{count}</button>
核心 :数据驱动视图------数据变了,React 自动重新渲染。
1.2 组件化
页面拆成独立组件,每个组件管自己的 UI + 逻辑:
App
├── Header
├── ProductList
│ └── ProductCard × N
└── Footer
1.3 单向数据流
数据从父组件流向子组件(props),子组件通过事件回调通知父组件------方向明确,好排查。
二、JSX 与渲染
2.1 JSX 是什么
JSX = JavaScript + XML,在 JS 里写类似 HTML 的语法。不是模板字符串,会被编译成 React.createElement 调用。
jsx
// JSX
const element = <h1>你好,{name}</h1>;
// 编译后等价于
const element = React.createElement('h1', null, '你好,', name);
2.2 JSX 语法要点
jsx
// 表达式用 {}(不是字符串模板)
const age = 25;
<p>年龄:{age}</p>
<p>{age > 18 ? '成年' : '未成年'}</p>
// 条件渲染
{isLogin && <span>欢迎回来</span>}
{isLogin ? <LogoutButton /> : <LoginButton />}
// 列表渲染(必须加 key)
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
// 内联样式(驼峰命名,值是对象)
<div style={{ color: 'red', fontSize: '16px' }}>文字</div>
// class 用 className(class 是保留字)
<div className="card">卡片</div>
2.3 事件处理
jsx
function Button() {
const handleClick = (e) => {
e.preventDefault(); // 阻止默认行为
console.log('点击了', e.target);
};
return <button onClick={handleClick}>点击</button>;
}
三、组件与 props
3.1 函数组件(现代标准)
jsx
// 函数组件:接收 props,返回 JSX
function Greeting({ name, age = 18 }) { // 解构 + 默认值
return <h1>你好,{name}({age}岁)</h1>;
}
// 使用
<Greeting name="张三" age={25} />
3.2 props 不可变
props 是只读的------组件不能改自己的 props。要变的数据用 state。
3.3 children 插槽
jsx
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div>{children}</div> {/* 子元素 */}
</div>
);
}
<Card title="订单信息">
<p>订单号:12345</p> {/* 作为 children 传入 */}
</Card>
四、State:组件状态
4.1 useState 基础
jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 初始值 0
return (
<div>
<p>当前:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useState 规则(必考):
- 返回
[当前值, 更新函数]; - 更新函数触发重新渲染;
- 值变了才重渲染(Object.is 比较)。
4.2 setState 的异步性
jsx
const [count, setCount] = useState(0);
// 错误:连续 set 只生效一次(闭包里的 count 是旧值)
setCount(count + 1);
setCount(count + 1); // 结果还是 1
// 正确:用函数式更新
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 结果 2
原因:React 批处理更新,函数式更新拿到的是最新值。
4.3 对象/数组 state 更新(不可变更新)
jsx
const [user, setUser] = useState({ name: '张三', age: 25 });
const [list, setList] = useState([1, 2, 3]);
// 错误:直接改对象(不触发渲染)
// user.age = 26;
// 正确:创建新对象
setUser({ ...user, age: 26 });
// 数组增删改
setList([...list, 4]); // 追加
setList(list.filter(x => x !== 2)); // 删除
setList(list.map(x => x * 2)); // 变换
原则:state 不可变更新------永远创建新值,不修改旧值。
五、Hooks 详解
5.1 useEffect:副作用(请求、订阅、DOM 操作)
jsx
import { useEffect, useState } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 组件挂载后执行(模拟 componentDidMount)
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => console.error(err));
// 返回清理函数(组件卸载时执行)
// return () => { ... };
}, []); // 依赖数组:空 = 只执行一次
if (loading) return <p>加载中...</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
useEffect 依赖数组(重点):
| 写法 | 执行时机 |
|---|---|
| 无依赖数组 | 每次渲染都执行 |
[] |
挂载时执行一次 |
[a, b] |
a 或 b 变化时执行 |
无限循环坑:
jsx
// 错误:setState 触发渲染 → 渲染触发 effect → 无限循环
useEffect(() => {
setCount(count + 1); // count 变化 → effect 再执行 → 再 set...
}, [count]);
5.2 useRef:引用(不触发渲染)
jsx
import { useRef } from 'react';
function InputFocus() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // 直接操作 DOM
};
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>聚焦</button>
</>
);
}
用途:
- 操作 DOM(focus、滚动);
- 存可变值(定时器 ID)------改 ref 不触发渲染;
- 保存上一次的值。
5.3 useMemo:缓存计算结果
jsx
import { useMemo } from 'react';
// 计算量大时才用(普通计算用不上)
const total = useMemo(() => {
return orders.reduce((s, o) => s + o.amount, 0);
}, [orders]); // orders 变才重算
5.4 useCallback:缓存函数
jsx
import { useCallback } from 'react';
// 传给子组件的函数要缓存(防止子组件每次重渲染)
const handleDelete = useCallback((id) => {
setList(list => list.filter(x => x.id !== id));
}, []); // 依赖不变,函数引用不变
useMemo/useCallback 什么时候用 :子组件用 React.memo 时,父组件的函数不缓存会让 memo 失效。普通组件别滥用(有开销)。
5.5 自定义 Hook
jsx
// useWindowSize:监听窗口大小
import { useState, useEffect } from 'react';
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () =>
setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 组件里使用
const { width, height } = useWindowSize();
命名规范 :以 use 开头;内部可以调其他 Hooks;不同组件共享同一 Hook 时状态独立。
六、组件通信
6.1 父子:props + 回调
jsx
// 父组件
function Parent() {
const [count, setCount] = useState(0);
return (
<Child
count={count}
onIncrement={() => setCount(count + 1)} // 传回调
/>
);
}
// 子组件
function Child({ count, onIncrement }) {
return (
<button onClick={onIncrement}>当前:{count}</button>
);
}
6.2 跨层级:Context
jsx
import { createContext, useContext, useState } from 'react';
// 1. 创建 Context
const ThemeContext = createContext('light');
// 2. 提供者(顶层)
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
// 3. 任意后代使用
function Button() {
const { theme } = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
注意:Context 值变化会让所有消费它的组件重渲染------别塞频繁变化的数据。
6.3 全局状态:Redux / Zustand
Zustand(轻量推荐):
js
import { create } from 'zustand';
const useStore = create((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null })
}));
// 组件里
function Profile() {
const user = useStore(s => s.user);
const logout = useStore(s => s.logout);
return <button onClick={logout}>{user?.name},退出</button>;
}
Redux Toolkit(大型项目):生态成熟、DevTools 强,但样板代码多。
七、React Router
7.1 安装与配置
bash
npm install react-router-dom
jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/products">商品</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
7.2 动态路由参数
jsx
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
function ProductDetail() {
const { id } = useParams(); // /products/1001 → id = "1001"
const navigate = useNavigate();
const [params] = useSearchParams(); // ?page=2
return (
<div>
<p>商品 ID:{id}</p>
<button onClick={() => navigate('/products')}>返回</button>
</div>
);
}
7.3 路由守卫(登录拦截)
jsx
function RequireAuth({ children }) {
const isLogin = useStore(s => s.user !== null);
if (!isLogin) {
return <Navigate to="/login" replace />;
}
return children;
}
// 使用
<Route path="/orders" element={
<RequireAuth><Orders /></RequireAuth>
} />
八、性能优化
8.1 React.memo:组件跳过重渲染
jsx
import { memo } from 'react';
// 父组件重渲染时,props 没变的子组件跳过
const ProductCard = memo(function ProductCard({ product, onBuy }) {
return (
<div>
<p>{product.name}</p>
<button onClick={() => onBuy(product.id)}>购买</button>
</div>
);
});
配合 useCallback :onBuy 不缓存的话,每次都是新函数引用,memo 失效。
8.2 key 的重要性
jsx
// 错误:用 index 做 key(删除/排序时状态错乱)
{items.map((item, i) => <Row key={i} item={item} />)}
// 正确:用稳定唯一 id
{items.map(item => <Row key={item.id} item={item} />)}
8.3 懒加载(代码分割)
jsx
import { lazy, Suspense } from 'react';
// 路由级懒加载:进入页面才加载对应 chunk
const Orders = lazy(() => import('./pages/Orders'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/orders" element={<Orders />} />
</Routes>
</Suspense>
);
}
8.4 常见优化清单
- 列表渲染加稳定 key;
- 大列表用虚拟滚动(react-window);
- 防抖搜索输入;
- 图片懒加载(loading="lazy");
- 函数/计算用 useCallback/useMemo(配合 memo 才有意义);
- 打包分析(vite-bundle-visualizer)。
九、React vs Vue(面试常问)
| 维度 | React | Vue3 |
|---|---|---|
| 模板 | JSX(JS 里写 UI) | 模板语法(HTML 扩展) |
| 数据流 | 单向(props 下传) | 单向 + v-model 双向 |
| 状态管理 | Redux/Zustand | Pinia |
| 响应式 | setState 触发 | 自动追踪依赖 |
| 上手 | 心智模型稍难 | 模板友好,上手快 |
| 生态 | 最大 | 大 |
| 适用 | 复杂交互、大团队 | 中小项目、快速开发 |
选型建议:都不是玄学------看团队熟悉度和项目类型。
十、常见坑速查
| 现象 | 原因 | 解决 |
|---|---|---|
| setState 不生效 | 直接修改对象 | 不可变更新 |
| 连续 set 只加 1 | 闭包旧值 | 函数式更新 |
| 无限循环 | effect 里 setState 依赖自身 | 检查依赖数组 |
| useEffect 执行多次 | 依赖没写/写错 | 明确依赖 |
| memo 不生效 | 函数没缓存 | useCallback |
| key 用 index | 列表重排状态错 | 用稳定 id |
| 组件重渲染很多 | 数据/引用变了 | memo + useCallback |
本章小结
React 的核心心智:声明式 UI + 单向数据流 + 组件化。日常开发 80% 时间在 useState/useEffect/props 三个工具上,10% 在路由和状态管理,10% 在性能优化。把 Hooks 的规则(不可变更新、依赖数组、函数式更新)吃透,React 就入门了。
下一篇讲计算机网络与 HTTP------面试必考的地基话题。
(全文完)