目录
[第三章 用个案例回顾](#第三章 用个案例回顾)
[第四章 跨域处理](#第四章 跨域处理)
[4.1 react解决跨域的两种方式](#4.1 react解决跨域的两种方式)
[4.1.1 方案二:(18/19版本)](#4.1.1 方案二:(18/19版本))
[4.1.2 方案二:(旧版本)](#4.1.2 方案二:(旧版本))
[第五章 React的状态管理](#第五章 React的状态管理)
[5.1 Redux Toolkit](#5.1 Redux Toolkit)
[5.1.1 准备](#5.1.1 准备)
[5.1.2 基本使用](#5.1.2 基本使用)
[5.2 Zustand](#5.2 Zustand)
[5.2.1 准备](#5.2.1 准备)
[5.2.2 基本使用](#5.2.2 基本使用)
[第六章 React 路由](#第六章 React 路由)
[6.1 Router 6 版本核心](#6.1 Router 6 版本核心)
[6.2 Router 5 版本核心](#6.2 Router 5 版本核心)
[6.3 区别](#6.3 区别)
基础回顾:
第三章 用个案例回顾
- 结合小编React基础篇(一)所了解的知识,接下来通过一个案例,更深刻的了解知识点(注:小编目前使用语法的基本上是官方推荐函数式+hook写法)
- App.jsx
javascript
import { Link, Route, Routes } from "react-router-dom";
import { useState } from "react";
import "./App.css";
import Header from "./components/Header/index";
import List from "./components/List/index";
import Footer from "./components/Footer/index";
import "./App.css";
function App() {
const [todos, setTodos] = useState([
{
id: 1,
title: "学习 React",
completed: false,
},
{
id: 2,
title: "学习 CSS",
completed: true,
},
{
id: 3,
title: "学习 HTML",
completed: false,
},
]);
// 添加任务
const handleAdd = (todoObj) => {
setTodos([...todos, todoObj]);
};
// 切换任务状态
const handleToggleChange = (todoObj) => {
setTodos((prev) =>
prev.map((todo) => (todo.id === todoObj.id ? todoObj : todo)),
);
};
// 删除任务
const handleDelete = (id) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
// 全选/取消全选
const handleToggleChangeAll = (checked) => {
setTodos((prev) =>
prev.map((todo) => ({
...todo,
completed: checked,
})),
);
};
return (
<>
<div>
<div className="todo-container">
<div className="todo-wrap">
<Header onAdd={handleAdd} />
<List
todos={todos}
onToggleChange={handleToggleChange}
onDelete={handleDelete}
/>
<Footer todos={todos} onToggleChangeAll={handleToggleChangeAll} />
</div>
</div>
</div>
</>
);
}
export default App;
- Header组件
javascript
import React, { useRef } from "react";
import { nanoid } from "nanoid";
import "./index.css";
export default function Header(props) {
const { onAdd } = props;
const inputRef = useRef(null);
// 添加任务
const handleKeyUp = (e) => {
if (e.key === "Enter") {
const title = inputRef.current.value;
if (title.trim() === "") {
return;
}
onAdd({
id: nanoid(),
title,
completed: false,
});
inputRef.current.value = "";
}
};
return (
<div className="todo-header">
<input
onKeyUp={handleKeyUp}
ref={inputRef}
type="text"
placeholder="请输入你的任务名称,按回车键确认"
/>
</div>
);
}
- List组件
javascript
import PropTypes from "prop-types";
import "./index.css";
import Item from "../Item/index";
function List(props) {
// 检查 props 是否符合 propTypes 定义
PropTypes.checkPropTypes(List.propTypes, props, "prop", "List");
// 解构 props 中的属性
const { todos, onToggleChange, onDelete } = props;
return (
<ul className="todo-main">
{todos.map((todo) => (
<Item
key={todo.id}
todo={todo}
onToggleChange={onToggleChange}
onDelete={onDelete}
/>
))}
</ul>
);
}
// propTypes 和 defaultProps 必须定义在函数外部
List.propTypes = {
todos: PropTypes.array.isRequired,
onToggleChange: PropTypes.func.isRequired,
};
List.defaultProps = {
todos: 1,
onToggleChange: () => {},
};
export default List;
- Item组件
javascript
import { useState } from "react";
import "./index.css";
export default function Item(props) {
const { todo, onToggleChange, onDelete } = props;
const { id, title, completed } = todo;
const [showDelete, setShowDelete] = useState(false);
// 切换任务状态
const handleToggleChange = (id) => {
onToggleChange({
id,
title,
completed: !completed,
});
};
// 删除任务
const handleDelete = (id) => {
if (!confirm("确定删除吗?")) {
return;
}
onDelete(id);
};
return (
<li
onMouseEnter={() => setShowDelete(true)}
onMouseLeave={() => setShowDelete(false)}
>
<label>
<input
type="checkbox"
checked={completed}
onChange={() => handleToggleChange(id)}
/>
<span>{title}</span>
</label>
<button
className="btn btn-danger"
style={{ display: showDelete ? "block" : "none" }}
onClick={() => handleDelete(id)}
>
删除
</button>
</li>
);
}
- Footer组件
javascript
import React from "react";
import "./index.css";
export default function Footer(props) {
const { todos, onToggleChangeAll } = props;
// 计算已完成任务数量
// const completedCount = todos.filter((todo) => todo.completed).length;
const completedCount = todos.reduce(
(prev, cur) => prev + (cur.completed ? 1 : 0),
0,
);
// 计算总任务数量
const totalCount = todos.length;
// 全选/取消全选
const handleToggleChange = (e) => {
onToggleChangeAll(e.target.checked);
};
// 清除已完成任务
const handleClearCompleted = () => {
onToggleChangeAll(false);
};
return (
<>
<div className="todo-footer">
<label>
<input
type="checkbox"
checked={completedCount === totalCount && totalCount !== 0}
onChange={handleToggleChange}
/>
</label>
<span>
<span>已完成{completedCount}</span> / 全部{totalCount}
</span>
<button
className="btn btn-danger"
onClick={() => handleClearCompleted()}
>
清除已完成任务
</button>
</div>
</>
);
}
- 效果

- 相关解释:
- 按功能拆分为Header(添加任务)、List(任务列表容器)、Item(单个任务项)、Footer(任务统计 / 操作),符合 React 组件化 "单一职责" 原则。
- 父子组件通信:通过 props 传递数据(如todos)和回调函数(如onAdd/onToggleChange/onDelete),子组件触发事件后调用父组件回调完成状态更新。(目前通信方面都是在App父/祖先组件上维护,向下级传数据使用的式props,子/孙组件想要给上级组件数据只有通过方法调用,)
- 添加任务:Header通过useRef获取输入框值,监听回车事件,校验非空后调用onAdd添加任务,清空输入框。
- PropTypes.checkPropTypes调用:React 15.5+ 后PropTypes从 React 剥离,且checkPropTypes仅需在开发环境才需要引出校验,才能看到传参错误控制台给提示
第四章 跨域处理
再不清楚,可以看小编小面这篇文章:(虽然是vue的,但是她们只有组件的区别,原理是相同的)
4.1 react解决跨域的两种方式
- 方案一:在package.json中追加如下配置
javascript
```json
"proxy":"http://localhost:5000"
```

- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
4.1.1 方案二:(18/19版本)
- 在vite.config.js中配置代理,与vue相似
javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
// 代理 1:/api -> http://localhost:5000
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq, req) => {
console.log(`📡 [PROXY] ${req.method} ${req.url} -> http://localhost:5000${proxyReq.path}`)
})
}
},
// 代理 2:/api2 -> http://localhost:5001
'/api2': {
target: 'http://localhost:5001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api2/, ''),
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq, req) => {
console.log(`📡 [PROXY] ${req.method} ${req.url} -> http://localhost:5001${proxyReq.path}`)
})
}
}
}
}
})
4.1.2 方案二:(旧版本)
- 创建代理配置文件 ` 在src下创建配置文件:src/setupProxy.js `
- 编写setupProxy.js配置具体代理规则:
javascript
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
createProxyMiddleware('/api1', { // 遇见/api1前缀的请求,就会触发该该代理配置
target: 'http://localhost:5000', // 代理服务器请求转发给谁
changeOrigin: true, // 控制服务器收到的响应头中的Host字段的指
pathRewrite: {'^/api1': ''} // 重写请求路径(必须)
}),
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置稍微繁琐,前端请求资源时必须加前缀。
- 注意事项
有的时候我们在使用下面这段代码时,会发现控制台调了两次接口,是因为我们开了严格模式导致的!在main.jsx中,如果不想要注释了就好;还有这种重复调用 只在开发模式下 发生,生产环境不会有这个问题。
javascript
useEffect(() => {
// 使用代理路径 /api,会被转发到 http://localhost:5000
// pathRewrite 会将 /api 去掉,所以实际请求是 http://localhost:5000/students
axios
.get("http://localhost:5173/api/students")
.then((response) => {
console.log(response.data);
setStudents(response.data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []);

第五章 React的状态管理
5.1 Redux Toolkit
5.1.1 准备
javascript
npm install react-redux @reduxjs/toolkit
5.1.2 基本使用
- Redux 核心模块划分(src/store)
Redux 遵循「单一数据源 + 状态不可变(RTK 内部已封装可变写法)+ 纯函数修改」的原则,代码中按「功能模块」拆分 store,符合模块化最佳实践:
javascript
src/store
├── index.js # 根 store 配置(组合所有子模块 reducer)
└── modules/
├── counterStore.js # 计数器功能模块(同步状态)
└── channelStore.js # 频道列表模块(异步请求 + 状态管理)
- 核心 API 解析及使用(Redux Toolkit):
- createSlice: 是 RTK 的核心简化工具,最核心的 API,整合了「初始状态、reducer 函数、action 创建函数」,避免原生 Redux 手动写 action type、action creator、switch reducer 的繁琐
javascript
import { createSlice } from "@reduxjs/toolkit";
const countStore = createSlice({
name: 'counter', // 命名空间(action type 前缀,如 "counter/inscrement")
initialState: { count: 0 }, // 初始状态
reducers: { // 同步 reducer 函数(RTK 内置 Immer,支持「直接修改状态」,底层自动转不可变操作)
inscrement (state) {
state.count++ // 看似直接修改,实际 Immer 处理为不可变更新
},
descrement (state) {
state.count--
},
addToNum (state, actions) {
state.count += actions.payload // payload 是 action 携带的参数
}
}
})
// 解构生成的 action creator 函数(直接调用返回标准 action 对象,如 {type: "counter/inscrement"})
const { inscrement, descrement, addToNum } = countStore.actions
// 导出 reducer(供根 store 组合)
const reducer = countStore.reducer
export {inscrement, descrement, addToNum}
export default reducer
- name:作为 action type 的前缀,避免多个模块 action type 冲突;
- reducers 中的函数:第一个参数是「当前状态」,第二个是「action 对象」(payload 为传参);
- Immer 库:RTK 内置 Immer,允许在 reducer 中「直接修改 state」(如 state.count++),无需手动写 {...state, count: state.count + 1}。
- configureStore:根 store 配置,store/index.js 中用 configureStore 替代原生 Redux 的 createStore,自动集成中间件(如 redux-thunk 处理异步)、DevTools 调试工具,无需手动配置
javascript
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './modules/counterStore'
import channelReducer from './modules/channelStore'
const store = configureStore({
reducer: { // 组合子模块 reducer,最终 state 结构为 { counter: {...}, channel: {...} }
counter: counterReducer,
channel: channelReducer
}
})
console.log('store', store, store.getState())
export default store
输出内容查看

- 异步 action 处理:RTK 内置 redux-thunk 中间件,允许 action creator 返回「函数」(而非仅 action 对象),从而处理异步逻辑:(注意写法)
javascript
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: []
},
reducers: {
setChannels(state, actions) {
state.channelList = actions.payload
}
}
})
const { setChannels } = channelStore.actions
// 异步 action creator
const fetchChannelList = () => {
// 返回一个接收 dispatch 的函数(thunk 函数)
return async (dispatch) => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels') // 异步请求
dispatch(setChannels(res.data.data.channels)) // 请求成功后,dispatch 同步 action 修改状态
}
}
export { fetchChannelList }
const reducer = channelStore.reducer
export default reducer
核心逻辑:
- 异步 action creator 不直接修改状态,而是通过「请求数据 → dispatch 同步 action」的方式修改;
- dispatch 由 thunk 中间件注入,用于触发同步 reducer 函数。
- Provider:全局注入 store(index.js)
javascript
import { Provider } from 'react-redux';
import store from './store';
root.render(
<Provider store={store}> {/* 把 store 注入整个 React 应用 */}
<App />
</Provider>
);
- Provider 是 react-redux 的核心组件,接收 store 属性,使所有子组件能通过 useSelector/useDispatch 访问 store;
- 必须包裹在根组件外层,否则组件无法获取 store。
- React 组件中使用 Redux(App.js)
javascript
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
inscrement,
descrement,
addToNum,
subtractFromNum,
} from "./store/modules/counterStore";
import { fetchChannelList } from "./store/modules/channelStore";
import "./App.css";
function App() {
const { count } = useSelector((state) => state.counter);
const { channelList } = useSelector((state) => state.channel);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchChannelList());
}, [dispatch]);
return (
<div className="App">
<button onClick={() => dispatch(descrement())}>-</button>
{count}
<button onClick={() => dispatch(inscrement())}>+</button>
<button onClick={() => dispatch(addToNum(10))}>+10</button>
<button onClick={() => dispatch(addToNum(20))}>+20</button>
<button onClick={() => dispatch(subtractFromNum(10))}>-10</button>
<button onClick={() => dispatch(subtractFromNum(20))}>-20</button>
<h2>Channel List</h2>
<ul>
{channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
export default App;
React 组件通过 react-redux 提供的 Hooks 连接 Redux:
1、useSelector:读取 Redux 状态
javascript
import { useSelector, useDispatch } from "react-redux";
// 从根 state 中解构 counter 模块的 count,channel 模块的 channelList
const { count } = useSelector(state => state.counter)
const { channelList } = useSelector(state => state.channel)
- useSelector 接收一个函数,参数是 Redux 根 state,返回需要的状态;
- 组件会订阅对应状态,状态更新时组件自动重渲染。
2、useDispatch:触发 action
javascript
const dispatch = useDispatch() // 获取 dispatch 函数
// 触发同步 action
<button onClick={()=>dispatch(descrement())}>-</button>
// 触发带参数的同步 action
<button onClick={()=>dispatch(addToNum(10))}>+10</button>
// 触发异步 action(在 useEffect 中初始化请求)
useEffect(() => {
dispatch(fetchChannelList())
}, [dispatch])
- useDispatch 返回 Redux store 的 dispatch 方法,用于触发 action(同步 / 异步);
- 异步 action 触发后,thunk 中间件执行异步逻辑,最终通过 dispatch 同步 action 修改状态。
| 维度 | 旧版 Redux | Redux Toolkit |
|---|---|---|
| 核心 API | createStore |
createSlice + configureStore |
| 状态修改 | 必须手动返回新对象,不能直接改 state | 可以直接写 state.num++,自动处理 |
| Action | 需要手动定义、手动导出 | createSlice 自动生成 |
| 异步支持 | 需要手动安装 redux-thunk、配置中间件 | 内置 thunk,开箱即用 |
| 开发体验 | 大量重复样板代码 | 极度精简,代码量少一半以上 |
| 官方态度 | 已废弃,不维护 | 官方强力推荐,唯一标准 |
| 配套生态 | 老旧、兼容性差 | 适配 React18/19、TS、现代项目 |
5.2 Zustand
5.2.1 准备
javascript
npm install zustand axios
5.2.2 基本使用
- 模块划分:
javascript
src/store
├── index.js # 根 store 配置
└── modules/
├── counterStore.js # 计数器功能模块(同步状态)
└── channelStore.js # 频道列表模块(异步请求 + 状态管理)
- modules/***.js
javascript
/**
* 计数器模块
* @param {Function} set - 更新状态的函数
* @param {Function} get - 获取当前状态的函数
* @param {Object} api - store 的 API 对象
*/
const counterSlice = (set, get, api) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
addToNum: (num) => set((state) => ({ count: state.count + num })),
subtractFromNum: (num) => set((state) => ({ count: state.count - num }))
})
export default counterSlice
javascript
import axios from 'axios'
/**
* 频道列表模块
* @param {Function} set - 更新状态的函数
* @param {Function} get - 获取当前状态的函数
* @param {Object} api - store 的 API 对象
*/
const channelSlice = (set, get, api) => ({
channelList: [],
fetchChannelList: async () => {
try {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
set({ channelList: res.data.data.channels })
} catch (error) {
console.error('Failed to fetch channels:', error)
}
}
})
export default channelSlice
- store/index.js
javascript
import { create } from 'zustand'
import counterSlice from './modules/counter'
import channelSlice from './modules/channel'
// 参数说明:
// - set: 用于更新状态
// - get: 用于获取当前状态
// - api: store 的 API 对象(包含 subscribe、destroy 等方法)
const useStore = create((set, get, api) => ({
...counterSlice(set, get, api),
...channelSlice(set, get, api)
}))
export default useStore
- 组件使用(App.jsx):极简的消费方式
javascript
import { useEffect } from 'react'
import useStore from './store'
import './App.css'
function App() {
const {
count,
channelList,
increment,
decrement,
addToNum,
subtractFromNum,
fetchChannelList
} = useStore()
useEffect(() => {
fetchChannelList()
}, [fetchChannelList])
return (
<div className="App">
<button onClick={decrement}>-</button>
{count}
<button onClick={increment}>+</button>
<button onClick={() => addToNum(10)}>+10</button>
<button onClick={() => addToNum(20)}>+20</button>
<button onClick={() => subtractFromNum(10)}>-10</button>
<button onClick={() => subtractFromNum(20)}>-20</button>
<h2>Channel List</h2>
<ul>
{channelList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}
export default App
- 注意:组件中解构多个状态时,Zustand 推荐「精准选择」避免不必要的重渲染:
javascript
// 优化前:解构所有,任意状态变化都会重渲染
const { count, channelList } = useStore()
// 优化后:仅监听需要的状态,粒度更细
const count = useStore(state => state.count)
const channelList = useStore(state => state.channelList)
这里 3 个超级重要参数:
- set:修改状态,等价于Redux 里的 dispatch + reducer 直接更新状态
- get:获取当前最新状态
- ap:i高级用法,一般很少用,暂时不用管。
- 常用语法总结:
javascript
// 1. 创建 store
const useStore = create((set) => ({
状态名: 值,
方法名: () => set({}),
}));
// 2. 组件使用
const 状态 = useStore(state => state.状态名)
const 方法 = useStore(state => state.方法名)
// 3. 修改状态
set({ 状态名: 新值 })
// 4. 异步
方法: async () => {
const res = await axios.get()
set({ 数据: res.data })
}
// 模块文件
const 模块 = (set) => ({ 状态、方法 })
// 合并
...模块(set, get, api)
第六章 React 路由
6.1 Router 6 版本核心
- 路由模式:集中式配置 + useRoutes(核心)
javascript
import { NavLink, useRoutes } from "react-router-dom";
// src/routes/index.jsx - 集中式路由配置数组
const routes = [
{ path: "/", element: <Navigate to="/home" /> }, // 默认重定向
{ path: "/home", element: <Home />, children: [...] }, // 嵌套路由
{ path: "*", element: <Navigate to="/home" /> } // 404 兜底
];
// src/App.jsx - 组件内使用 useRoutes 渲染路由
const element = useRoutes(routes);
return <Suspense fallback={<div>Loading...</div>}>{element}</Suspense>;
- 替代 Router 5 的 <Switch> + 零散 <Route> 写法,代码更易维护;
- 结合 React.lazy() 实现路由组件懒加载,配合 Suspense 处理加载态。(如 const Home = lazy(() => import("../pages/Home/index")))
- 嵌套路由:Outlet 作为子路由出口
javascript
// Home 组件(父路由)中渲染子路由出口
<Outlet />
// 路由配置中定义子路由(src/routes/index.jsx)
{
path: "/home",
element: <Home />,
children: [
{ path: "news", element: <News /> }, // /home/news
{ path: "message", element: <Message /> } // /home/message
]
}

- Router 6 强制要求嵌套路由通过 Outlet 声明渲染位置,Router 5 则是手动嵌套 <Route> 标签;
- 子路由路径无需写全路径(如 news 而非 /home/news),更简洁。
- 导航链接:NavLink 激活态重构
javascript
// 激活态通过函数式 className 判断(替代 Router 5 的 activeClassName)
<NavLink
to="/home"
className={({ isActive }) => `list-group-item ${isActive ? "active" : ""}`}
>Home</NavLink>
- Router 6 移除 activeClassName/activeStyle,改为通过回调函数获取 isActive 状态;
- 路由传参方式1:Params 传参
需要routes.js中配置:
javascript
{
path: "detail/:id/:type?", // 支持 params 传参,type 可选
// path: "detail", // 支持 search 传参,id 可选,title 可选,content 可选
element: <Detail />,
},
javascript
// 跳转(URL 格式:/home/detail/1001/news)
<NavLink to="/home/detail/1001/news">查看新闻详情 (Params)</NavLink>
// 接收参数
import { useParams } from "react-router-dom";
const params = useParams();
<p>ID: {params.id}</p>
<p>类型: {params.type}</p>

Router 6 默认精确匹配,但 /home/detail/:id/:type 这种写法会开启模糊匹配;
- 路由传参方式2:Search 传参
javascript
{
// path: "detail/:id/:type?", // 支持 params 传参,type 可选
path: "detail", // 支持 search 传参,id 可选,title 可选,content 可选
element: <Detail />,
},
javascript
// 跳转(URL 格式:/home/detail?id=2001&title=搜索传参示例)
<NavLink to="/home/detail?id=2001&title=搜索传参示例">查看详情 (Search)</NavLink>
import { useSearchParams } from "react-router-dom";
// 接收参数(Router 6 新增 useSearchParams)
const [searchParams] = useSearchParams();
<p>ID: {searchParams.get('id')}</p>
<p>标题: {searchParams.get('title')}</p>
- 路由传参方式3:State 传参
javascript
// 跳转(参数不暴露在 URL 中)
<NavLink to="/home/detail" state={{ id: 3001, content: "state 传参内容" }}>查看详情 (State)</NavLink>
// 接收参数
import { useLocation } from "react-router-dom";
const location = useLocation();
const state = location.state;
<p>ID: {state.id}</p>
<p>内容: {state.content}</p>
- 编程式导航:useNavigate 替代 useHistory
javascript
const navigate = useNavigate();
// Params 跳转
onClick={() => navigate("/home/detail/4001/navigate")}
// Search 跳转
onClick={() => navigate("/home/detail?id=5001&title=navigate搜索")}
// State 跳转
onClick={() => navigate("/home/detail", { state: { id: 6001 } })}
// 回退上一页
onClick={() => navigate(-1)}
// 跳转到 About
onClick={() => navigate("/about")}
第二个对象传参,还可以控制是replace还是push
javascript
navigate(路径, {
replace: true, // true=替换,false=push(默认)
state: { id: 1, name: 'xxx' } // 路由传参
})
| 功能 | Router 5(history) | Router 6(navigate) |
|---|---|---|
| 跳转(可返回) | push("/home") | navigate("/home") |
| 替换(不可返回) | replace("/home") | navigate("/home", {replace:true}) |
| 返回 | goBack() | navigate(-1) |
| 前进 | goForward() | navigate(1) |
| 跳多级 | go(-2) | navigate(-2) |
| 带 state 跳转 | push("/a", {id:1}) | navigate("/a", {state:{id:1}}) |
- 重定向:Navigate 替代 Redirect
javascript
// 默认路由重定向到 /home
{ path: "/", element: <Navigate to="/home" /> }
// 404 兜底重定向
{ path: "*", element: <Navigate to="/home" /> }
- 路由 Hooks 组合使用
javascript
// About 组件中获取路由全量信息
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
// Detail 组件中拆分使用
const params = useParams(); // Params 传参
const [searchParams] = useSearchParams(); // Search 传参
const location = useLocation(); // State 传参
6.2 Router 5 版本核心
- 路由模式,Router 5 必须这样写(Switch + Route)
javascript
import { Switch, Route, Redirect } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'
function App() {
return (
<div>
<Switch>
{/* 重定向 / → /home */}
<Route exact path="/" render={() => <Redirect to="/home" />} />
{/* 普通路由 */}
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
{/* 404 */}
<Route component={NotFound} />
</Switch>
</div>
)
}
- <Switch>:只匹配第一个路由
- <Route exact path="/">:精确匹配
- <Redirect to="/home">:重定向
- <Route component={Home}>:挂载组件
- 嵌套路由(Router5:必须在组件内部再写 <Switch> + <Route>)
javascript
// pages/Home/index.js
import { Switch, Route } from 'react-router-dom'
import News from './News'
import Message from './Message'
const Home = () => {
return (
<div>
<h1>Home 页面</h1>
{/* 子路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
</Switch>
</div>
)
}
- 必须写完整路径 /home/news
- 必须自己包 <Switch>
- 没有 <Outlet>
- NavLink 导航高亮
javascript
<NavLink activeClassName="active" to="/home">
首页
</NavLink>
- activeClassName="active" 自动激活
- 不用写函数判断
- 编程式导航useHistory()
javascript
import { useHistory } from 'react-router-dom'
const Home = () => {
const history = useHistory()
return (
<button onClick={() => history.push('/about')}>
跳转到关于
</button>
)
}
| 功能 | Router 5(history) | Router 6(navigate) |
|---|---|---|
| 跳转(可返回) | push("/home") | navigate("/home") |
| 替换(不可返回) | replace("/home") | navigate("/home", {replace:true}) |
| 返回 | goBack() | navigate(-1) |
| 前进 | goForward() | navigate(1) |
| 跳多级 | go(-2) | navigate(-2) |
| 带 state 跳转 | push("/a", {id:1}) | navigate("/a", {state:{id:1}}) |
- 路由传参方式一:params 传参
javascript
to="/detail/1001"
const params = useParams() // 一样
- 路由传参方式二:search 传参
javascript
import { useLocation } from 'react-router-dom'
const location = useLocation()
const search = new URLSearchParams(location.search)
const id = search.get('id')
- 路由传参方式三:state 传参
javascript
// 跳转
history.push('/detail', { id: 1001 })
// 获取
const location = useLocation()
location.state
注意:上面说的都是支持hooks之后,支持旧版本,也就是react还在使用类式组件时,当时是用props接收参数的,如下:
javascript
// Detail.js 【Router5 纯 props 写法】
function(props){
// 1. params 参数
console.log(props.match.params)
// 2. search 参数
console.log(props.location)
// 3. 跳转方法
console.log(props.history)
return ...
}
6.3 区别
| 功能 | Router 6 | Router 5 |
|---|---|---|
| 路由容器 | useRoutes() / <Routes> |
<Switch> |
| 重定向 | <Navigate to="/home" /> |
<Redirect to="/home" /> |
| 嵌套路由 | <Outlet /> |
组件内写 <Route> |
| 导航高亮 | className={({isActive}) => ...} |
activeClassName="active" |
| 编程导航 | useNavigate() |
useHistory() |
| 跳转 | navigate('/a') |
history.push('/a') |
| Search 参数 | useSearchParams() |
手动解析 location.search |
| 路径匹配 | 默认为精确匹配 | 需要加 exact 才精确 |
| 404 | path="*" |
最后一个 <Route> |