大白话React第三章高级应用阶段
1. 学习 React 路由
在单页应用里,页面不会像传统网页那样每次切换都刷新整个页面,React 路由就像是一个智能的导航员,能让你在一个页面里轻松切换不同的“场景”,就像在一个大房子里从客厅走到卧室。
想象一下你在开发一个类似博客的单页应用,有首页、文章详情页、关于页等。使用 React 路由,就能让用户在这些页面间流畅切换。
首先安装 react-router-dom
,在命令行运行 npm install react-router-dom
。
// 引入必要的组件
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// 定义不同的页面组件
const Home = () => <h1>欢迎来到博客首页</h1>;
const Article = () => <h1>这是一篇精彩的文章</h1>;
const About = () => <h1>关于我们的博客</h1>;
const App = () => {
return (
// 使用 Router 包裹整个应用
<Router>
{/* 导航栏,用 Link 组件实现页面跳转 */}
<nav>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/article">文章</Link></li>
<li><Link to="/about">关于</Link></li>
</ul>
</nav>
{/* Routes 组件用来匹配路径 */}
<Routes>
{/* 当路径为根路径时,渲染 Home 组件 */}
<Route path="/" element={<Home />} />
{/* 当路径为 /article 时,渲染 Article 组件 */}
<Route path="/article" element={<Article />} />
{/* 当路径为 /about 时,渲染 About 组件 */}
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
};
export default App;
2. 学习 Redux 状态管理
当你的应用变得复杂,就像一个大型商场,里面有很多店铺,每个店铺都有自己的商品库存信息。如果没有统一的管理系统,就会乱成一团。Redux 就像是商场的中央管理系统,能统一管理应用里的状态。
- Action(动作):就像顾客向商场管理系统发出的请求,比如“我要一件 T 恤”。
- Reducer(处理者):就像商场的库存管理员,根据顾客的请求(action)来更新库存信息(状态)。
- Store(仓库):就像商场的总仓库,存放着所有商品的库存信息(应用的状态)。
// 1. 定义 Action
// 增加商品数量的 action
const addItem = () => ({
type: 'ADD_ITEM'
});
// 2. 定义 Reducer
// 初始状态
const initialState = {
itemCount: 0
};
// reducer 函数,根据 action 类型更新状态
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, itemCount: state.itemCount + 1 };
default:
return state;
}
};
// 3. 创建 Store
import { createStore } from 'redux';
const store = createStore(itemReducer);
// 4. 在 React 组件中使用 Redux
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
const ItemCounter = () => {
// 获取状态
const itemCount = useSelector(state => state.itemCount);
// 分发 action
const dispatch = useDispatch();
return (
<div>
<p>商品数量: {itemCount}</p>
<button onClick={() => dispatch(addItem())}>增加商品</button>
</div>
);
};
export default ItemCounter;
3. 性能优化
随着应用变得复杂,可能会出现运行变慢的情况,就像一辆车拉的东西太多就跑不快了。我们需要对 React 应用进行性能优化,让它跑起来更顺畅。
React.memo
(函数组件):就像一个智能的门卫,会记住之前来过的客人(组件的输入),如果下次来的客人一样,就直接放行,不再重新检查(渲染组件)。
import React from 'react';
// 定义一个普通的函数组件
const MyComponent = ({ name }) => {
console.log('组件重新渲染了');
return <p>你好,{name}</p>;
};
// 使用 React.memo 包裹组件进行性能优化
const MemoizedComponent = React.memo(MyComponent);
const App = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>点击加 1</button>
{/* 即使 count 改变,只要 name 不变,MemoizedComponent 不会重新渲染 */}
<MemoizedComponent name="张三" />
</div>
);
};
export default App;
4. 开发可复用组件
可复用组件就像乐高积木,你可以用同样的积木搭建出不同的造型。开发可复用组件能提高开发效率,减少代码重复。
比如开发一个通用的按钮组件,这个按钮可以在不同的地方使用,样式和功能可以根据需要调整。
import React from 'react';
// 定义一个可复用的按钮组件
const CustomButton = ({ text, onClick, color }) => {
return (
<button
style={{ backgroundColor: color }}
onClick={onClick}
>
{text}
</button>
);
};
const App = () => {
const handleClick = () => {
alert('按钮被点击了');
};
return (
<div>
{/* 使用自定义按钮组件 */}
<CustomButton text="蓝色按钮" onClick={handleClick} color="blue" />
<CustomButton text="红色按钮" onClick={handleClick} color="red" />
</div>
);
};
export default App;
通过这个阶段的学习,你能让 React 应用变得更强大、更流畅,开发起来也更高效。
以下将结合一个简单的待办事项(Todo List)应用场景,详细解释 Redux 中的 store
、action
、reducer
并给出代码示例。
详细解释 Redux 中的 store、action、reducer
咱们可以把 Redux 想象成一个大型超市的管理系统,这样就能很好理解 store、action、reducer 分别是干啥的啦。
1. Store(仓库)
- 概念:Store 就像是超市的总仓库,它存放着超市里所有商品的信息,也就是整个应用的状态。在 Redux 里,所有组件需要共享的数据都放在 Store 里面。整个应用只能有一个 Store,就像一个超市只有一个总仓库一样。
- 作用:它负责保管和管理这些状态,并且提供了一些方法让我们可以获取和更新这些状态。组件可以从 Store 里获取需要的数据来展示给用户看,也可以通过特定的方式让 Store 里的数据发生变化。
- 代码示例:
javascript">import { createStore } from 'redux';
// 这是一个简单的 reducer 函数,后面会详细说
function counterReducer(state = { count: 0 }, action) {
// 根据不同的 action 来更新状态
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
// 创建 Store,把 reducer 函数传进去
const store = createStore(counterReducer);
// 现在 store 就管理着 { count: 0 } 这个状态啦
2. Action(请求)
- 概念:Action 就像是顾客给超市仓库管理员下的订单或者请求。在 Redux 里,它是一个描述状态变化的对象,必须有一个
type
属性,用来告诉管理员(reducer)要做什么操作,还可以有其他属性来携带一些额外的数据。 - 作用:组件通过创建和发送 Action 来告诉 Store 它想要改变状态。就像顾客通过订单告诉仓库管理员要增加或者减少某种商品的库存。
- 代码示例:
javascript">// 定义一个增加数量的 action
const incrementAction = {
type: 'INCREMENT'
};
// 定义一个减少数量的 action
const decrementAction = {
type: 'DECREMENT'
};
// 还可以有携带额外数据的 action
const addByAmountAction = {
type: 'ADD_BY_AMOUNT',
amount: 5 // 额外的数据,这里表示要增加 5
};
3. Reducer(管理员)
- 概念:Reducer 就像是超市的仓库管理员,它接收顾客的订单(Action),然后根据订单的要求来更新仓库里商品的信息(状态)。Reducer 是一个纯函数,这意味着它只根据输入的参数(当前状态和 Action)来计算新的状态,不会有任何副作用,比如不会修改传入的参数。
- 作用:根据不同类型的 Action 对当前的状态进行处理,返回一个新的状态。Store 会根据 Reducer 返回的新状态来更新自己管理的状态。
- 代码示例:
javascript">// 还是上面那个 counterReducer 函数
function counterReducer(state = { count: 0 }, action) {
// 根据 action 的 type 属性来决定怎么做
switch (action.type) {
case 'INCREMENT':
// 当收到增加的请求时,返回一个新的状态,数量加 1
return { count: state.count + 1 };
case 'DECREMENT':
// 当收到减少的请求时,返回一个新的状态,数量减 1
return { count: state.count - 1 };
case 'ADD_BY_AMOUNT':
// 当收到按指定数量增加的请求时,返回一个新的状态,数量加上指定的 amount
return { count: state.count + action.amount };
default:
// 如果收到不认识的请求,就返回原来的状态
return state;
}
}
整个流程
组件想要改变状态时,会创建一个 Action,然后把这个 Action 发送给 Store。Store 收到 Action 后,会把当前的状态和这个 Action 一起传给 Reducer。Reducer 根据 Action 的类型对状态进行处理,返回一个新的状态。最后,Store 用这个新的状态替换掉原来的状态,组件就可以从 Store 里获取到更新后的状态啦。就像顾客下订单,仓库管理员根据订单更新库存信息,然后其他部门可以看到更新后的库存一样。
Redux 中如何使用 Redux Toolkit 来简化配置?
Redux Toolkit 是官方推荐的简化 Redux 开发的工具集,它解决了传统 Redux 配置繁琐的问题。下面我们用通俗易懂的语言和示例代码,来详细介绍如何使用 Redux Toolkit 简化配置。
1. 安装 Redux Toolkit
首先,你得把 Redux Toolkit 这个“工具箱”添加到你的项目里,就像给你的工具库增添新工具一样。在命令行运行下面的命令:
npm install @reduxjs/toolkit react-redux
这里的 @reduxjs/toolkit
是 Redux Toolkit 本身,react-redux
则用于在 React 应用中连接 Redux 状态。
2. 创建 Slice(切片)
Slice 就像是把整个大的状态“蛋糕”切成小块,每个小块负责管理一部分状态。它把 action 和 reducer 组合在一起,让代码更简洁。
javascript">// 在 src/features/counter/counterSlice.js 文件中创建切片
import { createSlice } from '@reduxjs/toolkit';
// 创建一个 counterSlice
const counterSlice = createSlice({
// 切片的名称,方便识别
name: 'counter',
// 初始状态,这里表示计数器从 0 开始
initialState: {
value: 0
},
// 定义 reducers,也就是处理状态变化的函数
reducers: {
increment: (state) => {
// 直接修改状态,Redux Toolkit 允许这样做,它会自动处理不可变性
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
// action.payload 是传递过来的数据
state.value += action.payload;
}
}
});
// 导出 action 创建函数,方便组件调用
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer,用于创建 store
export default counterSlice.reducer;
3. 创建 Store
有了切片后,我们要把这些切片组合起来,放到一个“大仓库”(Store)里。
javascript">// 在 src/app/store.js 文件中创建 store
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
// 使用 configureStore 创建 store
const store = configureStore({
// 把不同的 reducer 组合在一起
reducer: {
counter: counterReducer
}
});
export default store;
4. 在 React 应用中使用 Store
现在我们已经有了状态管理的“大仓库”和各种操作“工具”,接下来要把它们用到 React 应用里。
// 在 src/index.js 文件中使用 Provider 包裹应用
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './app/store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
5. 在组件中使用状态和 dispatch action
最后,在组件里我们可以获取状态并触发状态的改变。
// 在 src/App.js 文件中使用状态和 action
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './features/counter/counterSlice';
const App = () => {
// 使用 useSelector 获取状态
const count = useSelector((state) => state.counter.value);
// 使用 useDispatch 获取 dispatch 函数,用于触发 action
const dispatch = useDispatch();
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => dispatch(increment())}>加 1</button>
<button onClick={() => dispatch(decrement())}>减 1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>加 5</button>
</div>
);
};
export default App;
总结
通过 Redux Toolkit,我们简化了 Redux 的配置过程:
createSlice
让我们可以把 action 和 reducer 写在一起,还能直接修改状态,不用手动处理不可变性。configureStore
自动集成了中间件和开发者工具,让创建 store 变得简单。- 在 React 组件里,使用
useSelector
和useDispatch
可以轻松获取状态和触发 action。
这样一来,我们就能更高效地进行 Redux 开发啦。
如何使用 Redux 在 React 项目中进行状态管理?
什么是 Redux 状态管理
想象你在玩一个大型的角色扮演游戏,游戏里有很多角色和物品,每个角色有自己的生命值、魔法值,物品也有不同的属性。如果每个角色自己管理自己的状态,很容易就会乱套。Redux 就像是游戏的中央数据库,把所有角色和物品的状态都统一管理起来,这样就方便多啦。在 React 项目里,当应用变得复杂,有很多组件需要共享和修改数据时,Redux 就能很好地帮我们管理这些状态。
如何使用 Redux 进行状态管理
1. 安装 Redux 和相关依赖
首先,你得给你的项目装上 Redux 这个“管理工具”。在命令行里运行下面的命令:
npm install redux react-redux
redux
是核心库,react-redux
是让 Redux 和 React 能友好合作的桥梁。
2. 定义 Action
Action 就像是游戏里的指令,告诉系统要做什么事情。比如在游戏里,玩家按下攻击键,这就是一个 Action。在代码里,Action 是一个包含 type
属性的对象。
// 定义一个增加计数器的 action
const increment = () => {
return {
type: 'INCREMENT'
};
};
// 定义一个减少计数器的 action
const decrement = () => {
return {
type: 'DECREMENT'
};
};
这里的 type
是 Action 的标识,用来告诉后面的处理者要执行什么操作。
3. 定义 Reducer
Reducer 就像是游戏的裁判,根据 Action 来更新游戏状态。它是一个纯函数,接收当前的状态和 Action 作为参数,返回新的状态。
// 初始状态
const initialState = {
count: 0
};
// reducer 函数
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
在这个例子里,当接收到 INCREMENT
类型的 Action 时,就把计数器的值加 1;接收到 DECREMENT
类型的 Action 时,就把计数器的值减 1。如果是其他类型的 Action,就返回原来的状态。
4. 创建 Store
Store 就是游戏的中央数据库,把所有的状态都存起来。使用 createStore
函数来创建 Store。
import { createStore } from 'redux';
// 创建 store
const store = createStore(counterReducer);
这里把刚才定义的 counterReducer
传给 createStore
函数,这样 Store 就知道怎么根据 Action 来更新状态了。
5. 在 React 组件中使用 Redux
要让 React 组件能和 Redux 的 Store 交互,需要使用 react-redux
提供的 Provider
和 useSelector
、useDispatch
等钩子。
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
// 根组件
const App = () => {
// 使用 useSelector 从 store 中获取状态
const count = useSelector(state => state.count);
// 使用 useDispatch 获取 dispatch 函数,用来分发 action
const dispatch = useDispatch();
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(decrement())}>减少</button>
</div>
);
};
// 使用 Provider 包裹根组件,把 store 传递给整个应用
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Provider
:就像是一个“传送门”,把 Store 传递给整个 React 应用,这样所有的组件都能访问 Store 里的状态。useSelector
:从 Store 里“挑选”出我们需要的状态,就像从数据库里查询数据一样。useDispatch
:用来分发 Action,就像给游戏裁判发送指令一样。
总结
通过以上步骤,我们就可以在 React 项目里使用 Redux 进行状态管理了。首先定义 Action 来描述要做的事情,然后用 Reducer 来处理这些 Action 并更新状态,创建 Store 来存储状态,最后在 React 组件里使用 react-redux
提供的工具和 Store 进行交互。这样,无论应用变得多么复杂,状态管理都会变得井井有条。
react路由以及路由跳转
什么是 React 路由
想象你有一个大房子,里面有客厅、卧室、厨房等不同的房间。你在这个房子里走来走去,从一个房间到另一个房间,就像是在不同的“页面”之间切换。React 路由就像是这个房子里的导航系统,它能帮你决定在什么情况下进入哪个房间,也就是在 React 应用里决定显示哪个页面。
路由跳转
怎么实现路由跳转
在 React 里,要实现路由跳转,就像你在房子里从一个房间走到另一个房间需要有门一样,得有相应的“通道”。通常使用 react-router-dom
这个库来实现,它提供了一些工具让我们方便地进行跳转。
比如你在网页上看到一个导航栏,点击上面的“首页”“关于我们”“产品介绍”这些链接,页面就会切换到对应的内容,这就是路由跳转。
代码示例
首先要安装 react-router-dom
,在命令行输入 npm install react-router-dom
。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// 定义不同的页面组件,就像不同的房间
const HomePage = () => <h1>这是首页</h1>;
const AboutPage = () => <h1>这是关于我们页面</h1>;
const ProductPage = () => <h1>这是产品介绍页面</h1>;
const App = () => {
return (
// Router 就像房子的整体框架,包裹整个应用
<Router>
{/* 导航栏,里面的 Link 就像房间之间的门 */}
<nav>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/about">关于我们</Link></li>
<li><Link to="/products">产品介绍</Link></li>
</ul>
</nav>
{/* Routes 就像一个智能的门卫,根据路径决定让你进哪个房间 */}
<Routes>
{/* 当路径是根路径 / 时,显示 HomePage 组件 */}
<Route path="/" element={<HomePage />} />
{/* 当路径是 /about 时,显示 AboutPage 组件 */}
<Route path="/about" element={<AboutPage />} />
{/* 当路径是 /products 时,显示 ProductPage 组件 */}
<Route path="/products" element={<ProductPage />} />
</Routes>
</Router>
);
};
export default App;
在这个例子里,Link
组件就像是一个个按钮,点击它就会触发路由跳转,改变浏览器的地址栏路径。Routes
和 Route
组件会根据这个路径来决定显示哪个页面组件。
路由使用场景
单页应用(SPA)
单页应用就像一个大型的商场,虽然看起来有很多不同的区域(页面),但实际上只有一个网页。用户在不同区域之间切换时,页面不会刷新,体验很流畅。比如很多网站的前端界面,像一些博客网站、电商网站的商品列表和详情页切换等,使用路由可以实现这种无刷新的页面切换,提高用户体验。
权限管理
想象一个公司的办公系统,不同的员工有不同的权限,有的员工只能访问自己的工作区域(页面),有的员工可以访问更多的区域。使用路由可以根据用户的权限来决定他们能访问哪些页面。比如普通员工只能访问自己的考勤页面,而管理员可以访问所有员工的考勤页面和系统设置页面。在代码里可以在路由跳转前检查用户的权限,如果权限不够就跳转到提示页面或者禁止访问。
动态内容展示
有些网站会根据用户的操作动态地展示不同的内容。比如在一个新闻网站里,用户点击不同的新闻标题,页面就会显示相应的新闻详情。这时候可以使用路由来根据不同的新闻 ID 显示对应的新闻内容。路由可以携带参数,就像给不同的房间贴上不同的标签,根据标签找到对应的房间内容。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';
// 模拟新闻数据
const newsList = [
{ id: 1, title: '新闻标题 1', content: '这是新闻 1 的内容' },
{ id: 2, title: '新闻标题 2', content: '这是新闻 2 的内容' }
];
// 新闻列表页面
const NewsList = () => {
return (
<div>
<h1>新闻列表</h1>
<ul>
{newsList.map(news => (
<li key={news.id}>
{/* 链接到新闻详情页,携带新闻 ID 作为参数 */}
<Link to={`/news/${news.id}`}>{news.title}</Link>
</li>
))}
</ul>
</div>
);
};
// 新闻详情页面
const NewsDetail = () => {
// 使用 useParams 获取路由参数
const { id } = useParams();
const news = newsList.find(n => n.id === parseInt(id));
return (
<div>
<h1>{news.title}</h1>
<p>{news.content}</p>
</div>
);
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<NewsList />} />
<Route path="/news/:id" element={<NewsDetail />} />
</Routes>
</Router>
);
};
export default App;
在这个例子里,点击新闻列表里的标题,会跳转到对应的新闻详情页,通过路由参数 id
来区分不同的新闻。
** React 嵌套路由**
什么是 React 路由中的嵌套路由
想象你去参观一个大型商场,商场有很多楼层,每个楼层又有不同的店铺。你从商场大门进去后,先选择去某一层楼,然后在这层楼里再选择进入某一家店铺。这就类似于 React 中的嵌套路由。
在 React 应用里,一个大的页面(父路由)可以包含多个小的子页面(子路由),子路由的显示是基于父路由的。也就是说,当你访问父路由对应的页面时,页面里还可以根据不同的情况显示不同的子路由页面,这样的路由结构就叫做嵌套路由。
为什么需要嵌套路由
- 提高页面组织性:当应用变得复杂,有很多不同的功能模块时,使用嵌套路由可以把页面结构划分得更清晰,每个子模块有自己独立的路由,便于管理和维护。
- 实现复杂页面布局:有些页面的布局是分区域的,比如一个后台管理系统,有侧边栏导航和主内容区,侧边栏导航可以控制主内容区显示不同的子页面,这就可以通过嵌套路由来实现。
如何使用嵌套路由
下面我们通过一个具体的例子来详细说明如何在 React 中使用嵌套路由,这里使用 react-router-dom
库。
1. 安装依赖
首先,确保你已经安装了 react-router-dom
,如果没有安装,可以在项目目录下的命令行中运行以下命令:
npm install react-router-dom
2. 创建路由组件
假设我们要创建一个简单的博客管理系统,有博客列表和博客详情页,博客详情页又包含博客内容和评论区,这就可以用嵌套路由来实现。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
// 博客列表页组件
const BlogList = () => {
return (
<div>
<h2>博客列表</h2>
<ul>
<li><Link to="blog/1">博客 1</Link></li>
<li><Link to="blog/2">博客 2</Link></li>
</ul>
</div>
);
};
// 博客详情页父组件
const BlogDetail = () => {
return (
<div>
<h2>博客详情</h2>
{/* Outlet 组件用于渲染子路由对应的组件 */}
<Outlet />
</div>
);
};
// 博客内容子组件
const BlogContent = () => {
return <p>这是博客的具体内容</p>;
};
// 博客评论区子组件
const BlogComments = () => {
return <p>这里是博客的评论区</p>;
};
// 根组件
const App = () => {
return (
<Router>
<Routes>
{/* 父路由,显示博客列表页 */}
<Route path="/" element={<BlogList />} />
{/* 父路由,显示博客详情页,包含子路由 */}
<Route path="blog/:id" element={<BlogDetail />}>
{/* 子路由,显示博客内容 */}
<Route index element={<BlogContent />} />
{/* 子路由,显示博客评论区 */}
<Route path="comments" element={<BlogComments />} />
</Route>
</Routes>
</Router>
);
};
export default App;
3. 代码解释
BrowserRouter
:包裹整个应用,提供路由功能的上下文环境。Routes
和Route
:Routes
组件用于匹配路径,Route
组件定义具体的路由规则。父路由可以包含子路由,子路由嵌套在父路由的Route
组件内部。Link
:用于创建导航链接,点击后会触发路由跳转。Outlet
:在父路由组件中使用,它就像一个占位符,当访问子路由时,子路由对应的组件会渲染在Outlet
的位置。index
属性:在子路由中使用index
属性,表示当访问父路由路径时默认显示的子路由组件。
4. 访问路径示例
- 访问博客列表页:
http://localhost:3000
- 访问博客 1 的内容:
http://localhost:3000/blog/1
- 访问博客 1 的评论区:
http://localhost:3000/blog/1/comments
通过以上步骤,你就可以在 React 应用中使用嵌套路由来构建复杂的页面结构了。
** React 路由传参、路由多级传参**
React 路由传参
啥是路由传参
想象你要去不同的房间拿不同的东西,每个房间都有自己的编号。在 React 里,页面就像房间,你从一个页面跳到另一个页面的时候,有时候得告诉新页面一些额外的信息,就像告诉别人去几号房间拿东西,这个传递额外信息的过程就是路由传参。
路由传参的方式及示例
1. 路径参数
就好比每个房间的门上都有个独特的号码牌,你根据号码牌找到对应的房间。在路由里,路径参数就是把参数直接放在路径里。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';
// 定义一个显示用户信息的组件
const UserInfo = () => {
// 使用 useParams 获取路径参数
const { userId } = useParams();
return <h1>这是用户 {userId} 的信息页面</h1>;
};
const App = () => {
return (
<Router>
<Routes>
{/* 定义包含路径参数的路由 */}
<Route path="/user/:userId" element={<UserInfo />} />
</Routes>
<Link to="/user/123">查看用户 123 的信息</Link>
</Router>
);
};
export default App;
在这个例子里,/user/:userId
中的 :userId
就是路径参数,Link
组件跳转到 /user/123
时,123
就作为 userId
参数传递给 UserInfo
组件,在 UserInfo
组件里用 useParams
就能拿到这个参数。
2. 查询参数
查询参数就像你给房间送东西时,在包裹上贴个小纸条写着额外信息。在路由里,查询参数是跟在路径后面,用 ?
分隔,参数之间用 &
连接。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useSearchParams } from 'react-router-dom';
// 定义一个显示商品信息的组件
const ProductInfo = () => {
// 使用 useSearchParams 获取查询参数
const [searchParams] = useSearchParams();
const productId = searchParams.get('productId');
return <h1>这是商品 {productId} 的信息页面</h1>;
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/product" element={<ProductInfo />} />
</Routes>
<Link to="/product?productId=456">查看商品 456 的信息</Link>
</Router>
);
};
export default App;
这里 /product?productId=456
中的 productId=456
就是查询参数,在 ProductInfo
组件里用 useSearchParams
来获取这些参数。
多级路由传参
啥是多级路由传参
多级路由就像你要去一个大商场里的小店铺,先得找到商场(一级路由),再在商场里找到具体的楼层(二级路由),最后找到店铺(三级路由)。多级路由传参就是在这个过程中,从一级路由一直把信息传到最底层的路由。
多级路由传参示例
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useParams, useSearchParams } from 'react-router-dom';
// 一级路由组件:商场
const Mall = () => {
return (
<div>
<h1>欢迎来到商场</h1>
<Link to="floor/1/shop/789?discount=20%">去 1 楼 789 号店铺,有 20% 折扣</Link>
</div>
);
};
// 二级路由组件:楼层
const Floor = () => {
const { floorId } = useParams();
return (
<div>
<h2>这是 {floorId} 楼</h2>
{/* Outlet 用于渲染子路由组件 */}
<Outlet />
</div>
);
};
// 三级路由组件:店铺
const Shop = () => {
const { shopId } = useParams();
const [searchParams] = useSearchParams();
const discount = searchParams.get('discount');
return <h3>这是 {shopId} 号店铺,现在有 {discount} 折扣</h3>;
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Mall />} />
<Route path="floor/:floorId" element={<Floor />}>
<Route path="shop/:shopId" element={<Shop />} />
</Route>
</Routes>
</Router>
);
};
export default App;
在这个例子里,从 Mall
组件跳转到 Shop
组件的过程中,通过路径参数传递了 floorId
和 shopId
,通过查询参数传递了 discount
信息。在 Shop
组件里,用 useParams
获取路径参数,用 useSearchParams
获取查询参数,这样就实现了多级路由传参。
多级路由传参和普通路由传参有什么区别?
多级路由传参和普通路由传参本质上都是在路由跳转过程中传递数据,但由于多级路由存在嵌套结构,它们在使用场景、参数传递的复杂度、组件获取参数的方式等方面存在一些区别,以下为你详细介绍:
使用场景
- 普通路由传参:适用于简单的单级页面跳转场景,页面结构相对扁平。例如一个新闻列表页跳转到新闻详情页,只需在跳转时传递新闻的 ID 即可获取对应新闻的详细信息,这种情况下使用普通路由传参就能够满足需求,逻辑简单清晰。
- 多级路由传参:主要用于复杂的页面嵌套结构,当应用具有多级菜单或者多层级的页面导航时会用到。比如一个电商管理系统,有一级导航为商品管理、订单管理等,点击商品管理进入二级页面,包含商品列表、商品分类等,再点击商品列表中的某一商品进入三级页面商品详情。在这个过程中,从一级页面到三级页面的跳转就需要多级路由传参来确保每一级页面都能获取到所需的数据。
参数传递的复杂度
- 普通路由传参:通常只涉及一次跳转,传递的参数数量和层次相对较少。参数传递方式简单直接,可能只是传递一个或几个基本信息,如用户 ID、文章标题等。例如,从用户列表页跳转到用户详情页,只需要传递用户 ID 这一个参数。
// 普通路由传参示例
<Link to={`/user/${userId}`}>查看用户详情</Link>
- 多级路由传参:由于涉及多个层级的跳转,可能需要在不同层级之间传递和共享参数,参数的传递和管理更为复杂。可能需要考虑参数在不同层级的传递顺序、参数的作用范围以及如何确保参数在多级跳转中不丢失。例如,在上述电商管理系统中,从一级页面传递商品类型信息到二级页面,二级页面再结合商品 ID 传递到三级页面。
// 多级路由传参示例
<Link to={`/category/${categoryId}/product/${productId}`}>查看商品详情</Link>
组件获取参数的方式
- 普通路由传参:在接收参数的组件中,获取参数的方式较为直接。通常使用 React Router 提供的钩子函数,如
useParams
用于获取路径参数,useSearchParams
用于获取查询参数,直接就能获取到传递过来的参数。
// 普通路由接收参数示例
const UserDetail = () => {
const { userId } = useParams();
return <div>用户 ID: {userId}</div>;
};
- 多级路由传参:在多级路由中,由于存在嵌套关系,不同层级的组件获取参数的情况更为复杂。除了使用
useParams
和useSearchParams
外,还可能需要考虑如何在嵌套组件中正确获取上级传递下来的参数。有时候可能需要通过上下文(Context)或者状态管理库来辅助传递和获取参数,以确保参数能够在多级组件中正确共享。
// 多级路由接收参数示例
const ProductDetail = () => {
const { categoryId, productId } = useParams();
return (
<div>
商品分类 ID: {categoryId}, 商品 ID: {productId}
</div>
);
};
路由配置的复杂度
- 普通路由传参:路由配置相对简单,只需要在路由定义中设置好路径和对应的组件即可,参数传递通过跳转链接或编程式导航实现。例如:
<Routes>
<Route path="/user/:userId" element={<UserDetail />} />
</Routes>
- 多级路由传参:需要配置多级嵌套的路由结构,确保每一级路由都能正确匹配和渲染。在配置时要考虑不同层级之间的路径关系以及如何让子路由能够接收到父路由传递的参数,路由配置文件会更加复杂和冗长。
<Routes>
<Route path="/category" element={<CategoryPage />}>
<Route path=":categoryId/product/:productId" element={<ProductDetail />} />
</Route>
</Routes>
React 路由传递数据的持久化
想象你去图书馆借书,你在前台登记了要借的书的信息,然后去书架找书。要是你中途有事离开图书馆再回来,还能接着找那本书,就说明借书信息被保留下来了,这就是一种信息的持久化。在 React 里,当你从一个页面跳到另一个页面,把一些数据也带过去,要是在页面刷新、重新访问或者经过多次跳转后,这些数据还能被正确使用,这就叫路由传递数据的持久化。
为啥需要数据持久化
有时候,我们在页面跳转时传递的数据很重要,比如用户登录后的信息、购物车里选好的商品。要是页面一刷新这些数据就没了,用户体验就会很差。就像你去超市选好东西放购物车了,结果一结账发现购物车空了,你得多郁闷呐。所以,让传递的数据持久化能保证用户操作的连贯性和数据的完整性。
实现数据持久化的方法
1. 使用本地存储(Local Storage)
本地存储就像你在图书馆有个自己的小柜子,可以把重要的东西放进去,下次来还能找到。在 React 里,你可以把要传递的数据存到浏览器的本地存储里。
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// 第一个页面,把数据存到本地存储
const PageOne = () => {
const saveDataToLocalStorage = () => {
const data = { message: '这是要传递的数据' };
localStorage.setItem('myData', JSON.stringify(data));
};
return (
<div>
<button onClick={saveDataToLocalStorage}>
<Link to="/page-two">跳转到页面二并保存数据</Link>
</button>
</div>
);
};
// 第二个页面,从本地存储读取数据
const PageTwo = () => {
const storedData = localStorage.getItem('myData');
const data = storedData ? JSON.parse(storedData) : null;
return (
<div>
{data && <p>收到的数据: {data.message}</p>}
</div>
);
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<PageOne />} />
<Route path="/page-two" element={<PageTwo />} />
</Routes>
</Router>
);
};
export default App;
在这个例子里,PageOne
页面把数据存到本地存储,PageTwo
页面从本地存储读取数据。就算页面刷新,数据还在本地存储里,能继续用。
2. 使用会话存储(Session Storage)
会话存储和本地存储有点像,但它更像你在图书馆的临时寄存柜,只要你还在图书馆(浏览器会话没关闭),东西就还在,你一离开图书馆(关闭浏览器窗口),东西就没了。
// 把上面例子里的 localStorage 换成 sessionStorage 就行
const PageOne = () => {
const saveDataToSessionStorage = () => {
const data = { message: '这是要传递的数据' };
sessionStorage.setItem('myData', JSON.stringify(data));
};
return (
<div>
<button onClick={saveDataToSessionStorage}>
<Link to="/page-two">跳转到页面二并保存数据</Link>
</button>
</div>
);
};
const PageTwo = () => {
const storedData = sessionStorage.getItem('myData');
const data = storedData ? JSON.parse(storedData) : null;
return (
<div>
{data && <p>收到的数据: {data.message}</p>}
</div>
);
};
3. 使用状态管理库(如 Redux)
状态管理库就像图书馆的管理员,能统一管理所有的数据。不管你在哪个页面,都能从管理员那里拿到你要的数据。
// 假设已经安装了 redux 和 react-redux
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { Provider, useSelector, useDispatch } from 'react-redux';
import { createStore } from 'redux';
// 定义 reducer
const initialState = { message: '' };
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'SET_MESSAGE':
return { ...state, message: action.payload };
default:
return state;
}
};
// 创建 store
const store = createStore(reducer);
// 第一个页面,把数据存到 store
const PageOne = () => {
const dispatch = useDispatch();
const setMessage = () => {
dispatch({ type: 'SET_MESSAGE', payload: '这是要传递的数据' });
};
return (
<div>
<button onClick={setMessage}>
<Link to="/page-two">跳转到页面二并保存数据</Link>
</button>
</div>
);
};
// 第二个页面,从 store 读取数据
const PageTwo = () => {
const message = useSelector(state => state.message);
return (
<div>
{message && <p>收到的数据: {message}</p>}
</div>
);
};
const App = () => {
return (
<Provider store={store}>
<Router>
<Routes>
<Route path="/" element={<PageOne />} />
<Route path="/page-two" element={<PageTwo />} />
</Routes>
</Router>
</Provider>
);
};
export default App;
在这个例子里,PageOne
页面把数据存到 Redux 的 store 里,PageTwo
页面从 store 里读取数据。就算页面刷新,只要 store 没被清空,数据就能保留。