本手册是一份结构化、可查阅、从入门到进阶的 React 综合指南,适用于前端初学者快速入门 React,也可作为中高级开发者的日常参考工具书。手册涵盖 React 核心语法、常用 API、工程化实践、路由与状态管理、性能优化等核心内容,所有示例代码均可直接复用。
- 基础准备:环境搭建与项目结构
- 核心语法:JSX 与组件基础
- 数据管理:Props 与 State 核心
- 钩子函数:Hooks 全解析(核心 + 自定义)
- 组件通信:父子 / 跨组件 / 兄弟通信方案
- 路由管理:React Router v6 实战
- 状态管理:从内置方案到主流库
- 样式方案:多种 React 样式实现
- 性能优化:组件与项目级优化技巧
- 工程化:规范、打包与部署
- 实战案例:整合核心知识点
- 附录:API 速查 & 常见问题
React 基于 JavaScript 开发,需提前安装:
- Node.js(v14.18+ 推荐,自带 npm 包管理器)
- 代码编辑器:VS Code(推荐安装 React 插件:ES7+ React Snippets)
Vite 是新一代前端构建工具,启动速度、热更新效率远超传统 CRA。
npm create vite@latest my-react-vite-app -- --template react
cd my-react-vite-app
npm install
npm run dev
npm run build
npm run preview
npx create-react-app my-react-cra-app
cd my-react-cra-app
npm start
npm run build
npm test
my-react-vite-app/
├── node_modules/ # 项目依赖包
├── dist/ # 生产环境打包输出目录
├── public/ # 静态资源(不会被 Vite 编译,直接复制到 dist)
│ └── favicon.ico # 网站图标
├── src/ # 核心源码目录(开发重点)
│ ├── assets/ # 静态资源(会被 Vite 编译,如图片、CSS)
│ ├── components/ # 公共组件(可复用,如 Button、Card)
│ ├── pages/ # 页面组件(路由对应页面,如 Home、About)
│ ├── App.jsx # 根组件
│ ├── main.jsx # 入口文件(挂载 React 到 DOM)
│ └── index.css # 全局样式
├── .gitignore # Git 忽略配置
├── index.html # 入口 HTML(Vite 特有,挂载根节点)
├── package.json # 项目配置(依赖、脚本命令)
└── vite.config.js # Vite 配置文件
React 18 新增 createRoot API,替代传统 ReactDOM.render,支持并发渲染特性。
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
JSX(JavaScript XML)是 React 的语法糖,允许在 JavaScript 中书写类 HTML 结构,最终会被 @babel/plugin-transform-react-jsx 编译为 React.createElement() 调用。
const element = (
<div className="greeting" onClick={() => console.log('hello')}>
<h1>Hello, {name}!</h1>
</div>
);
const element = React.createElement(
'div',
{ className: 'greeting', onClick: () => console.log('hello') },
React.createElement('h1', null, `Hello, ${name}!`)
);
组件是 React 应用的最小构建单元,用于封装 UI 结构、样式和行为,支持复用。React 组件分为 函数组件(现代主流)和 类组件(传统方案,逐步淘汰)。
- 本质:普通 JavaScript 函数,接收
props 参数,返回 JSX 结构
- 特点:简洁无冗余、无
this 问题、支持 Hooks 特性、性能更优
- 定义方式:箭头函数(推荐)、普通函数
const Button = (props) => {
const { text, onClick } = props;
return <button onClick={onClick}>{text}</button>;
};
function Input({ placeholder, value, onChange }) {
return <input placeholder={placeholder} value={value} onChange={onChange} />;
}
const HelloWorld = () => {
return <h1>Hello React!</h1>;
};
- 本质:继承
React.Component 的 ES6 类,必须实现 render() 方法返回 JSX
- 特点:语法冗余、存在
this 绑定问题、不支持 Hooks(需用生命周期函数替代)
import React, { Component } from 'react';
class Counter extends Component {
state = {
count: 0
};
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>计数:{this.state.count}</p>
<button onClick={this.increment}>点击加1</button>
</div>
);
}
}
组件需导出后才能在其他文件中使用,支持默认导出和命名导出。
const Button = ({ text }) => <button>{text}</button>;
export default Button;
export const PrimaryButton = ({ text }) => (
<button style={{ backgroundColor: 'blue', color: 'white' }}>{text}</button>
);
import Button from './components/Button';
import { PrimaryButton } from './components/Button';
const App = () => {
return (
<div>
<Button text="普通按钮" />
<PrimaryButton text="主要按钮" />
</div>
);
};
- 作用:实现 父子组件单向数据传递(父传子,子组件无法直接修改 props)
- 只读性:props 是只读的,修改 props 会触发 React 警告(遵循单向数据流原则)
- 传递内容:可传递基本类型(字符串、数字、布尔等)、引用类型(对象、数组)、函数(实现子传父)、JSX 元素(插槽)
const UserCard = ({ name, age, isAdult }) => {
return (
<div style={{ padding: '16px', border: '1px solid #eee' }}>
<h3>姓名:{name}</h3>
<p>年龄:{age}</p>
<p>是否成年:{isAdult ? '是' : '否'}</p>
</div>
);
};
const Parent = () => {
return (
<div>
{}
<UserCard name="张三" age={25} isAdult={true} />
{}
<UserCard name="李四" age={17} isAdult={false} />
</div>
);
};
两种设置方式:函数参数默认值(推荐)、defaultProps(兼容类组件)
const Greeting = ({ name = '匿名用户', message = '欢迎使用 React' }) => {
return <p>{name}:{message}</p>;
};
const Greeting = ({ name, message }) => {
return <p>{name}:{message}</p>;
};
Greeting.defaultProps = {
name: '匿名用户',
message: '欢迎使用 React'
};
<Greeting />
使用 prop-types 库校验 props 类型,避免传参错误(开发环境生效,生产环境可移除)
npm install prop-types --save
import PropTypes from 'prop-types';
const UserCard = ({ name, age, isAdult, hobbies }) => {
return (
<div>
<h3>{name}</h3>
<p>年龄:{age}</p>
<p>爱好:{hobbies.join(', ')}</p>
</div>
);
};
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdult: PropTypes.bool,
hobbies: PropTypes.arrayOf(PropTypes.string)
};
export default UserCard;
- 作用:管理组件自身的可变数据,状态更新会触发组件重新渲染
- 不可直接修改:必须通过状态更新函数修改,直接修改不会触发渲染
- 异步更新:React 会批量优化状态更新,无法在 setState 后立即获取最新状态
- 函数式更新:当新状态依赖上一次状态时,需使用函数形式更新
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '张三', age: 20 });
const [todos, setTodos] = useState(['学习 React', '掌握 Hooks']);
const increment = () => {
setCount(count + 1);
};
const incrementAsync = () => {
setTimeout(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
};
const updateUser = () => {
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));
};
const addTodo = () => {
setTodos(prevTodos => [...prevTodos, '学习 React Router']);
};
return (
<div style={{ padding: '16px' }}>
<p>计数:{count}</p>
<button onClick={increment}>点击加1</button>
<button onClick={incrementAsync}>异步加1</button>
<p>用户名:{user.name},年龄:{user.age}</p>
<button onClick={updateUser}>增加年龄</button>
<p>待办列表:</p>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
<button onClick={addTodo}>添加待办</button>
</div>
);
};
export default Counter;
- 只能在 函数组件 或 自定义 Hooks 中调用(不能在类组件、普通函数中调用)
- 只能在组件的 顶层作用域 调用(不能在 if/for 循环、嵌套函数中调用)
- 可通过 ESLint 插件
eslint-plugin-react-hooks 强制遵守规则
- 作用:处理函数组件中的 副作用(数据请求、DOM 操作、定时器 / 订阅、本地存储操作等)
- 执行时机:由第二个参数(依赖数组)控制
- 清除副作用:返回一个清理函数,在组件卸载或依赖更新前执行(防止内存泄漏)
import React, { useState, useEffect } from 'react';
const EffectDemo = () => {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
useEffect(() => {
console.log(`count 更新为:${count}`);
return () => {
console.log(`清理:count 即将更新`);
};
}, [count]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const result = await res.json();
setData(result);
} catch (err) {
console.error('请求失败:', err);
}
};
fetchData();
return () => {
console.log('组件卸载,清理副作用');
};
}, []);
return (
<div>
<p>count:{count}</p>
<button onClick={() => setCount(count + 1)}>加1</button>
{data && (
<div>
<h3>请求数据:</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
};
export default EffectDemo;
- 两种核心用途:
- 获取 DOM 元素 / 组件实例(需通过
ref 属性绑定)
- 保存组件生命周期内的持久化数据(组件重新渲染时,数据不丢失,且修改不会触发组件渲染)
- 核心属性:
current,用于存储引用数据 / DOM 实例
import React, { useState, useEffect, useRef } from 'react';
const RefDemo = () => {
const [inputValue, setInputValue] = useState('');
const inputRef = useRef(null);
const timerRef = useRef(null);
const prevValueRef = useRef('');
useEffect(() => {
inputRef.current.focus();
}, []);
useEffect(() => {
prevValueRef.current = inputValue;
}, [inputValue]);
const startTimer = () => {
timerRef.current = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
};
const clearTimer = () => {
clearInterval(timerRef.current);
};
return (
<div style={{ padding: '16px' }}>
{}
<input
ref={inputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="自动聚焦输入框"
/>
<p>当前值:{inputValue}</p>
<p>上一次值:{prevValueRef.current}</p>
<button onClick={startTimer}>启动定时器</button>
<button onClick={clearTimer} style={{ marginLeft: '10px' }}>清除定时器</button>
</div>
);
};
export default RefDemo;
- 作用:配合
createContext,解决 多层级非父子组件通信 问题,无需逐层传递 props(避免 props 透传)
- 核心流程:创建 Context → 提供 Context 数据(Provider)→ 消费 Context 数据(useContext)
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
const contextValue = {
theme,
toggleTheme
};
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
};
const ThemeButton = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
const buttonStyle = {
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
cursor: 'pointer'
};
return (
<button style={buttonStyle} onClick={toggleTheme}>
当前主题:{theme},点击切换
</button>
);
};
const App = () => {
return (
<ThemeProvider>
<div style={{ padding: '16px' }}>
<ThemeButton />
</div>
</ThemeProvider>
);
};
export default App;
- 作用:抽取组件中可复用的逻辑,实现逻辑复用(替代类组件中的 HOC/Render Props)
- 命名规范:必须以
use 开头(如 useTimer、useFetch),便于 React 识别 Hooks 规则
- 返回值:灵活自定义(可返回状态、方法、对象等)
- 特性:内部可调用其他内置 Hooks,遵循 Hooks 核心规则
import { useState, useEffect } from 'react';
const useFetch = (url, options = {}) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
setLoading(true);
const res = await fetch(url, { ...options, signal });
if (!res.ok) {
throw new Error(`请求失败:${res.status} ${res.statusText}`);
}
const result = await res.json();
setData(result);
setError(null);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setData(null);
}
} finally {
if (!signal.aborted) {
setLoading(false);
}
}
};
fetchData();
return () => {
controller.abort();
};
}, [url, options]);
return { data, loading, error };
};
export default useFetch;
import useFetch from './hooks/useFetch';
const FetchDemo = () => {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
if (loading) {
return <p>加载中...</p>;
}
if (error) {
return <p style={{ color: 'red' }}>错误:{error}</p>;
}
return (
<div>
<h3>数据请求结果:</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
export default FetchDemo;
import { useState, useEffect, useRef } from 'react';
const useTimer = (initialSeconds = 0) => {
const [seconds, setSeconds] = useState(initialSeconds);
const timerRef = useRef(null);
const startTimer = () => {
if (timerRef.current) return;
timerRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
};
const pauseTimer = () => {
clearInterval(timerRef.current);
timerRef.current = null;
};
const resetTimer = () => {
clearInterval(timerRef.current);
timerRef.current = null;
setSeconds(initialSeconds);
};
useEffect(() => {
return () => {
clearInterval(timerRef.current);
};
}, []);
return { seconds, startTimer, pauseTimer, resetTimer };
};
export default useTimer;
import useTimer from './hooks/useTimer';
const TimerDemo = () => {
const { seconds, startTimer, pauseTimer, resetTimer } = useTimer(0);
return (
<div style={{ padding: '16px' }}>
<h3>计时器:{seconds} 秒</h3>
<button onClick={startTimer} style={{ marginRight: '10px' }}>启动</button>
<button onClick={pauseTimer} style={{ marginRight: '10px' }}>暂停</button>
<button onClick={resetTimer}>重置</button>
</div>
);
};
export default TimerDemo;
核心思路:父组件定义回调函数并通过 props 传递给子组件,子组件调用该回调函数并传递数据。
const ChildInput = ({ onValueChange }) => {
const handleInput = (e) => {
onValueChange(e.target.value);
};
return (
<input
type="text"
placeholder="输入内容传递给父组件"
onChange={handleInput}
/>
);
};
const Parent = () => {
const [inputValue, setInputValue] = useState('');
const handleValueChange = (value) => {
setInputValue(value);
};
return (
<div style={{ padding: '16px' }}>
<p>父组件接收的值:{inputValue}</p>
{}
<ChildInput onValueChange={handleValueChange} />
</div>
);
};
核心思路:通过 父组件中转(兄弟组件 A → 父组件 → 兄弟组件 B)
const BrotherA = ({ onSendMsg }) => {
const handleClick = () => {
onSendMsg('我是兄弟组件A传递的消息');
};
return <button onClick={handleClick}>给兄弟组件B传值</button>;
};
const BrotherB = ({ msg }) => {
return <p>兄弟组件B接收的消息:{msg}</p>;
};
const Parent = () => {
const [msg, setMsg] = useState('');
const handleMsgChange = (data) => {
setMsg(data);
};
return (
<div style={{ padding: '16px' }}>
<BrotherA onSendMsg={handleMsgChange} />
<BrotherB msg={msg} />
</div>
);
};
- 全局状态管理库(推荐,如 Redux Toolkit、Zustand)
- 事件总线(不推荐,易维护性差,如
mitt 库)
import mitt from 'mitt';
const eventBus = mitt();
export default eventBus;
import eventBus from './utils/eventBus';
const ComponentA = () => {
const handleSend = () => {
eventBus.emit('customEvent', '组件A发送的消息');
};
return <button onClick={handleSend}>发送事件</button>;
};
import { useEffect } from 'react';
import eventBus from './utils/eventBus';
const ComponentB = () => {
useEffect(() => {
const handler = (data) => {
console.log('组件B接收的事件数据:', data);
};
eventBus.on('customEvent', handler);
return () => {
eventBus.off('customEvent', handler);
};
}, []);
return <p>组件B:监听事件</p>;
};
React Router v6 是 React 官方推荐的路由库,用于构建单页应用(SPA)的页面跳转与路由管理。
npm install react-router-dom --save
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{}
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Routes 替代 v5 中的 Switch,Route 必须包裹在 Routes 中,element 属性指定路由对应组件。
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
const App = () => {
return (
<div style={{ padding: '16px' }}>
{}
<nav style={{ marginBottom: '20px' }}>
<Link to="/" style={{ marginRight: '20px' }}>首页</Link>
<Link to="/about">关于我们</Link>
</nav>
{}
<Routes>
{}
<Route path="/" element={<Home />} />
{}
<Route path="/about" element={<About />} />
{}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
};
export default App;
const Home = () => {
return <h2>首页:欢迎学习 React Router v6</h2>;
};
export default Home;
const About = () => {
return <h2>关于我们:React 路由实战</h2>;
};
export default About;
const NotFound = () => {
return <h2 style={{ color: 'red' }}>404:页面不存在</h2>;
};
export default NotFound;
使用 useNavigate Hook 实现编程式跳转(替代 v5 中的 useHistory)。
import { useNavigate } from 'react-router-dom';
const Home = () => {
const navigate = useNavigate();
const goToAbout = () => {
navigate('/about');
};
return (
<div>
<h2>首页:欢迎学习 React Router v6</h2>
<button onClick={goToAbout} style={{ marginTop: '10px' }}>
跳转到关于我们
</button>
</div>
);
};
export default Home;
import { Routes, Route, Link } from 'react-router-dom';
import User from './pages/User';
<Link to="/user/1001">用户1001</Link>
<Routes>
<Route path="/user/:id" element={<User />} />
</Routes>
import { useParams } from 'react-router-dom';
const User = () => {
const { id } = useParams();
return <h2>用户页面:用户ID为 {id}</h2>;
};
export default User;
<Link to="/user/1001?name=张三&age=25">用户1001(带查询参数)</Link>
import { useParams, useSearchParams } from 'react-router-dom';
const User = () => {
const { id } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const name = searchParams.get('name');
const age = searchParams.get('age');
const updateSearchParams = () => {
setSearchParams({ name: '李四', age: 26 });
};
return (
<div>
<h2>用户页面:用户ID为 {id}</h2>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<button onClick={updateSearchParams} style={{ marginTop: '10px' }}>
修改查询参数
</button>
</div>
);
};
export default User;
核心思路:父路由组件中使用 <Outlet /> 作为子路由的占位符,配置子路由时路径继承父路由路径。
import { Routes, Route, Link } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
const App = () => {
return (
<div style={{ padding: '16px' }}>
<nav style={{ marginBottom: '20px' }}>
<Link to="/" style={{ marginRight: '20px' }}>首页</Link>
<Link to="/about" style={{ marginRight: '20px' }}>关于我们</Link>
<Link to="/dashboard">控制台</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{}
<Route path="/dashboard" element={<Dashboard />}>
{}
<Route index element={<p>请选择控制台子菜单</p>} />
{}
<Route path="user" element={<p>用户管理页面</p>} />
{}
<Route path="order" element={<p>订单管理页面</p>} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
};
export default App;
import { Link, Outlet } from 'react-router-dom';
const Dashboard = () => {
return (
<div>
<h2>控制台</h2>
{}
<nav style={{ margin: '10px 0', borderBottom: '1px solid #eee', paddingBottom: '10px' }}>
<Link to="user" style={{ marginRight: '20px' }}>用户管理</Link>
<Link to="order">订单管理</Link>
</nav>
{}
<Outlet />
</div>
);
};
export default Dashboard;
核心思路:封装私有路由组件,判断用户权限,有权限则渲染目标组件,无权限则跳转到登录页。
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoute = () => {
const isLogin = localStorage.getItem('isLogin') === 'true';
return isLogin ? <Outlet /> : <Navigate to="/login" replace />;
};
export default PrivateRoute;
import { Routes, Route } from 'react-router-dom';
import PrivateRoute from './components/PrivateRoute';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
<Routes>
{}
<Route element={<PrivateRoute />}>
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<p>请选择控制台子菜单</p>} />
<Route path="user" element={<p>用户管理页面</p>} />
<Route path="order" element={<p>订单管理页面</p>} />
</Route>
</Route>
{}
<Route path="/login" element={<Login />} />
</Routes>
import { useNavigate } from 'react-router-dom';
const Login = () => {
const navigate = useNavigate();
const handleLogin = () => {
localStorage.setItem('isLogin', 'true');
navigate('/dashboard');
};
return (
<div style={{ padding: '16px' }}>
<h2>登录页</h2>
<button onClick={handleLogin}>模拟登录</button>
</div>
);
};
export default Login;
已在 4.2.4(useContext)详细讲解,核心优势:无需额外安装依赖,简单易用。
Redux Toolkit 是 Redux 官方推出的简化工具,解决了传统 Redux 样板代码多、配置复杂的问题。
npm install @reduxjs/toolkit react-redux --save
configureStore:创建 Redux Store,简化配置(内置中间件、DevTools)
createSlice:创建切片,封装 state、reducers、actions
Provider:在 React 应用中提供 Store 上下文
useSelector:获取 Store 中的状态
useDispatch:获取 Dispatch 函数,用于分发 Action
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './slices/counterSlice';
import userSlice from './slices/userSlice';
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
user: userSlice.reducer
}
});
export default store;
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice;
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
name: '张三',
age: 20,
isLogin: false
},
reducers: {
login: (state) => {
state.isLogin = true;
},
logout: (state) => {
state.isLogin = false;
},
updateUserInfo: (state, action) => {
state.name = action.payload.name;
state.age = action.payload.age;
}
}
});
export const { login, logout, updateUserInfo } = userSlice.actions;
export default userSlice;
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{}
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './store/slices/counterSlice';
import { login, logout, updateUserInfo } from './store/slices/userSlice';
const ReduxDemo = () => {
const counterValue = useSelector((state) => state.counter.value);
const userInfo = useSelector((state) => state.user);
const dispatch = useDispatch();
return (
<div style={{ padding: '16px' }}>
<h2>Redux Toolkit 示例</h2>
<h3>计数器:{counterValue}</h3>
<button onClick={() => dispatch(increment())} style={{ marginRight: '10px' }}>
加1
</button>
<button onClick={() => dispatch(decrement())} style={{ marginRight: '10px' }}>
减1
</button>
<button onClick={() => dispatch(incrementByAmount(5))}>
加5
</button>
<div style={{ marginTop: '20px' }}>
<h3>用户信息:</h3>
<p>姓名:{userInfo.name}</p>
<p>年龄:{userInfo.age}</p>
<p>登录状态:{userInfo.isLogin ? '已登录' : '未登录'}</p>
<button onClick={() => dispatch(login())} style={{ marginRight: '10px' }}>
登录
</button>
<button onClick={() => dispatch(logout())} style={{ marginRight: '10px' }}>
登出
</button>
<button onClick={() => dispatch(updateUserInfo({ name: '李四', age: 25 }))}>
更新用户信息
</button>
</div>
</div>
);
};
export default ReduxDemo;
Zustand 是一款轻量、简洁的状态管理库,无需 Provider 包裹,API 简洁,性能优秀。
npm install zustand --save
import { create } from 'zustand';
const useStore = create((set) => ({
counter: 0,
user: {
name: '张三',
age: 20
},
increment: () => set((state) => ({ counter: state.counter + 1 })),
decrement: () => set((state) => ({ counter: state.counter - 1 })),
incrementByAmount: (amount) => set((state) => ({ counter: state.counter + amount })),
updateUser: (userInfo) => set((state) => ({ user: { ...state.user, ...userInfo } }))
}));
export default useStore;
import useStore from './store/useStore';
const ZustandDemo = () => {
const counter = useStore((state) => state.counter);
const user = useStore((state) => state.user);
const increment = useStore((state) => state.increment);
const decrement = useStore((state) => state.decrement);
const incrementByAmount = useStore((state) => state.incrementByAmount);
const updateUser = useStore((state) => state.updateUser);
return (
<div style={{ padding: '16px' }}>
<h2>Zustand 示例</h2>
<h3>计数器:{counter}</h3>
<button onClick={increment} style={{ marginRight: '10px' }}>加1</button>
<button onClick={decrement} style={{ marginRight: '10px' }}>减1</button>
<button onClick={() => incrementByAmount(5)}>加5</button>
<div style={{ marginTop: '20px' }}>
<h3>用户信息:</h3>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<button onClick={() => updateUser({ name: '李四', age: 25 })}>
更新用户信息
</button>
</div>
</div>
);
};
export default ZustandDemo;
已在 2.1.1 详细讲解,优势:简单直接,无样式冲突;劣势:不支持伪类、媒体查询,样式冗余。
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
width: 1200px;
margin: 0 auto;
}
const App = () => {
return <div className="container">全局样式示例</div>;
};
CSS 模块会自动为样式类名添加唯一哈希值,避免全局样式冲突。
.button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary {
background-color: #1890ff;
color: #fff;
}
.secondary {
background-color: #f5f5f5;
color: #333;
}
import styles from './Button.module.css';
const Button = ({ type = 'primary', children, onClick }) => {
return (
<button
className={`${styles.button} ${styles[type]}`}
onClick={onClick}
>
{children}
</button>
);
};
export default Button;
npm install sass --save-dev
npm install node-sass --save-dev
$primary-color: #1890ff;
$border-radius: 4px;
$padding: 16px;
.card {
padding: $padding;
border: 1px solid #eee;
border-radius: $border-radius;
.title {
font-size: 18px;
color: $primary-color;
margin-bottom: 10px;
}
.content {
font-size: 14px;
color: #333;
}
}
import styles from './Card.module.scss';
const Card = ({ title, children }) => {
return (
<div className={styles.card}>
<div className={styles.title}>{title}</div>
<div className={styles.content}>{children}</div>
</div>
);
};
export default Card;
npm install styled-components --save
import styled from 'styled-components';
const StyledButton = styled.button`
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: ${props => props.primary ? '#1890ff' : '#f5f5f5'};
color: ${props => props.primary ? '#fff' : '#333'};
&:hover {
opacity: 0.9;
}
`;
const Card = styled.div`
padding: 16px;
border: 1px solid #eee;
border-radius: 4px;
.title {
font-size: 18px;
color: #1890ff;
margin-bottom: 10px;
}
`;
const StyledDemo = () => {
return (
<div style={{ padding: '16px' }}>
<StyledButton primary>主要按钮</StyledButton>
<StyledButton style={{ marginLeft: '10px' }}>次要按钮</StyledButton>
<Card style={{ marginTop: '20px' }}>
<div className="title">Card 标题</div>
<div>Card 内容</div>
</Card>
</div>
);
};
export default StyledDemo;
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
@tailwind base;
@tailwind components;
@tailwind utilities;
const TailwindDemo = () => {
return (
<div className="container mx-auto p-4">
<h2 className="text-2xl font-bold text-blue-600 mb-4">Tailwind CSS 示例</h2>
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 mr-2">
主要按钮
</button>
<button className="bg-gray-200 text-gray-800 px-4 py-2 rounded hover:bg-gray-300">
次要按钮
</button>
<div className="mt-4 p-4 border border-gray-200 rounded">
<div className="text-lg font-semibold text-blue-600 mb-2">Card 标题</div>
<div className="text-gray-700">Card 内容</div>
</div>
</div>
);
};
export default TailwindDemo;
用于优化函数组件的不必要重渲染,当组件的 props 未发生变化时,复用上次渲染的结果。
import React, { memo, useState } from 'react';
const Child = memo(({ name }) => {
console.log('Child 组件重渲染了');
return <p>子组件:{name}</p>;
});
const Parent = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
return (
<div>
<p>父组件:{count}</p>
<button onClick={() => setCount(count + 1)}>加1</button>
{}
<Child name={name} />
</div>
);
};
用于优化昂贵的计算操作,仅当依赖项发生变化时,才重新计算结果。
import React, { useState, useMemo } from 'react';
const ExpensiveCalculation = ({ list }) => {
const sortedList = useMemo(() => {
console.log('执行昂贵计算');
return list.filter(item => item > 10).sort((a, b) => a - b);
}, [list]);
return (
<div>
<p>排序后的列表:{sortedList.join(', ')}</p>
</div>
);
};
const Parent = () => {
const [count, setCount] = useState(0);
const [list, setList] = useState([20, 5, 15, 30, 8]);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>加1</button>
{}
<ExpensiveCalculation list={list} />
</div>
);
};
用于优化传递给子组件的函数 props,避免因函数引用变化导致子组件不必要重渲染(需配合 React.memo 使用)。
import React, { useState, memo, useCallback } from 'react';
const Child = memo(({ onButtonClick }) => {
console.log('Child 组件重渲染了');
return <button onClick={onButtonClick}>子组件按钮</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
const handleButtonClick = useCallback(() => {
console.log('按钮被点击');
}, []);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>加1</button>
{}
<Child onButtonClick={handleButtonClick} />
</div>
);
};
列表渲染是 React 应用中高频场景,不合理的列表渲染会导致大量 DOM 操作和不必要的重渲染,需重点优化。
key 是 React 用于识别列表元素唯一性的标识,用于优化 Diff 算法的执行效率,避免不必要的 DOM 增删改操作。
const TodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ padding: '8px', borderBottom: '1px solid #eee' }}>
<span>{todo.text}</span>
<button style={{ marginLeft: '10px', color: 'red' }}>删除</button>
</li>
))}
</ul>
);
};
const BadTodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}> {}
{todo.text}
</li>
))}
</ul>
);
};
当列表数据量极大(如 1000+ 条)时,直接渲染所有 DOM 元素会导致页面卡顿、加载缓慢,虚拟列表 仅渲染当前可视区域的 DOM 元素,滚动时动态替换可视区域内容,大幅减少 DOM 数量。
推荐使用成熟库:react-window(轻量)、react-virtualized(功能全面),以下以 react-window 为例。
npm install react-window --save
import React from 'react';
import { FixedSizeList as List } from 'react-window';
const bigListData = Array.from({ length: 10000 }, (_, index) => ({
id: index + 1,
content: `长列表项 ${index + 1}`
}));
const ListItem = ({ index, style }) => {
const item = bigListData[index];
return (
<div style={{ ...style, padding: '12px', borderBottom: '1px solid #eee' }}>
{item.id} - {item.content}
</div>
);
};
const VirtualListDemo = () => {
return (
<div style={{ width: '400px', height: '400px', border: '1px solid #eee' }}>
<List
height={400}
width="100%"
itemCount={bigListData.length}
itemSize={44}
>
{ListItem}
</List>
</div>
);
};
export default VirtualListDemo;
组件级优化聚焦单个组件的渲染效率,项目级优化覆盖打包、资源、网络等全局层面,进一步提升应用加载速度和运行性能。
将应用代码拆分为多个小块(chunk),仅在用户需要时加载对应模块(如路由懒加载),减少首屏加载体积。
- 路由懒加载(React Router v6 + 动态
import())
import { Routes, Route, Link, lazy, Suspense } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Loading = () => <p>页面加载中...</p>;
const App = () => {
return (
<div>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于我们</Link>
<Link to="/dashboard">控制台</Link>
</nav>
{}
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</div>
);
};
- Vite 打包分割配置(
vite.config.js)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
utils: ['lodash', 'axios']
}
}
}
}
});
剔除代码中未被使用的死代码(如未调用的函数、未引用的变量),减少打包体积。
- 要求:使用 ES6 模块化(
import/export),避免 CommonJS 模块化(require)
- Vite/CRA 已默认开启 Tree Shaking,无需额外配置,只需避免禁用相关优化(如
sideEffects 配置)
- 配置
package.json 标记无副作用文件:
{
"sideEffects": [
"*.css",
"*.scss"
]
}
- Vite/CRA 生产环境默认开启 JS/CSS 压缩(使用
terser 压缩 JS,cssnano 压缩 CSS)
- 可通过配置进一步优化(如移除 console.log):
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}
});
- 懒加载:仅当图片进入可视区域时才加载,减少首屏请求数量
- 原生方案:
loading="lazy"(适用于普通图片)
<img src="image.jpg" alt="示例图片" loading="lazy" width="400" height="300" />
- 格式优化:使用 WebP/AVIF 等高效图片格式,减小图片体积
- 图片压缩:使用 TinyPNG 等工具压缩图片,或通过 Vite 插件自动压缩(
vite-plugin-imagemin)
- CDN 加速:将图片等静态资源部署到 CDN,提升加载速度
- 使用 Web 字体(如 Google Fonts)时,开启字体预加载(
preload)
<link
rel="preload"
href="/fonts/Roboto-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
- 本地缓存:使用
localStorage/sessionStorage 缓存非实时数据(如用户信息、静态配置)
- 内存缓存:使用
useMemo/ 自定义 Hook 缓存接口请求结果,避免重复请求
import { useState, useEffect, useRef } from 'react';
const useFetchWithCache = (url, options = {}, cacheKey = null, cacheTime = 3600000) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cacheRef = useRef({});
useEffect(() => {
if (cacheKey && cacheRef.current[cacheKey]) {
const cacheData = cacheRef.current[cacheKey];
if (Date.now() - cacheData.time < cacheTime) {
setData(cacheData.data);
setLoading(false);
return;
}
}
if (cacheKey && localStorage.getItem(cacheKey)) {
const cacheData = JSON.parse(localStorage.getItem(cacheKey));
if (Date.now() - cacheData.time < cacheTime) {
setData(cacheData.data);
setLoading(false);
cacheRef.current[cacheKey] = cacheData;
return;
}
}
const fetchData = async () => {
try {
const res = await fetch(url, options);
const result = await res.json();
setData(result);
if (cacheKey) {
const cacheData = { data: result, time: Date.now() };
cacheRef.current[cacheKey] = cacheData;
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, options, cacheKey, cacheTime]);
return { data, loading, error };
};
export default useFetchWithCache;
- 防抖(Debounce):避免高频触发请求(如搜索框输入),等待输入停止后再发起请求
- 节流(Throttle):限制请求触发频率(如滚动加载),固定时间内仅触发一次
- 使用
lodash 的 debounce/throttle 快速实现:
import { useState } from 'react';
import { debounce } from 'lodash';
const SearchInput = () => {
const [keyword, setKeyword] = useState('');
const handleSearch = debounce(async (key) => {
if (!key.trim()) return;
const res = await fetch(`https://api.example.com/search?keyword=${key}`);
const data = await res.json();
console.log('搜索结果:', data);
}, 500);
const handleInputChange = (e) => {
const value = e.target.value;
setKeyword(value);
handleSearch(value);
};
return (
<input
type="text"
value={keyword}
onChange={handleInputChange}
placeholder="输入关键词搜索"
/>
);
};
统一的代码规范能提升团队协作效率,减少代码冲突和 Bug,核心依赖 ESLint(语法检查)和 Prettier(代码格式化)。
npm install -D eslint prettier eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'prettier'
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', 'react-hooks'],
rules: {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always']
},
settings: {
react: {
version: 'detect'
}
}
};
{
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true
}
# .eslintignore & .prettierignore 通用配置
node_modules/
dist/
public/
vite.config.js
{
"scripts": {
"lint": "eslint . --ext .js,.jsx",
"lint:fix": "eslint . --ext .js,.jsx --fix",
"format": "prettier --write \"**/*.{js,jsx,css,md}\""
}
}
使用 husky(Git 钩子工具)和 lint-staged(暂存区文件校验),确保提交的代码符合规范,避免不合格代码提交到仓库。
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
npx husky install
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged"
{
"*.{js,jsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["prettier --write"],
"*.md": ["prettier --write"]
}
module.exports = {
extends: ['@commitlint/config-conventional']
};
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"
遵循 Conventional Commits 规范,格式:type(scope): subject
- type:提交类型(feat:新功能;fix:修复 Bug;docs:文档更新;style:代码格式调整;refactor:重构;test:测试;chore:构建 / 工具配置)
- scope:可选,提交影响的范围(如 home、user)
- subject:提交描述,简洁明了
- 示例:
feat(home): 添加轮播图组件、fix(user): 修复用户登录状态保存问题
已在 9.3.1 中详细讲解(代码分割、Tree Shaking、资源压缩),此处补充不同脚手架的打包细节。
npm run build
npm run preview
- 打包输出目录:
dist/
- 可通过
vite.config.js 自定义输出目录、公共路径等:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
base: '/react-demo/',
build: {
outDir: 'build',
assetsDir: 'static'
}
});
npm run build
npm install -g serve
serve -s build
- 打包输出目录:
build/
- 若需自定义配置,可通过
craco(替代 eject)修改配置,避免暴露 CRA 核心配置。
React 应用分为 静态应用(纯前端,无服务端逻辑)和 SSR 应用(服务端渲染,如 Next.js),此处重点讲解静态应用部署。
- Netlify:
- 关联 GitHub 仓库
- 构建命令:
npm run build
- 输出目录:
dist(Vite)/build(CRA)
- 自动部署:提交代码后自动构建并上线
- Vercel:与 Netlify 类似,对 React 应用有良好支持,内置 CDN 加速
- GitHub Pages:
- 修改
package.json:添加 "homepage": "https://用户名.github.io/仓库名/"
- 添加 npm 脚本:
"deploy": "gh-pages -d dist"(Vite)/ "deploy": "gh-pages -d build"(CRA)
- 执行
npm run build && npm run deploy 部署
- 打包应用:
npm run build
- 将打包后的
dist/build 目录上传到服务器(通过 FTP/SSH)
- 配置 Nginx 反向代理(以阿里云 CentOS 为例):
vi /etc/nginx/conf.d/react-demo.conf
server {
listen 80;
server_name 你的域名(如 demo.com);
root /usr/local/react-demo/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|gif|woff2|woff)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
}
nginx -s reload
- 打包应用:
npm run build
- 部署方式:
- Vercel(推荐):一键关联 GitHub 仓库,自动部署
- 云服务器:启动 Node 服务(
npm start),配置 Nginx 反向代理转发请求到 Node 服务端口
实现一个 简易商品列表应用,整合 React 核心语法、React Router v6、Zustand 状态管理、Tailwind CSS 样式、性能优化等知识点,功能包括:
- 路由跳转(首页 / 商品列表 / 商品详情)
- 商品列表展示(虚拟列表优化)
- 商品搜索(防抖优化)
- 购物车功能(全局状态管理)
- 样式美化(Tailwind CSS)
npm install react react-dom react-router-dom zustand react-window
npm install -D tailwindcss postcss autoprefixer
npm install lodash
react-product-demo/
├── src/
│ ├── assets/ # 静态资源
│ ├── components/ # 公共组件(Navbar、ProductItem、Cart等)
│ ├── pages/ # 页面组件(Home、ProductList、ProductDetail)
│ ├── store/ # Zustand 状态管理
│ ├── hooks/ # 自定义 Hook(useDebounce、useFetchWithCache)
│ ├── App.jsx # 根组件
│ ├── main.jsx # 入口文件
│ └── index.css # Tailwind CSS 入口
├── tailwind.config.js # Tailwind CSS 配置
├── vite.config.js # Vite 配置
└── package.json # 项目配置
import { create } from 'zustand';
const useProductStore = create((set, get) => ({
products: [],
cartList: [],
searchKeyword: '',
setProducts: (products) => set({ products }),
setSearchKeyword: (keyword) => set({ searchKeyword: keyword }),
addToCart: (product) => {
const cartList = get().cartList;
const existProduct = cartList.find(item => item.id === product.id);
if (existProduct) {
set({
cartList: cartList.map(item =>
item.id === product.id ? { ...item, count: item.count + 1 } : item
)
});
} else {
set({ cartList: [...cartList, { ...product, count: 1 }] });
}
},
removeFromCart: (productId) => {
set({
cartList: get().cartList.filter(item => item.id !== productId)
});
}
}));
export default useProductStore;
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { FixedSizeList as List } from 'react-window';
import { debounce } from 'lodash';
import useProductStore from '../store/useProductStore';
import useFetchWithCache from '../hooks/useFetchWithCache';
const ProductList = () => {
const [inputKeyword, setInputKeyword] = useState('');
const { products, setProducts, setSearchKeyword, addToCart } = useProductStore();
const debouncedSearch = debounce((keyword) => {
setSearchKeyword(keyword);
}, 500);
const { data, loading } = useFetchWithCache(
'https://fakestoreapi.com/products',
{},
'productList',
3600000
);
useEffect(() => {
if (data) {
setProducts(data);
}
}, [data, setProducts]);
const handleInputChange = (e) => {
const value = e.target.value;
setInputKeyword(value);
debouncedSearch(value);
};
const filteredProducts = products.filter(product =>
product.title.toLowerCase().includes(inputKeyword.toLowerCase())
);
const ProductItem = ({ index, style }) => {
const product = filteredProducts[index];
return (
<div style={style} className="p-4 border-b border-gray-200 flex items-center gap-4">
<Link to={`/product/${product.id}`} className="w-20 h-20 flex-shrink-0">
<img
src={product.image}
alt={product.title}
className="w-full h-full object-contain"
loading="lazy"
/>
</Link>
<div className="flex-1">
<Link to={`/product/${product.id}`} className="font-medium text-gray-800 hover:text-blue-600 line-clamp-1">
{product.title}
</Link>
<p className="text-red-600 font-bold mt-1">${product.price.toFixed(2)}</p>
<button
onClick={() => addToCart(product)}
className="mt-2 px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700"
>
加入购物车
</button>
</div>
</div>
);
};
if (loading) {
return <div className="flex justify-center items-center h-64">加载中...</div>;
}
return (
<div className="container mx-auto p-4">
<div className="mb-6">
<input
type="text"
placeholder="搜索商品..."
value={inputKeyword}
onChange={handleInputChange}
className="w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="h-[600px] border border-gray-200 rounded">
<List
height={600}
width="100%"
itemCount={filteredProducts.length}
itemSize={120}
>
{ProductItem}
</List>
</div>
</div>
);
};
export default ProductList;
import { Routes, Route, Link, Outlet } from 'react-router-dom';
import Home from './pages/Home';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
import Cart from './components/Cart';
import useProductStore from './store/useProductStore';
const Navbar = () => {
const cartList = useProductStore(state => state.cartList);
const cartCount = cartList.reduce((total, item) => total + item.count, 0);
return (
<nav className="bg-gray-800 text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<div className="flex gap-6">
<Link to="/" className="hover:text-blue-300">首页</Link>
<Link to="/products" className="hover:text-blue-300">商品列表</Link>
</div>
<div className="relative">
<Link to="/cart" className="hover:text-blue-300">
购物车
{cartCount > 0 && (
<span className="absolute -top-2 -right-2 bg-red-600 text-white text-xs rounded-full w-5 h-5 flex justify-center items-center">
{cartCount}
</span>
)}
</Link>
</div>
</div>
</nav>
);
};
const Layout = () => {
return (
<div>
<Navbar />
<Outlet /> {}
</div>
);
};
const App = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
</Route>
</Routes>
);
};
export default App;
- 启动开发服务:
npm run dev
- 生产环境打包:
npm run build
- 部署:参考 10.3 节的静态部署方案,可部署到 Netlify/Vercel/ 阿里云服务器
- 原因 1:直接修改 State/Props(未使用状态更新函数)
- 解决方案:使用
setState 函数,返回新状态(对象 / 数组需保持不可变性)
- 原因 2:依赖数组缺失(
useEffect/useMemo/useCallback)
- 原因 3:
React.memo 浅比较未生效(Props 为引用类型)
- 解决方案:使用
useCallback 缓存函数 Props,useMemo 缓存对象 / 数组 Props
- 问题:调用
setState 后立即获取 State,得到的是旧值
- 解决方案 1:使用函数式更新(依赖上一次状态)
setCount(prevCount => prevCount + 1);
- 解决方案 2:使用
useEffect 监听 State 变化
useEffect(() => {
console.log('最新 count:', count);
}, [count]);
- 问题:单页应用刷新路由后出现 404
- 解决方案 1:Nginx 配置
try_files $uri $uri/ /index.html;(云服务器部署)
- 解决方案 2:使用
HashRouter 替代 BrowserRouter(无需服务器配置,URL 带 #)
- 问题:多层级组件传递 Props,代码冗余且维护困难
- 解决方案 1:使用
Context API(中小型项目)
- 解决方案 2:使用 Zustand/Redux Toolkit(中大型项目)
- 问题:渲染大量列表项导致 DOM 过多,页面卡顿
- 解决方案:使用虚拟列表(
react-window/react-virtualized),仅渲染可视区域元素