深入解析:React知识点梳理
注:纯手打,如有错误欢迎评论区交流!
转载请注明出处: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;