注:纯手打,如有错误欢迎评论区交流!
转载请注明出处:https://blog.csdn.net/testleaf/article/details/148403372
编写此文是为了更好地学习前端知识,如果损害了有关人的利益,请联系删除!
本文章将不定时更新,敬请期待!!!
欢迎点赞、收藏、转发、关注,多谢!!!
一、React开发环境搭建
1、必备工具安装
Node.js:
作用:提供npm/yarn包管理工具和运行JavaScript的环境
下载:Node.js官网
验证安装:
node -v # 检查Node版本
npm -v # 检查npm版本
代码编辑器:
推荐:VS Code(安装插件:ES7+ React/Redux、Prettier、ESLint)
2、创建React项目
方式1:使用Create React App(CRA,官方推荐)
npx create-react-app my-app # 创建项目
cd my-app # 进入项目目录
npm start # 启动开发服务器
特点:
零配置,内置Babel、Webpack、ESLint等工具
适合新手和快速原型开发
方式2:使用Vite(更快的现代构建工具)
npm create vite@latest my-react-app --template react
cd my-react-app
npm install
npm run dev
优势:启动和热更新速度极快,适合大型项目。
方式3:手动配置Webpack(进阶)
适合需要深度定制化的项目,需自行配置Babel、Loader等(不推荐新手)。
3、项目目录结构(CRA生成)
my-app/
├── node_modules/ # 依赖库
├── public/ # 静态资源(HTML模板、图片等)
│ └── index.html # 主HTML文件
├── src/ # 源码目录
│ ├── App.js # 根组件
│ ├── index.js # 入口文件
│ └── styles/ # CSS文件
├── package.json # 项目配置和依赖
└── README.md
4、关键依赖说明
- react & react-dom:React核心库和DOM渲染库
- react-scripts(CRA):封装了Webpack/Babel配置
- 其他常用库:
- 路由:react-router-dom
- 状态管理:redux / zustand
- UI组件库:Material-UI / Ant Design
5、开发与构建命令
命令 | 作用 |
---|---|
npm start | 启动开发服务器(默认3000端口) |
npm run build | 生成生产环境优化代码(在build/目录) |
npm test | 运行Jest测试 |
npm run eject | 暴露CRA配置(不可逆操作) |
6、常见问题解决
端口冲突:
修改启动端口
PORT=3001 npm start
依赖安装慢:
查看当前 npm 镜像源
npm config get registry
检查所有 npm 配置(包括镜像源、全局安装路径等)
npm config list
切换npm镜像源
npm config set registry https://registry.npmmirror.com
恢复为官方源
npm config set registry https://registry.npmjs.org
临时使用镜像源(单次安装)
npm install 包名 --registry=https://registry.npmmirror.com
常见镜像源地址
镜像源名称 | 地址 |
---|---|
npm官方源 | https://registry.npmjs.org/ |
淘宝镜像 | https://registry.npmmirror.com/ |
腾讯云镜像 | https://mirrors.cloud.tencent.com/npm/ |
React版本升级:
npm install react@latest react-dom@latest
7、创建React项目【TypeScript版】
方法 1:使用 create-react-app(CRA,官方推荐)
创建项目:
npx create-react-app my-app --template typescript
或(旧版 CRA):
npx create-react-app my-app --typescript
项目结构:
my-app/
├── src/
│ ├── App.tsx # TypeScript 组件
│ ├── index.tsx # TypeScript 入口文件
│ └── react-app-env.d.ts # React 类型声明
├── tsconfig.json # TypeScript 配置
└── package.json
启动项目:
cd my-app
npm start
访问 http://localhost:3000 查看运行效果。
方法 2:使用 Vite(推荐,速度更快)
创建项目:
npm create vite@latest my-react-ts-app --template react-ts
或使用 yarn:
yarn create vite my-react-ts-app --template react-ts
项目结构:
my-react-ts-app/
├── src/
│ ├── App.tsx
│ ├── main.tsx # 入口文件
│ └── vite-env.d.ts # Vite 类型声明
├── tsconfig.json
├── vite.config.ts # Vite 配置
└── package.json
安装依赖并启动:
cd my-react-ts-app
npm install
npm run dev
访问 http://localhost:5173(Vite 默认端口)。
方法 3:手动配置(Webpack + TypeScript)
适用于需要深度定制的项目(不推荐新手)。
初始化项目:
mkdir my-react-ts-project
cd my-react-ts-project
npm init -y
安装依赖:
npm install react react-dom
npm install --save-dev typescript @types/react @types/react-dom
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
配置 tsconfig.json:
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
配置 webpack.config.js:
const path = require("path");
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: "babel-loader",
},
],
},
devServer: {
static: path.join(__dirname, "dist"),
port: 3000,
},
};
创建 src/index.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
const App = () => <h1>Hello, React + TypeScript!</h1>;
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
启动开发服务器:
npx webpack serve --mode development
二、JSX基础
JSX(JavaScript XML)是 React 的核心语法,它允许在 JavaScript 中编写类似 HTML 的代码,用于定义 React 组件的结构。
1、JSX 是什么?
JSX = JavaScript + XML,是一种语法扩展。
它会被 Babel 转译为 React.createElement() 调用,最终生成 JavaScript 对象(虚拟 DOM)。
示例:
const element = <h1>Hello, JSX!</h1>;
编译后:
const element = React.createElement("h1", null, "Hello, JSX!");
2、JSX 基本规则
(1) 必须有一个根元素
// ✅ 正确
const element = (
<div>
<h1>标题</h1>
<p>内容</p>
</div>
);
// ❌ 错误(多个根元素)
const element = (
<h1>标题</h1>
<p>内容</p>
);
解决方案:使用 (<></> 语法糖)包裹:
const element = (
<>
<h1>标题</h1>
<p>内容</p>
</>
);
(2) 所有标签必须闭合
// ✅ 正确
<img src="logo.png" alt="Logo" />
<input type="text" />
// ❌ 错误
<img src="logo.png" alt="Logo">
<input type="text">
(3) 使用 className 代替 class
// ✅ 正确
<div className="container">内容</div>
// ❌ 错误
<div class="container">内容</div>
(4) 使用 htmlFor 代替 for(表单标签)
// ✅ 正确
<label htmlFor="username">用户名</label>
<input id="username" type="text" />
// ❌ 错误
<label for="username">用户名</label>
3、JSX 嵌入 JavaScript 表达式
用 {} 包裹 JavaScript 表达式:
const name = "Alice";
const age = 25;
const element = (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>明年年龄:{age + 1}</p>
<p>是否成年:{age >= 18 ? "是" : "否"}</p>
</div>
);
可以嵌入的内容:
- 变量、数字、字符串
- 三元运算符
condition ? true : false
- 函数调用(如
user.getName()
) - 数组(如
[1, 2, 3].map(...)
)
不能嵌入的内容:
- 普通对象(如
{a: 1}
,除非用于样式) if/for
语句(改用三元运算符或map
)
4、JSX 中的样式
(1) 行内样式(style 属性)
使用 对象 传递样式,属性名用 驼峰命名法:
const style = {
color: "red",
fontSize: "20px", // 注意是 fontSize,不是 font-size
};
const element = <p style={style}>红色文字</p>;
或直接写:
<p style={{ color: "red", fontSize: "20px" }}>红色文字</p>
(2) CSS 类名(className)
// App.css
.container {
background: #f0f0f0;
}
// App.jsx
import "./App.css";
const element = <div className="container">内容</div>;
5、JSX 中的条件渲染
(1) 三元运算符
const isLoggedIn = true;
const element = (
<div>
{isLoggedIn ? <button>退出</button> : <button>登录</button>}
</div>
);
(2) &&
短路运算
const hasMessages = true;
const element = (
<div>
{hasMessages && <p>您有未读消息</p>}
</div>
);
(3) if-else
语句(在函数/组件外使用)
function renderContent(isAdmin) {
if (isAdmin) {
return <AdminPanel />;
} else {
return <UserPanel />;
}
}
const element = <div>{renderContent(true)}</div>;
6、JSX 中的列表渲染(map)
使用 map 遍历数组生成元素列表,必须加 key 属性:
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
const userList = (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
key 的作用:帮助 React 识别哪些元素发生了变化(通常用唯一 ID 或索引)。
7、JSX 中的事件处理
事件名用 驼峰命名法(如 onClick 而不是 onclick)。
传递函数,而不是字符串。
基础实现:
const handleClick = () => {
alert("按钮被点击了!");
};
const element = <button onClick={handleClick}>点击我</button>;
使用事件参数:
const handleClick = (e) => {
alert("按钮被点击了!", e);
};
const element = <button onClick={handleClick}>点击我</button>;
传递自定义参数:
const deleteUser = (userId) => {
console.log("删除用户", userId);
};
const userList = users.map((user) => (
<button onClick={() => deleteUser(user.id)}>删除 {user.name}</button>
));
同时传递事件对象和自定义参数:
const deleteUser = (userId, e) => {
console.log("删除用户", userId, e);
};
const userList = users.map((user) => (
<button onClick={(e) => deleteUser(user.id, e)}>删除 {user.name}</button>
));
8、JSX 中的注释
{/* 注释 */}
三、React组件基础使用
React 的核心思想是 组件化开发,组件是构建 UI 的独立、可复用的代码单元。
1、组件的两种定义方式
(1) 函数组件(推荐)
使用 JavaScript 函数定义,返回 JSX。
适用于简单、无状态逻辑的组件。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用组件
<Welcome name="Alice" />
(2) 类组件(旧版写法)
使用 ES6 class 继承 React.Component。
适用于需要生命周期或状态管理的组件(现代 React 推荐用 Hooks 替代)。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 使用组件
<Welcome name="Bob" />
2、组件的 props(属性)
props 是父组件传递给子组件的数据,只读不可修改。
(1) 传递 props
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>年龄:{props.age}</p>
</div>
);
}
// 使用组件
<UserCard name="Alice" age={25} />
(2) 解构 props(推荐)
function UserCard({ name, age }) {
return (
<div>
<h2>{name}</h2>
<p>年龄:{age}</p>
</div>
);
}
(3) 默认 props
function UserCard({ name, age = 18 }) {
// 如果未传递 age,默认为 18
return <p>{name} 年龄:{age}</p>;
}
// 或者使用 defaultProps(类组件)
UserCard.defaultProps = {
age: 18,
};
(4) 传递子元素(children)
function Card({ children }) {
return <div className="card">{children}</div>;
}
// 使用
<Card>
<h3>标题</h3>
<p>内容</p>
</Card>
3、组件的 state(状态)
state 是组件内部的可变数据,修改状态会触发重新渲染。
(1) 函数组件:useState Hook
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>
);
}
修改对象状态:
import { useState } from "react";
function Park() {
const [person, setPerson] = useState({
name:'testleaf'
});
const changeName = () => {
setPerson({
...person,
name: 'testleaf.cn'
})
}
return (
<div>
<button onClick={changeName}>{person.name}</button>
</div>
);
}
(2) 类组件:this.state
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>当前计数:{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
+1
</button>
</div>
);
}
}
4、组件通信
(1) 父传子:通过 props
function Parent() {
const message = "Hello from Parent";
return <Child msg={message} />;
}
function Child({ msg }) {
return <p>{msg}</p>;
}
(2) 子传父:通过回调函数
function Parent() {
const handleChildClick = (data) => {
console.log("子组件传递的数据:", data);
};
return <Child onClick={handleChildClick} />;
}
function Child({ onClick }) {
return <button onClick={() => onClick("Child Data")}>传递数据</button>;
}
(3) 兄弟组件通信
通过 共同的父组件 传递状态(状态提升)。
使用 Context API 或 状态管理库(如 Redux、Zustand)。
状态提升【两个兄弟组件需要共享同一个数据源,将共享状态提升到父组件,通过 props 传递数据和回调函数】:
import { useState } from 'react';
// 父组件
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<ChildA count={count} />
<ChildB onIncrement={() => setCount(count + 1)} />
</div>
);
}
// 子组件 A(显示计数)
function ChildA({ count }) {
return <p>当前计数:{count}</p>;
}
// 子组件 B(控制计数)
function ChildB({ onIncrement }) {
return <button onClick={onIncrement}>+1</button>;
}
使用 Context API【多个组件需要共享状态,避免 props 层层传递,使用 React.createContext 创建全局状态,并通过 Provider 和 useContext 访问】:
import { createContext, useContext, useState } from 'react';
// 1. 创建 Context
const ThemeContext = createContext();
// 父组件(Provider)
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Content />
</ThemeContext.Provider>
);
}
// 子组件 A(显示当前主题)
function Header() {
const { theme } = useContext(ThemeContext);
return <header>当前主题:{theme}</header>;
}
// 子组件 B(切换主题)
function Content() {
const { setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}>
切换主题
</button>
);
}
5、组件的生命周期(类组件)
生命周期方法 | 触发时机 |
---|---|
componentDidMount | 组件挂载后(首次渲染完成) |
componentDidUpdate | 组件更新后(state/props 变化) |
componentWillUnmount | 组件卸载前(清理副作用) |
函数组件替代方案:使用 useEffect Hook。
6、组件复用
(1) 组合模式
通过 props.children 或自定义 props 实现灵活组合:
function Layout({ header, content }) {
return (
<div>
<div className="header">{header}</div>
<div className="content">{content}</div>
</div>
);
}
// 使用
<Layout
header={<h1>标题</h1>}
content={<p>正文内容</p>}
/>
(2) 高阶组件(HOC)
function withLogger(WrappedComponent) {
return function (props) {
console.log("组件渲染:", props);
return <WrappedComponent {...props} />;
};
}
const EnhancedComponent = withLogger(MyComponent);
(3) 自定义 Hook
function useCounter(initialValue) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
return { count, increment };
}
// 使用
function CounterA() {
const { count, increment } = useCounter(0);
return <button onClick={increment}>计数:{count}</button>;
}
7、表单控制
受控组件:
受控组件是指表单元素的值由 React 的 state 控制,并通过 onChange 事件更新。
import { useState } from 'react';
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交的数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="用户名"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="密码"
/>
<button type="submit">登录</button>
</form>
);
}
非受控组件:
非受控组件是指表单数据由 DOM 自身管理,通过 ref 获取值。
import { useRef } from 'react';
function LoginForm() {
const usernameRef = useRef(null);
const passwordRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交的数据:', {
username: usernameRef.current.value,
password: passwordRef.current.value
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={usernameRef}
placeholder="用户名"
/>
<input
type="password"
ref={passwordRef}
placeholder="密码"
/>
<button type="submit">登录</button>
</form>
);
}
四、useEffect
useEffect 是 React Hooks 中最重要的 API 之一,用于处理 副作用(Side Effects),如数据获取、订阅、手动 DOM 操作等。
1、useEffect 基本语法
import { useEffect } from 'react';
useEffect(() => {
// 副作用逻辑(组件渲染后执行)
return () => {
// 清理逻辑(组件卸载或依赖项变化前执行)
};
}, [dependencies]); // 依赖项数组
参数说明:
参数 | 说明 |
---|---|
effect | 包含副作用的函数,在组件渲染后执行 |
dependencies | 依赖项数组,决定 effect 何时重新执行 |
2、useEffect 的 3 种使用方式
(1) 无依赖项(每次渲染后执行)
useEffect(() => {
console.log('每次组件更新后都会执行');
});
适用场景: 极少使用,可能导致性能问题。
(2) 空依赖项(仅首次渲染后执行)
useEffect(() => {
console.log('仅在组件挂载时执行一次');
}, []); // 空数组
适用场景:
- 数据初始化请求(如 fetch)
- 事件监听(如 window.addEventListener)
- 定时器(如 setInterval)
示例:组件挂载时获取数据
useEffect(() => {
const fetchData = async () => {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
setData(data);
};
fetchData();
}, []);
(3) 有依赖项(依赖变化时执行)
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count 变化时执行:', count);
}, [count]); // 依赖 count
适用场景:
- 监听特定状态变化(如搜索关键词变化时重新查询)
- 计算衍生数据
示例:监听输入框变化
const [keyword, setKeyword] = useState('');
useEffect(() => {
if (keyword) {
search(keyword); // 关键词变化时触发搜索
}
}, [keyword]);
3、useEffect 的清理机制
如果 effect 返回一个函数,React 会在 组件卸载 或 依赖项变化导致 effect 重新执行前 调用它进行清理。
示例 1:清除定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('每秒执行');
}, 1000);
return () => {
clearInterval(timer); // 组件卸载时清除定时器
};
}, []);
示例 2:取消事件监听
useEffect(() => {
const handleClick = () => console.log('点击了窗口');
window.addEventListener('click', handleClick);
return () => {
window.removeEventListener('click', handleClick); // 清理事件
};
}, []);
4、useEffect 常见使用场景
(1) 数据获取(Fetch Data)
useEffect(() => {
let ignore = false; // 防止竞态条件
const fetchData = async () => {
const res = await fetch(`https://api.example.com/data/${id}`);
const data = await res.json();
if (!ignore) setData(data); // 确保组件未卸载
};
fetchData();
return () => {
ignore = true; // 组件卸载时标记忽略结果
};
}, [id]); // id 变化时重新获取
(2) 订阅外部数据源
useEffect(() => {
const subscription = dataSource.subscribe((data) => {
setData(data);
});
return () => {
subscription.unsubscribe(); // 清理订阅
};
}, []);
(3) 手动操作 DOM
useEffect(() => {
const button = document.getElementById('my-button');
button.addEventListener('click', handleClick);
return () => {
button.removeEventListener('click', handleClick);
};
}, []);
5、useEffect 的注意事项
(1) 避免无限循环
错误示例:
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 每次渲染后修改 count,导致无限循环
});
修复方式:
确保依赖项正确
使用函数式更新(避免直接依赖 count)
useEffect(() => {
setCount(prev => prev + 1); // 不依赖 count
}, []); // 仅在挂载时执行
(2) 依赖项遗漏导致的问题
如果 effect 使用了某个变量但没有声明为依赖项,可能导致逻辑错误:
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // 依赖 count 但未声明
}, []); // 控制台警告:缺少依赖项
修复方式:
- 添加所有依赖项(ESLint 会提示)
- 如果某些依赖项变化时不想重新执行 effect,可以拆分逻辑或使用 useRef 存储可变值。
(3) useEffect 与 useLayoutEffect 的区别
Hook | 执行时机 | 适用场景 |
---|---|---|
useEffect | 浏览器绘制后异步执行 | 大多数副作用(数据获取、订阅) |
useLayoutEffect | DOM 更新后同步执行 | 需要阻塞浏览器渲染的场景(如测量 DOM 尺寸) |
五、React路由
React Router 是 React 生态中最流行的路由解决方案,用于构建单页应用(SPA)的导航系统。
1、安装与基础配置
(1) 安装
npm install react-router-dom
(2) 基本路由结构
// main.jsx / App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</BrowserRouter>
);
}
2、核心组件与 API
(1) 路由类型
组件 | 作用 | 使用场景 |
---|---|---|
<BrowserRouter> | 使用 HTML5 History API | 生产环境(需要服务器支持) |
<HashRouter> | 使用 URL hash (#) | 静态文件部署(无服务器配置) |
(2) 路由定义 |
<Routes>
{/* 精确匹配 */}
<Route path="/" element={<Home />} />
{/* 嵌套路由 */}
<Route path="/users" element={<UsersLayout />}>
<Route index element={<UserList />} /> // 默认子路由
<Route path=":id" element={<UserDetail />} /> // 动态参数
<Route path="create" element={<CreateUser />} /> // 静态子路由
</Route>
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
(3) 导航组件
import { Link, NavLink, useNavigate } from 'react-router-dom';
// 1. Link (普通导航)
<Link to="/about">关于我们</Link>
// 2. NavLink (激活状态样式)
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active' : ''}
>关于我们</NavLink>
// 3. 编程式导航
const navigate = useNavigate();
<button onClick={() => navigate('/profile')}>跳转</button>
3、动态路由与参数获取
(1) 路径参数
// 路由定义
<Route path="/users/:id" element={<UserDetail />} />
// 组件中获取参数
import { useParams } from 'react-router-dom';
function UserDetail() {
const { id } = useParams(); // 获取 :id
return <div>User ID: {id}</div>;
}
(2) 查询参数
// 跳转时传递
navigate(`/search?query=${keyword}`);
// 组件中获取
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams] = useSearchParams();
const query = searchParams.get('query'); // 获取 ?query=xxx
}
(3) 状态参数
// 跳转时传递
navigate('/profile', { state: { fromHome: true } });
// 组件中获取
import { useLocation } from 'react-router-dom';
function ProfilePage() {
const { state } = useLocation(); // 获取 { fromHome: true }
}
4、嵌套路由与布局模式
(1) 共享布局
// 定义带布局的路由
<Route path="/admin" element={<AdminLayout />}>
<Route index element={<Dashboard />} />
<Route path="products" element={<ProductList />} />
</Route>
// AdminLayout.jsx
import { Outlet } from 'react-router-dom';
function AdminLayout() {
return (
<div>
<h1>Admin Header</h1>
<Outlet /> {/* 子路由将渲染在这里 */}
</div>
);
}
(2) 多布局入口
function App() {
return (
<Routes>
<Route element={<PublicLayout />}>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
</Route>
<Route element={<PrivateLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
</Routes>
);
}
对象配置方式:
const router = createBrowserRouter([
{
element: <PublicLayout />,
children: [
{
path: '/',
element: <Home />
},
{
path: '/login',
element: <Login />
}
]
},
{
element: <PrivateLayout />,
children: [
{
path: '/dashboard',
element: <Dashboard />
}
]
}
]);
export default router;
5、路由守卫(权限控制)
(1) 高阶组件模式
function PrivateRoute({ children }) {
const { user } = useAuth(); // 自定义权限钩子
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
(2) 包装器组件模式
<Route
path="/admin"
element={
<RequireAuth>
<AdminLayout />
</RequireAuth>
}
>
{/* 子路由自动继承权限 */}
<Route path="settings" element={<Settings />} />
</Route>
6、代码分割与懒加载
import { lazy, Suspense } from 'react';
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
7、404 路由配置
使用JSX配置方式:
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 404 路由 - 放在最后 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
使用对象配置方式:
const router = createBrowserRouter([
{
path: '/',
element: <Home />
},
{
path: '/about',
element: <About />
},
{
path: '*',
element: <NotFound />
}
]);
六、Redux
Redux 是 JavaScript 应用的可预测状态管理库,常用于 React 等框架中管理全局状态。
1、Redux 三大原则
- 单一数据源:整个应用状态存储在唯一的 store 中。
- 状态只读:只能通过 dispatch(action) 修改状态。
- 纯函数修改:使用 reducer 纯函数处理状态变更。
2、Redux 核心概念
(1) Action
描述发生了什么的对象,必须包含 type 字段:
// action 类型常量(避免拼写错误)
const ADD_TODO = 'ADD_TODO';
// action 创建函数(Action Creator)
const addTodo = (text) => ({
type: ADD_TODO,
payload: { text }
});
(2) Reducer
纯函数,接收当前 state 和 action,返回新状态:
const initialState = { todos: [] };
function todoReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload.text]
};
default:
return state; // 必须返回默认状态
}
}
(3) Store
通过 createStore 创建,提供核心方法:
import { createStore } from 'redux';
const store = createStore(todoReducer);
// 获取当前状态
store.getState();
// 派发 action
store.dispatch(addTodo('Learn Redux'));
// 订阅状态变化
const unsubscribe = store.subscribe(() => {
console.log('State updated:', store.getState());
});
// 取消订阅
unsubscribe();
3、React-Redux 集成
(1) 安装依赖
npm install redux react-redux
(2) 创建 Redux Store
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
(3) 使用 Provider 注入 Store
// index.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
(4) 组件中访问状态(useSelector)
import { useSelector } from 'react-redux';
function TodoList() {
const todos = useSelector(state => state.todos);
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
);
}
(5) 组件中派发 Action(useDispatch)
import { useDispatch } from 'react-redux';
import { addTodo } from './actions';
function AddTodo() {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleSubmit = () => {
dispatch(addTodo(text));
setText('');
};
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button onClick={handleSubmit}>Add</button>
</div>
);
}
4、异步 Action 处理(Redux-Thunk)
(1) 安装中间件
npm install redux-thunk
(2) 配置 Store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
(3) 编写异步 Action
// actions.js
const fetchTodos = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_TODOS_REQUEST' });
try {
const res = await fetch('/api/todos');
const todos = await res.json();
dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
} catch (error) {
dispatch({ type: 'FETCH_TODOS_FAILURE', error });
}
};
};
(4) 组件中调用
function TodoApp() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTodos());
}, []);
// ...
}
5、Redux 最佳实践
(1) 项目结构
src/
├── store/ # Redux 核心目录
│ ├── index.js # Store 创建和配置
│ ├── actions/ # Action 定义
│ │ └── todoActions.js
│ ├── reducers/ # Reducer 定义
│ │ ├── todoReducer.js
│ │ └── index.js # Root Reducer
│ └── types.js # Action 类型常量
├── components/ # 展示组件
└── containers/ # 容器组件(连接 Redux)
(2) 代码实现
定义 Action 类型 (store/types.js)
export const ADD_TODO = 'ADD_TODO';
export const FETCH_TODOS = 'FETCH_TODOS';
创建 Action (store/actions/todoActions.js)
import { ADD_TODO, FETCH_TODOS } from '../types';
// 同步 Action
export const addTodo = (text) => ({
type: ADD_TODO,
payload: text
});
// 异步 Action (需配置 Redux-Thunk)
export const fetchTodos = () => {
return async (dispatch) => {
const res = await fetch('/api/todos');
const todos = await res.json();
dispatch({ type: FETCH_TODOS, payload: todos });
};
};
编写 Reducer (store/reducers/todoReducer.js)
import { ADD_TODO, FETCH_TODOS } from '../types';
const initialState = {
todos: []
};
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload]
};
case FETCH_TODOS:
return {
...state,
todos: action.payload
};
default:
return state;
}
}
合并 Reducers (store/reducers/index.js)
import { combineReducers } from 'redux';
import todoReducer from './todoReducer';
export default combineReducers({
todos: todoReducer
});
创建 Store (store/index.js)
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
(3) React 组件集成
注入 Store (src/index.js)
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
组件中访问状态和派发 Action
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, fetchTodos } from './store/actions/todoActions';
function TodoApp() {
const todos = useSelector(state => state.todos.todos);
const dispatch = useDispatch();
const handleAddTodo = () => {
dispatch(addTodo('New Task'));
};
useEffect(() => {
dispatch(fetchTodos());
}, []);
return (
<div>
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
}
(4) 使用 Redux Toolkit(官方推荐)
简化 Redux 代码的官方工具库
npm install @reduxjs/toolkit
重构 Store (store/index.js)
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './reducers/todoReducer';
export default configureStore({
reducer: {
todos: todoReducer
}
});
使用 createSlice 简化 Reducer (store/reducers/todoReducer.js)
import { createSlice } from '@reduxjs/toolkit';
const todoSlice = createSlice({
name: 'todos',
initialState: { todos: [] },
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload); // 直接修改(Immer 支持)
}
}
});
export const { addTodo } = todoSlice.actions;
export default todoSlice.reducer;
七、Zustand
Zustand 是一个轻量级的 React 状态管理库,相比 Redux 更简洁,比 Context API 更高效。
1、安装与基础使用
(1) 安装
npm install zustand
(2) 创建 Store
// store/counterStore.js
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
export default useCounterStore;
(3) 在组件中使用
import useCounterStore from './store/counterStore';
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
2、核心特性
(1) 状态更新
直接更新:
set({ count: 10 }); // 直接替换
基于旧状态更新:
set((state) => ({ count: state.count + 1 })); // 函数式更新
(2) 异步操作
const useUserStore = create((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true });
const res = await fetch(`/api/users/${id}`);
const user = await res.json();
set({ user, loading: false });
},
}));
(3) 计算属性
const useCartStore = create((set, get) => ({
items: [],
total: () => get().items.reduce((sum, item) => sum + item.price, 0),
}));
3、性能优化
(1) 选择性订阅(避免不必要的渲染)
function CartTotal() {
// 只订阅 total 计算属性,不依赖整个 store
const total = useCartStore((state) => state.total());
return <div>Total: ${total}</div>;
}
(2) 浅比较
const { name, age } = useUserStore(
(state) => ({ name: state.name, age: state.age }),
shallow // 避免在 name/age 未变化时触发渲染
);
(3) 使用 Immer 简化嵌套更新
npm install immer
import { produce } from 'immer';
const useNestedStore = create((set) => ({
user: { name: 'Alice', age: 25 },
updateName: (name) =>
set(produce((state) => { state.user.name = name })),
}));
4、高级用法
(1) 持久化
npm install zustand/middleware
import { persist } from 'zustand/middleware';
const useAuthStore = create(
persist(
(set) => ({
token: null,
login: (token) => set({ token }),
logout: () => set({ token: null }),
}),
{ name: 'auth-storage' } // 存储到 localStorage
)
);
(2) 中间件
const logMiddleware = (config) => (set, get, api) =>
config((args) => {
console.log('State changed:', args);
set(args);
}, get, api);
const useStore = create(logMiddleware((set) => ({
// ...store logic
})));
(3) 组合多个 Store
const useCombinedStore = create((...a) => ({
...useCounterStore(...a),
...useUserStore(...a),
}));
(4) 完整示例
// store/userStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useUserStore = create(
persist(
(set, get) => ({
user: null,
isLoggedIn: () => !!get().user,
login: async (email, password) => {
const res = await fetch('/api/login', { /* ... */ });
set({ user: await res.json() });
},
logout: () => set({ user: null }),
}),
{ name: 'user-storage' }
)
);
export default useUserStore;