React学习笔记
React系列笔记学习
上篇笔记地址:【超全】React学习笔记 上:基础使用与脚手架
中篇笔记地址:【超全】React学习笔记 中:进阶语法与原理机制
React路由概念与理解使用
1. 引入
React路由是构建单页面应用(SPA, Single Page Application)中的核心技术之一,它允许用户在不重新加载整个页面的情况下,实现页面的切换。通过React路由,开发者可以为用户提供丰富的页面导航体验,同时保持应用的性能和响应速度。
- 路由的基本使用:
React路由通过定义一组路由规则,将URL与应用中的特定组件关联起来。用户通过点击链接或直接在浏览器中输入URL,可以快速导航到应用的不同部分。
- 路由的执行过程:
了解React路由的执行过程有助于开发者理解路由是如何工作的,以及在遇到问题时如何调试和解决路由相关的问题。
- 编程式导航:
除了通过链接导航,React路由还提供了编程式导航的能力,开发者可以通过代码来控制应用的导航行为,使应用能够根据用户的操作或其他条件动态地导航到不同的页面。
- 默认路由:
默认路由允许开发者为应用定义一个默认的页面,当用户访问的URL与任何已定义的路由规则都不匹配时,应用会自动导航到这个默认页面。
- 匹配模式:
React路由提供了多种匹配模式,使开发者能够灵活地控制路由规则,匹配不同的URL模式,并在URL中捕获参数。
在接下来的学习中,我们将通过实例和代码示例,详细介绍React路由的使用方法和原理,让你能够熟练地利用React路由构建单页面应用。通过掌握React路由,你将能够为用户提供丰富、流畅的页面导航体验,同时保持应用的高性能和良好的可维护性。
2. React路由介绍
现代的前端应用大多都是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系使用
- React路由简单来说,就是配置路径和组件(配对)
3. React路由的基本使用
3.1 BrowserRouter使用步骤
React路由的基本使用非常简单和直观,下面是使用react-router-dom
库来创建基本路由的步骤:
- 安装:
通过 yarn 或 npm 安装 react-router-dom
库:
yarn add react-router-dom
# 或者
npm install react-router-dom
- 导入核心组件:
从 react-router-dom
中导入三个核心组件:BrowserRouter
(也可以重命名为Router
)、Route
和Link
。
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
- 使用
Router
组件包裹整个应用:
Router
组件应当包裹住你的整个应用,确保路由的上下文可用于应用的其他部分。
<Router>
<div className="App">
{/* ...省略页面内容 */}
</div>
</Router>
- 使用
Link
组件作为导航菜单(路由入口):
Link
组件提供了一个简单的方式来创建导航链接,它会渲染为HTML中的<a>
标签。
<Link to="/first">页面一</Link>
- 使用
Route
组件配置路由规则和要展示的组件(路由出口):
Route
组件定义了URL路径与组件之间的映射关系。你可以通过path
prop指定URL路径,通过component
prop指定要渲染的组件。
const First = () => <p>页面一的页面内容</p>;
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
3.2 HashRouter介绍
除了BrowserRouter
,react-router-dom
库还提供了一个HashRouter
组件。HashRouter
和BrowserRouter
的工作方式略有不同,但都能提供基本的路由功能。
HashRouter
使用URL的hash部分(即#
符号后的部分)来保持UI和URL的同步。这种方式对于不能提供服务器端渲染支持的静态文件服务器非常有用,因为它不需要服务器配置即可提供深层链接。
以下是HashRouter
的基本使用方法:
import { HashRouter as Router, Route, Link } from 'react-router-dom';
const First = () => <p>页面一的页面内容</p>;
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
在上述代码中,我们从react-router-dom
导入了HashRouter
而不是BrowserRouter
,并将其重命名为Router
以保持代码的简洁和一致。其他的用法和BrowserRouter
基本相同,只是URL的格式会略有不同,例如/first
将变为#/first
。
4. Route执行过程
React路由的执行过程可以理解为一个按步骤自动触发的序列,从用户点击Link
组件开始,到React路由渲染相应的Route
组件。下面是这个过程的详细解释:
- 点击
Link
组件:
当用户点击Link
组件时,它会修改浏览器地址栏中的URL。例如,如果Link
组件的to
prop是/first
,那么点击该Link
组件会使浏览器地址栏的URL变为/first
。
<Link to="/first">页面一</Link>
- React路由监听URL变化:
React路由库(react-router-dom
)会监听浏览器地址栏中的URL变化。这个监听过程是通过HTML5 History API或者哈希变化实现的,具体取决于你是使用BrowserRouter
还是HashRouter
。
- 遍历所有
Route
组件:
一旦URL发生变化,React路由就会开始遍历所有的Route
组件,检查每个Route
组件的path
prop与当前的URL的pathname部分是否匹配。
<Route path="/first" component={First}></Route>
- 展示匹配的
Route
组件内容:
如果Route
组件的path
prop与当前的URL的pathname部分匹配,React路由就会渲染该Route
组件所指定的component
prop。在这个例子中,如果URL的pathname是/first
,First
组件就会被渲染到页面上。
const First = () => <p>页面一的页面内容</p>;
这个过程保证了当用户通过导航链接(由Link
组件提供)更改URL时,React路由能够正确地渲染与新URL对应的组件。通过这种方式,React路由提供了一个简单而强大的方式来构建单页应用程序(SPA),使开发者能够以一种组织良好、可维护的方式来管理应用的视图和导航逻辑。
5. 编程式导航
在某些场景下,我们可能需要通过编程的方式来控制路由的跳转,例如,在用户登录成功后自动跳转到某个页面。React Router提供了一种编程式导航的方式,使得我们能够在JavaScript代码中控制路由的跳转。
场景说明:
假设我们有一个登录页面,当用户点击登录按钮并且登录成功后,我们想要通过代码将用户重定向到后台首页。
实现编程式导航:
React Router为我们提供了history
对象,它包含了与浏览器历史记录交互的一些方法。我们可以利用history
对象的push
和go
方法来实现编程式导航。
使用history.push
方法:
history.push
方法允许我们导航到一个新的位置,以下是如何在登录成功后使用history.push
方法跳转到后台首页的示例:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class Login extends Component {
handleLogin = () => {
// 假设登录验证逻辑已通过
this.props.history.push('/home');
};
render() {
return (
<div>
<button onClick={this.handleLogin}>登录</button>
</div>
);
}
}
export default withRouter(Login);
在上述代码中:
- 我们首先从
react-router-dom
导入了withRouter
高阶组件,它会将history
对象作为prop传递给Login
组件。 - 在
handleLogin
方法中,我们调用了this.props.history.push('/home')
来导航到/home
路径。 - 用户点击登录按钮后,
handleLogin
方法被调用,并将用户导航到后台首页。
使用history.go
方法:
history.go
方法允许我们在浏览器历史记录中前进或后退。参数n
表示前进或后退的页面数量。例如,n
为-1表示后退到上一页:
goBack = () => {
this.props.history.go(-1);
};
在上述方法中,我们调用了this.props.history.go(-1)
来后退到上一页。类似地,我们可以使用n
为1来前进到下一页。
通过这种方式,React Router的history
对象为我们提供了强大而灵活的编程式导航功能,使我们能够在代码中精确控制路由的跳转。
6. 默认路由
在React Router中,有时我们可能想要为未匹配到的路径提供一个默认的页面,通常这个页面是一个“404 Not Found”页面,用来告诉用户他们访问了一个不存在的页面。为了实现这个功能,我们可以使用<Route>
组件和<Switch>
组件来配置一个默认的路由。
使用<Switch>
和<Route>
实现默认路由:
<Switch>
组件是用来包裹一组<Route>
组件的,它会从上到下匹配其中的<Route>
,一旦找到一个匹配的<Route>
,它就会渲染这个<Route>
并忽略其它的<Route>
。如果所有的<Route>
都没有匹配到,我们可以在<Switch>
的最后放置一个没有path
属性的<Route>
作为默认路由。
以下是一个实现默认路由的示例:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const HomePage = () => <div>Home Page</div>;
const AboutPage = () => <div>About Page</div>;
const NotFoundPage = () => <div>404 Not Found</div>;
const App = () => {
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/about" component={AboutPage} />
{/* 默认路由 */}
<Route component={NotFoundPage} />
</Switch>
</Router>
);
};
export default App;
在上述代码中:
- 我们首先导入了所需的
Router
,Route
和Switch
组件。 - 在
<Switch>
组件中,我们定义了几个具有特定path
属性的<Route>
组件。 - 在
<Switch>
组件的最后,我们添加了一个没有path
属性的<Route>
组件,并指定了NotFoundPage
组件作为它的component
属性值。这样,当用户访问一个未定义的路由时,NotFoundPage
组件会被渲染,从而展示一个“404 Not Found”页面。
通过这种方式,我们可以为React应用配置一个简单而有效的默认路由,以处理未匹配到的路径。
7. 匹配模式
React Router的匹配模式可以分为模糊匹配和精确匹配。模糊匹配是默认的匹配模式,它允许你在路径中定义一种模式,并且任何以该模式开头的路径都会被匹配。这可能会导致多个<Route>
组件被匹配和渲染。为了避免这种情况,你可能会想使用精确匹配模式。
7.1 模糊匹配模式
在模糊匹配模式下,如果<Route>
组件的path
属性值是当前URL路径的前缀,那么该<Route>
组件就会被匹配。这种匹配模式允许你创建嵌套路由,但是也可能会导致不期望的路由匹配。
例子:
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Home = () => <div>Home Page</div>;
const Login = () => <div>Login Page</div>;
const App = () => {
return (
<Router>
<div>
<Link to="/login">Login</Link>
{/* 模糊匹配 */}
<Route path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
);
};
export default App;
在上面的例子中,当你点击"Login"链接时,你会发现Home
组件和Login
组件都被渲染了。这是因为/
是/login
的前缀,所以<Route path="/" component={Home} />
被匹配了。
这种模式可能不是你想要的,特别是当你有一个默认路由时。为了解决这个问题,你可以使用精确匹配模式。
7.2 精确匹配模式
通过为<Route>
组件添加exact
属性,你可以启用精确匹配模式。在这种模式下,只有当path和pathname完全匹配时,<Route>
组件才会被匹配。
例子:
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Home = () => <div>Home Page</div>;
const Login = () => <div>Login Page</div>;
const App = () => {
return (
<Router>
<div>
<Link to="/login">Login</Link>
{/* 精确匹配 */}
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
);
};
export default App;
在上述代码中,我们通过添加exact
属性到<Route path="/" component={Home} />
,确保只有当路径完全是/
时,Home
组件才会被渲染。现在,当你点击"Login"链接时,只有Login
组件会被渲染。
通过精确匹配模式,你可以避免不期望的路由匹配,特别是当你有多层路由结构时。所以,我们通常会推荐给默认路由添加exact属性。
8.React路由函数的使用
抽象化路由:路由表的实现
路由表提供了一个集中的方式来定义应用中所有的路由路径和它们应该如何响应。在React中,使用路由表可以使应用结构更加清晰。
- 创建路由函数
这些函数允许你为你的应用创建自定义路由。
- createBrowserRouter: 创建一个使用HTML5
history
API的路由器。 - createMemoryRouter: 创建一个保持路由信息在内存中的路由器,通常用于测试和非浏览器环境如React Native。
- createStaticRouter: 用于在Node环境中渲染静态页面,例如服务器端渲染。
- createHashRouter: 创建一个哈希Api的路由器.
在创建好路由表之后,我们可以使用RouterProvider
将路由表注入我们的APP内
import {createBrowserRouter, RouterProvider} from "react-router-dom"
const router = createBrowserRouter(
[
{
path: "/login",
element: <div>登录页</div>
},
{
path: "/admin",
element: <div>管理页</div>
}
])
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<RouterProvider router={router}></RouterProvider>
)
路由导航跳转
路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信。
声明式导航
声明式导航是指通过在模版中通过**<Link/>
组件描述出要跳转到哪里去**,比如后台管理系统的左侧菜单通常使用这种方式进行。
语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可。
<Link to"/article">文章</Link>
编程式导航:命令化导航
编程式导航是指通过useNavigate
钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活。
import {useNavigate} from "react-router-dom"
const App() =>
{
const navigate = useNavigate();
return (
<div>
我是登录页
<button onClick={()=>navigate("/article")}>跳转文章</button>
</div>
)
}
语法说明:通过调用navigate方法传入地址path实现跳转.
路由导航跳转传参
searchParams
传参
我们在navigate
函数里,在传入路由的路径时可以直接写入参数也可以直接传入参数,并使用searchParams
就可以进行使用了。
// 直接声明参数
const id = 1001
const name = 'user'
navigate(`/artical?id=${id}&name=${name}")
在searchParams
里使用传入的参数,使用get方法获取
const [params] = searchParams()
const id = params.get("id")
useParams
传参
同样,在navigate函数内,我们还可以使用useParams
进行接收参数,接受到的参数是一个参数对象
const params = useParams()
const id = params.id
但是在使用useParams
传参之前,我们需要修改路由表的配置,我们需要用占位符来让路由器知道我们这个路由将要传入对应的参数。
const router = createBrowserRouter(
[
{
path: "/login",
element: <div>登录页</div>
},
{
path: "/admin",
element: <div>管理页</div>
},
{
path: "/article/:id",
element: <div>文章页</div>
}
])
useLocation
传参
在useLocation
传参过程中可以不需要路由表的配合,但是我们需要在<Link /
中传入state参数。并在对应的路由组件内获取参数。
<Link to="/home" state={{id:id, title:title, content:content}}>我是Link</Link>
在对应的路由组件内获取对应的参数
const Home() =>
{
const {state:{id, title, content}} = useLocation();
return (
<div>
<p>{id}</p>
<p>{title}</p>
<p>{content}</p>
</div>
)
}
嵌套路由实现
实现步骤:
-
使用children属性配置路由嵌套关系
-
使用
<Outletl>
组件配置二级路由渲染位置
首先,我们需要在路由表内声明当前路由下存在子路由,并声明路由组件
const router = createBrowserRouter(
[
{
path: "/",
element: <div>我是主页</div>
children: [ // 使用children值来声明子路由
{
path: "home",
element: <div>我是主页的home</div>
},
{
path: "about",
element: <About />
}
]
}
{
path: "/login",
element: <div>登录页</div>
},
{
path: "/admin",
element: <div>管理页</div>
},
])
在路由组件之下,我们要使用<Outletl>
表示二级路由的渲染位置
const layout() => {
return (
<div>
<div>我是layout</div>
<Link to="/home">主页</Link>
<Link to="/about">关于页</Link>
{/*二级路由渲染位置*/}
<Outlet />
</div>
)
}
嵌套路由的默认二级路由的配置
当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true。
比如,我们想进入/
路由之后,自动进入他的二级路由里的home
路由内,这个时候我们只需要把他的path去掉,设置index属性为true即可。
const router = createBrowserRouter(
[
{
path: "/",
element: <div>我是主页</div>
children: [ // 使用children值来声明子路由
{
index: true,
element: <div>我是主页的home</div>
},
{
path: "about",
element: <About></About>
}
]
}
{
path: "/login",
element: <div>登录页</div>
},
{
path: "/admin",
element: <div>管理页</div>
},
])
404路由导航的实现
404路由
场景∶当浏览器输入url的路径在整个路由配置中都找不到对应的path,为了用户体验,可以使用404兜底组件进行渲染
实现步骤:
-
准备一个NotFound组件;
-
在路由表数组的末尾,以*号作为路由path配置路由。
const router = createBrowserRouter(
[
{
path: "/",
element: <div>我是主页</div>
children: [ // 使用children值来声明子路由
{
index: true,
element: <div>我是主页的home</div>
},
{
path: "about",
element: <About></About>
}
]
}
{
path: "/login",
element: <div>登录页</div>
},
{
path: "/admin",
element: <div>管理页</div>
},
{
path: "*",
element: <NotFonud />
}
])
9. React路由总结
React路由是React应用中处理路由(即页面导航)的核心机制,它允许我们构建单页应用(SPA),在单个页面中展现多个视图,而无需重新加载整个页面。以下是对前面讨论内容的简单总结,以及对React路由基础的进一步阐述:
-
单页应用(SPA)与React路由:
- React路由通过管理多个视图(组件)来实现SPA的效果,它提供了一种方式来定义不同的URL路径与不同的React组件之间的映射关系。
-
核心组件:
- Router: 该组件负责包裹整个应用,创建一个路由上下文,使得其他路由相关的组件可以正确地工作。通常情况下,
<Router>
组件只需要在应用的最外层使用一次。 - Link: 该组件允许用户通过点击链接来导航到不同的页面。它是路由的入口。
- Route: 该组件负责根据当前的URL路径来决定是否渲染对应的组件。它是路由的出口。
- Router: 该组件负责包裹整个应用,创建一个路由上下文,使得其他路由相关的组件可以正确地工作。通常情况下,
-
编程式导航:
- 通过
props.history
对象,我们可以在JavaScript代码中直接控制路由的行为,例如导航到一个新页面。
- 通过
-
匹配模式:
- 默认情况下,React路由使用模糊匹配模式,只要
pathname
以path
开头,就认为匹配成功。这种模式在某些情况下可能会导致意外的匹配结果。 - 为了更精确地控制匹配行为,可以通过给
<Route>
组件添加exact
属性来启用精确匹配模式。在精确匹配模式下,只有当pathname
和path
完全相等时,才认为匹配成功。
- 默认情况下,React路由使用模糊匹配模式,只要
-
组件化的路由:
- React路由的设计理念是“一切皆组件”,即路由本身也是通过组件来实现的。这种设计使得React路由非常灵活和易于理解,你可以像思考React组件一样来思考React路由。
通过理解和应用以上几点,你可以构建出具有良好导航结构和用户体验的React应用。同时,React路由的组件化设计也使得路由的扩展和自定义变得非常简单和直观。
Redux的概述与使用
Redux的概述
1.1 什么是 Redux
Redux 是一个用于JavaScript的状态容器,它提供可预测化的状态管理能力。通过Redux,你可以构建出具有一致性的应用,这些应用可以运行在不同的环境(如客户端、服务器、原生应用)中,并且非常容易进行测试。Redux不仅限于React,也可以与其他UI库(如Angular、Vue等)一起使用。Redux与React没有直接关联,如同Java与JavaScript并不是一个同门语言一般,但是Redux最多的使用场景就是与React共同合作管理全局组件的状态。
1.2 Redux的设计初衷
随着JavaScript单页面应用(SPA)的开发变得日益复杂,需要管理更多的state,包括但不限于服务器响应、缓存数据、本地生成尚未持久化到服务器的数据以及UI状态。如果不妥善管理,这些状态可能会相互影响,导致应用的行为难以预测。例如,一个模型(model)的变化可能会影响到另一个模型,而视图(view)的变化可能会进一步影响到模型,从而可能引发另一个视图的变化。Redux的出现,旨在通过引入严格的状态管理规则,解决这种复杂状态间的依赖和影响问题,使得状态的变化变得可预测和可控。
1.3 Redux的三大核心原则
-
单一数据源:
- 整个应用的 state 被储存在一颗 object tree 中,这个 object tree 只存在于唯一一个 store 中。这样做不仅使得应用的状态结构变得清晰、一目了然,同时也便于调试和状态的持久化。
- (在这里,你可以使用Mermaid流程图来绘制和解释React中每个组件如何维护自己的状态,以及如何将整个应用的状态储存在一个唯一的store中的object tree。)
-
State 是只读的:
- 唯一改变 state 的方式是通过触发 action。action 是一个描述事件的对象,它通常包含一个
type
属性来指示这个 action 的类型,以及其他一些数据。 - 这样做确保了视图和网络请求不能直接修改state,而只能通过分发(dispatch)action来表达修改意图。所有的修改都被集中处理,并且严格按照一个接一个的顺序执行。
store.dispatch({ type: 'COMPLETE_TODO', index: 1 });
- 唯一改变 state 的方式是通过触发 action。action 是一个描述事件的对象,它通常包含一个
-
使用纯函数来执行修改:
- Reducers 是用于描述如何根据 action 更新 state 的纯函数。它们接收先前的 state 和一个 action,然后返回新的 state。
- Reducers 的纯函数特性使得它们易于测试、易于组合和重用。同时,它们也可以被用于实现时间旅行调试、状态持久化等高级功能。
1.4 Redux概述总结:
- Redux 是一个 JavaScript 状态容器库:
- 可预测: 通过单一数据源和纯函数的使用,Redux使得状态的变化变得可预测和一致。每次发出一个动作(action),都会通过一个纯函数(reducer)来更新状态,从而确保相同的输入始终会得到相同的输出。
- 一致: 不管是在客户端、服务器还是原生环境中,Redux的行为都是一致的,使得它非常适合构建跨平台的应用。
- 透明: 通过Redux DevTools,开发者可以实时地观察到状态的变化,以及每一个动作如何影响应用的状态。这种透明度使得调试和理解应用的行为变得更为简单。
- Redux 的三大核心原则:
- 单一数据源:
- 所有的状态都存储在一个JavaScript对象中,这使得它更容易管理,也更容易调试和检查。这个对象也是序列化的,所以可以方便地持久化到本地存储或服务器。
- 单一的状态树也使得实现如时间旅行、状态持久化或者服务器渲染等功能变得可能和相对容易。
- State 是只读的:
- 这个原则保证了状态的不可变性,即状态是不可以直接被修改的,唯一改变状态的方式是通过触发action。
- 这种方式提供了一种清晰、一致的方法来修改状态,并且也使得状态的变化变得可追踪和可预测。
- 使用纯函数来执行修改:
- Reducers是纯函数,它们不会产生任何副作用,也不依赖外部的状态或变量。它们只依赖于传入的参数,并且总是会返回一个新的状态对象。
- 这种纯函数的特性使得它们非常容易测试,并且也可以通过组合不同的reducers来构建复杂的应用逻辑。
- 单一数据源:
通过这些核心原则,Redux提供了一个可靠且强大的状态管理解决方案,它可以帮助开发者构建复杂、可维护和可扩展的应用。
你已经很好地概述了 Redux 的组成部分和它们的功能。让我们为每个部分提供更多细节和清晰度。
Redux组成
2.1 State - 状态:
State 是应用的状态树,它包含了应用的数据和UI状态。在React项目中,通常可以将 State 分为三类:
- Domain Data: 通常来自服务器端的数据,例如用户信息、商品列表等。
- UI State: 决定当前UI展示的状态,例如弹框的显示隐藏、受控组件的状态等。
- App State: App级别的状态,例如当前是否显示加载中、当前路由信息等,通常这些状态可能会被多个组件共享和使用。
2.2 Action - 事件:
Actions是发送数据到store的载体。它们是描述“发生了什么”的对象,通常通过store.dispatch()
方法发送到store。
-
Action特点:
- Action 本质上是一个 JavaScript 对象。
- 必须包含一个
type
属性来表示要执行的动作,多数情况下,type
会被定义成字符串常量。 - 除
type
字段外,action 对象的结构是自由的,可以根据需要添加其他属性。 - Action 创建函数是创建 action 的函数,它描述了有事情要发生,但并没有描述如何更新 state。
-
基本结构: 一个Action是一个简单的JavaScript对象,它至少包含一个
type
属性,通常还包含一些数据。{ type: 'ADD_TODO', text: 'Learn Redux' }
-
Action创建函数: 这些函数返回actions,它们是创建action的唯一方式,使得代码更清晰,也更容易测试。
function addTodo(text) { return { type: 'ADD_TODO', text }; }
序列图详解:
在这个序列图中:
- 组件 (
Component
) 调用store.dispatch(action)
方法来发送一个Action
到Store
。 Store
调用Reducer
,传递当前的State
和Action
作为参数。Reducer
返回一个新的State
到Store
。Store
更新State
,然后重新渲染相关的组件。
这个流程图简明地展示了在Redux中,如何通过store.dispatch()
方法发送一个Action,并通过Reducer来更新State,最终达到更新应用UI的目的。
2.3 Reducer:
Reducer 是一个函数,它响应发送过来的 actions,并返回新的 state。
Reducers指定了如何根据actions更新state。它们是纯函数,接收先前的state和一个action,并返回新的state。
基本结构:
function todoApp(state = initialState, action) {
switch (action.type) {
case 'ADD_TODO':
// ...省略
default:
return state;
}
}
- Reducer特点:
- Reducer 函数接收两个参数: 第一个参数是当前的 state,第二个参数是 action。
- 必须返回一个新的 state,或者在没有变化时返回原始的 state。
- Reducer 是纯函数,不应产生任何副作用。
- 分割Reducers: 为了管理大型应用的复杂状态,通常将一个大的reducer分割成多个小的reducers,每个管理state的一部分,然后使用
combineReducers
将它们组合在一起。
序列图详解
在这个序列图中:
组件 (Component)
通过调用store.dispatch(action)
方法发送一个动作 (Action)
到存储库 (Store)
,其中 Action 描述了“发生了什么”。存储库 (Store)
随后调用Reducer
,传递当前的状态 (State)
和动作 (Action)
作为参数。Reducer 是一个纯函数,它根据接收到的状态 (State)
和动作 (Action)
计算并返回一个新的状态 (State)
。Reducer
返回新的状态 (State)
到存储库 (Store)
,随后存储库 (Store)
更新状态 (State)
。- 最终,
存储库 (Store)
更新了状态 (State)
后,通知组件 (Component)
重新渲染,以反映状态的变化。
2.4 Store:
Store 是 Redux 的核心,它将 action 和 reducer 联系到一起,同时维护应用的 state。
- Store特点:
- 提供
getState()
方法获取 state。 - 提供
dispatch()
方法发送 action。 - 提供
subscribe()
方法注册监听器,监听 state 的变化。 subscribe()
返回一个函数,用于注销监听器。- 通过
createStore()
方法创建 store,传递 reducer 作为参数。
- 提供
import { createStore } from 'redux';
import todoApp from './reducers';
const store = createStore(todoApp);
2.5 Redux组成总结:
- State: 应用的数据和UI状态,可以分为 Domain Data, UI State, 和 App State.
- Action: 描述事件的对象,是改变 state 的唯一方式,但不直接修改state,主要用来描述即将要发生什么。
- Reducer: 纯函数,根据 action 更新并返回新的 state。
- Store: Redux的核心,连接 action 和 reducer,同时维护应用的 state,提供了一些方法来操作和监听 state。
通过理解 Redux 的这些组成部分以及它们的作用和交互方式,你将能够更好地理解和使用 Redux 来管理你的应用状态。
Redux快速安装开始
创建一个 React + Redux 项目涉及多个步骤。以下是从安装 Redux 到创建 Redux 文件夹的详细步骤。
1. 创建一个新的React项目:
首先,你需要创建一个新的React项目(如果你还没有一个的话)。
npx create-react-app my-redux-app
cd my-redux-app
2. 安装 Redux 和 React-Redux:
你需要安装 Redux 和 React-Redux,React-Redux 是连接 React 和 Redux 的官方库。
npm install redux react-redux
3. 创建 Redux 文件夹:
在你的项目根目录下,创建一个名为 redux
的文件夹。这个文件夹将包含你所有的 Redux 相关代码(例如,reducers, actions, 和 middleware)。
mkdir src/redux
cd src/redux
4. 创建 Action 和 Reducer 文件:
-
创建Action文件夹
-
在
redux
文件夹中创建一个名为actions
的文件夹,并在该文件夹中创建你的 action 文件。例如,你可以创建一个名为index.js
的文件来存储你的 actions。mkdir actions touch actions/index.js
-
在actions文件里的代码文件里创建一个action函数,里面返回一个action对象。注意,action对象必须要有type属性
const sendAction () => { // 返回一个action对象 return { type : "send_action_type", value: "发送了一个action" } }
-
把这个action创建的函数导出
module.exports = { sendAction }
-
-
Reducers: 在
redux
文件夹中创建一个名为reducers
的文件夹,并在该文件夹中创建你的 reducer 文件。例如,你可以创建一个名为index.js
的文件来组合和导出你的 reducers。mkdir reducers touch reducers/index.js
- 创建一个reducer函数,注意reducer需要接受两个参数:state和action。
const rootReducer = (state, action) => { // to do somethings. }
- 第一个参数是默认状态,我们可以定义一个初始化的state,然后进行赋值
const initState = {value : "default"} const rootReducer = (state = initState, action) => { // to do somethings. }
-
在函数里面判断第二个参数action的type值是否是我们发送的
-
如果是的话,我们可以通过return返回新的state。
const initState = {value : "default"} const rootReducer = (state = initState, action) => { switch (action) { case send_type: return Object.assign({/*new state object*/}, state, action); default: return state } }
- 把reducer进行导出
const initState = {value : "default"} const rootReducer = (state = initState, action) => { switch (action) { case "send_type": return Object.assign({/*new state object*/}, state, action); default: return state } } module.exports = { rootReducer }
5. 创建 Store:
在 redux
文件夹中创建一个名为 store.js
的文件,用于创建和导出你的 Redux store。
touch store.js
在 store.js
文件中,你将导入 createStore
函数和你的 root reducer,然后使用它们来创建你的 store。
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(
rootReducer
);
export default store;
6. 连接 Redux 到你的 React 应用:
- 给页面的button按钮绑定一个点击事件
- 在组件一加载完毕的时候我们通过store来进行监听器的注册,返回值可以用来注销监听
this.unSubbscribe = store.subscribe(() => {...} );
- 在点击事件处理函数中,通过store.dispatch来发送一个action.
handleClick = () => {
store.dispath(sendAction())
}
React-redux的使用与入门
React-Redux 简介
React-Redux 是 Redux 的官方 React 绑定库,它允许你轻松地在 React 应用中集成和使用 Redux。通过 React-Redux,你可以让你的 React 组件连接到 Redux Store,共享和管理全局状态。React-Redux 提供了一些 API 和工具来简化在 React 中使用 Redux 的过程。
下面是 React-Redux 的一些主要特点和组成部分:
- Provider 组件:
Provider
是一个 React 组件,它使得 Redux Store 可以在你的 React 应用中的任何地方被访问。你只需将Provider
组件包裹在你的应用的根组件周围,并将你的 Redux Store 传递给它。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
- connect 函数:
connect
函数是一个高阶函数,它返回一个新的连接到 Redux Store 的 React 组件。它接受两个参数mapStateToProps
和mapDispatchToProps
,分别用于将 Redux Store 中的状态和 dispatch 方法映射到你的 React 组件的 props。
import { connect } from 'react-redux';
const mapStateToProps = state => ({
// map state to props
});
const mapDispatchToProps = dispatch => ({
// map dispatch to props
});
export default connect(mapStateToProps, mapDispatchToProps)(YourComponent);
- useDispatch 和 useSelector 钩子:
- 在函数组件中,你可以使用
useDispatch
和useSelector
钩子来分别获取 dispatch 函数和选择 Redux Store 中的状态。
- 在函数组件中,你可以使用
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
function YourComponent() {
const dispatch = useDispatch();
const state = useSelector(state => state);
// ...
}
- 创建切片 (Slice):
- React-Redux 鼓励使用切片 (Slice) 模式来组织你的 actions 和 reducers。一个切片包含了它自己的 actions、reducers 和初始状态,使得代码更加模块化和易于管理。
React-Redux 不仅为你提供了一种在 React 中使用 Redux 的简单方法,还提供了性能优化和其他实用功能,以确保你的应用运行得更加顺畅。通过使用 React-Redux,你可以构建出结构清晰、可维护和可扩展的 React 应用。
React-redux基本使用
React-Redux 是官方提供的 Redux 和 React 的绑定库。它使你能够将 Redux 的状态和 dispatch 方法映射到 React 组件的 props 中。使用 React-Redux,你可以轻松地在 React 应用中集成 Redux。
下面是 React-Redux 的基本使用步骤:
- 安装依赖: 在开始之前,确保你已经安装了
react-redux
和redux
。
npm install redux react-redux
- 文件架构:为了更好管理redux项目内容,通常会在src文件夹下的创建一个redux文件夹,里面分别存放store\action\reducer的代码进行存放。
编写React-redux代码
- 创建 Redux Actions: Actions 是描述发生了什么的对象,也就是描述事件对象,它们通常通过
dispatch
函数发送到 store。
// src/redux/actions.js
export const increment = () => ({
type: 'INCREMENT'
});
export const decrement = () => ({
type: 'DECREMENT'
});
- 创建 Redux Reducers: Reducers 是纯函数,它们接受先前的状态和动作,并返回新的状态。
// src/redux/reducers/index.js
import { combineReducers } from 'redux';
const initialState = {
count: 0
};
function 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;
}
}
const rootReducer = combineReducers({
counter: counterReducer
});
export default rootReducer;
- 创建 Redux Store: 首先,你需要创建一个 Redux Store 来保存应用的状态。
// src/redux/store.js
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
- 在 React 组件中使用 React-Redux: 现在,你可以在 React 组件中使用
connect
函数将 Redux 的 state 和 dispatch 方法映射到组件的 props 中。
// src/components/Counter.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../redux/actions';
function Counter({ count, increment, decrement }) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
const mapStateToProps = state => ({
count: state.counter.count
});
const mapDispatchToProps = {
increment,
decrement
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
- 使用
Provider
包装应用: 最后,使用react-redux
的Provider
组件将你的应用包装在 Redux store 之中。Provider包裹下的所有react组件都会被传递同一个store,以此进行组件数据对象的使用。
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import Counter from './components/Counter';
ReactDOM.render(
<Provider store={store}>
<Counter />
</Provider>,
document.getElementById('root')
);
通过以上步骤,你应该已经成功地在你的 React 应用中集成了 Redux 和 React-Redux。现在,你可以在组件中通过 props 访问 Redux 的状态和 dispatch 方法,并能够轻松地管理和更新应用的状态。
react-toolkit的介绍与基本使用
React Toolkit (RTK) 是官方推荐的用于构建 Redux 应用程序的标准方法。它旨在简化 Redux 代码的编写和维护,并提供了许多有用的实用程序和中间件,以帮助开发人员更快更容易地构建复杂的应用程序。简单来说,React Toolkit是一套工具的集合,意在简化在React中编写redux逻辑的过程,通过一个函数传入对应的参数使得RTK帮你完成生成action和action type,也简化store创建的过程,以此实现简化书写方式。
安装: 你可以通过 npm 或 yarn 安装 React Toolkit。
npm install @reduxjs/toolkit react-redux # 需要安装好react-redux
# 或 yarn管理包工具
yarn add @reduxjs/toolkit react-redux # 需要安装好react-redux
React-toolkit基本使用代码
使用React-Toolkit能够减少我们大量的逻辑编码的过程。
- 例如,我们要使用react toolkit创建一个计数器,我们可以这样配置和创建我们的counterStore
counterStore.js
import { createSlice } import "@reduxjs/toolkit"
const counterStore = createSlice({
name : "counter",
// 初始化状态数据
initialState: {
count: 0
}
// 修改数据的同步方法
reducers: {
inscrement(state){
state.count++;
},
decrement(state){
state.count--;
}
}
})
// 解构出创建的action对象函数 {actionCreater}
const {inscrement, decrement} = counterStore.actions
// 获取reducer函数
const counterReducer = counterStore.reducer
// 导出创建action对象的函数和reducer函数
export {inscrement, decrement}
export default counterReducer
- 使用对象:src/store/index.js
import { configureStore } from "@reduxjs/toolkit"
// 导入子模块reducer
import counterReducer from "./modules/counterStore"
// 创建根store组合的子模块
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
- 为React注入store
react-redux负责把Redux和React链接起来,内置Provider组件通过store参数把创建好的store实例注入到应用中,链接正式建立。
App.js
import { Provider } from "react-redux"
import store from "./store"
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<Provider store={store}>
<App />
<Provider>
)
- React组件使用store中的数据
在React组件中使用store中的数据,需要用到一个钩子函数: useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:
const {count} = useSelector(state => state.counter)
这里的state.counter
是来自于src/store/index.js下的部分
const store = configureStore({
reducer: {
counter: counterReducer // state.counter来自于这里
}
})
pages/counter.js
import {useSelector} from "react-redux"
function App(){
const {count} = useSelector(state => state.counter)
return (
<div className="App">
{count}
</div>
)
}
export default App
- React组件修改store中的数据
React组件中修改store中的数据需要借助另外一个hook函数– useDispatch,它的作用是生成提交action对象的dispatch函数,使用样例如下:
pages/counter.js
import {useSelector} from "react-redux"
// 导入之前创建的actionCreator
import {inscrement, decrement} from "./store/modules/counterStore"
function App(){
const {count} = useSelector(state => state.counter)
const dispath = useDispatch()
return (
<div className="App">
<button onClick={()=> dispatch(decreament())}>-</button>
{count}
<button onClick={()=> dispatch(inscrement())}>+</button>
</div>
)
}
export default App
总结:
- 组件中使用哪个hook函数获取store中的数据?
useSelector
- 组件中使用哪个hook函数获取dispatch方法?
useDispatch
- 如何得到要提交action对象?
执行store模块中导出的actionCreater方法
React-toolkit异步状态处理
在RTK里的异步状态处理的逻辑一般如下:
创建store的写法保持不变,配置好同步修改状态的方法
单独封装一个函数,在函数内部return一个新函数,在新函数中
- 封装异步请求获取数据
- 调用同步actionCreator传入异步数据生成一个action对象,并使用dispatch提交
组件中dispatch的写法保持不变。
完整编码:
store/modules/channelList.js
import {createSlice} from "@reduxjs/toolkit"
// 步骤1: 创建store的写法保持不变,配置好同步修改状态的方法
createSlice({
name: "channel",
initialState: {
channelList: []
},
reducers: {
setChannels(state, action)
{
state.channelList = action.payload
}
}
})
// 异步请求
const {setChannels} = channelStore.actions
// 步骤2: 单独封装一个函数,在函数内部return一个新函数
const fetchChannelList = () =>
{
return (dispatch) => { // 在新函数中
// 封装异步请求获取数据
const resultList = [
{id:1, name:"caixy"},
{id:2, name:"caixypromise"},
{id:3, name:"CAIXYPROMISE"}
]
// 调用同步actionCreator传入异步数据生成一个action对象,并使用dispatch提交
dispatch(setChannels(resultList))
}
}
export {fetchChannelList}
const reducer = channelStore.reducer
export default reducer;
src/App.js
import {useSelector, useDispatch} from "react-redux"
function App() {
const {channelList} = useSelector(state => state.channel)
const dispatch = useDispatch()
//使用useEffect触发异步执行
useEffect(() => {
dispatch(fetchChannelList())
}, [dispatch])// 利用dispatch不可变的规则,保证组件第一次渲染时执行。避免了每次组件渲染时都发送请求
return (
<div className="App">
<ul>
{channelList.map(
item => <li key={item.id}>{item.name}</li>
)}
</ul>
</div>
)
}
在这段代码中,useEffect
的使用是为了确保 dispatch(fetchChannelList())
只在组件第一次渲染时执行,而不是在每次渲染时执行。如果不使用 useEffect
,dispatch(fetchChannelList())
会在每次组件渲染时执行,可能会导致不必要的重复请求和渲染。
dispatch
是 Redux 提供的一个函数,它通常是不可变的。在 useEffect
的依赖项数组中包含 dispatch
,能确保 useEffect
中的代码只在组件第一次渲染时执行,而不是在每次渲染时执行。这样做可以减少不必要的请求和操作,提高应用的性能。
通过设置 useEffect
的依赖项数组为 [dispatch]
,你告诉 React 只有当 dispatch
函数发生变化时才重新执行 useEffect
中的代码。但由于 dispatch
函数通常不会变化,所以实际上 useEffect
中的代码只会在组件第一次渲染时执行。这种做法避免了每次组件渲染时都发送请求,节省了网络资源,提高了应用的性能。
r.net/gh/CaixyPromise/Blog_Image@main/img/202310241623879.png" alt=“image-20231024162250503” style=“zoom:80%;” />
- 例如,我们要使用react toolkit创建一个计数器,我们可以这样配置和创建我们的counterStore
counterStore.js
import { createSlice } import "@reduxjs/toolkit"
const counterStore = createSlice({
name : "counter",
// 初始化状态数据
initialState: {
count: 0
}
// 修改数据的同步方法
reducers: {
inscrement(state){
state.count++;
},
decrement(state){
state.count--;
}
}
})
// 解构出创建的action对象函数 {actionCreater}
const {inscrement, decrement} = counterStore.actions
// 获取reducer函数
const counterReducer = counterStore.reducer
// 导出创建action对象的函数和reducer函数
export {inscrement, decrement}
export default counterReducer
- 使用对象:src/store/index.js
import { configureStore } from "@reduxjs/toolkit"
// 导入子模块reducer
import counterReducer from "./modules/counterStore"
// 创建根store组合的子模块
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
- 为React注入store
react-redux负责把Redux和React链接起来,内置Provider组件通过store参数把创建好的store实例注入到应用中,链接正式建立。
App.js
import { Provider } from "react-redux"
import store from "./store"
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<Provider store={store}>
<App />
<Provider>
)
- React组件使用store中的数据
在React组件中使用store中的数据,需要用到一个钩子函数: useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:
const {count} = useSelector(state => state.counter)
这里的state.counter
是来自于src/store/index.js下的部分
const store = configureStore({
reducer: {
counter: counterReducer // state.counter来自于这里
}
})
pages/counter.js
import {useSelector} from "react-redux"
function App(){
const {count} = useSelector(state => state.counter)
return (
<div className="App">
{count}
</div>
)
}
export default App
- React组件修改store中的数据
React组件中修改store中的数据需要借助另外一个hook函数– useDispatch,它的作用是生成提交action对象的dispatch函数,使用样例如下:
pages/counter.js
import {useSelector} from "react-redux"
// 导入之前创建的actionCreator
import {inscrement, decrement} from "./store/modules/counterStore"
function App(){
const {count} = useSelector(state => state.counter)
const dispath = useDispatch()
return (
<div className="App">
<button onClick={()=> dispatch(decreament())}>-</button>
{count}
<button onClick={()=> dispatch(inscrement())}>+</button>
</div>
)
}
export default App
总结:
- 组件中使用哪个hook函数获取store中的数据?
useSelector
- 组件中使用哪个hook函数获取dispatch方法?
useDispatch
- 如何得到要提交action对象?
执行store模块中导出的actionCreater方法
React-toolkit异步状态处理
在RTK里的异步状态处理的逻辑一般如下:
创建store的写法保持不变,配置好同步修改状态的方法
单独封装一个函数,在函数内部return一个新函数,在新函数中
- 封装异步请求获取数据
- 调用同步actionCreator传入异步数据生成一个action对象,并使用dispatch提交
组件中dispatch的写法保持不变。
完整编码:
store/modules/channelList.js
import {createSlice} from "@reduxjs/toolkit"
// 步骤1: 创建store的写法保持不变,配置好同步修改状态的方法
createSlice({
name: "channel",
initialState: {
channelList: []
},
reducers: {
setChannels(state, action)
{
state.channelList = action.payload
}
}
})
// 异步请求
const {setChannels} = channelStore.actions
// 步骤2: 单独封装一个函数,在函数内部return一个新函数
const fetchChannelList = () =>
{
return (dispatch) => { // 在新函数中
// 封装异步请求获取数据
const resultList = [
{id:1, name:"caixy"},
{id:2, name:"caixypromise"},
{id:3, name:"CAIXYPROMISE"}
]
// 调用同步actionCreator传入异步数据生成一个action对象,并使用dispatch提交
dispatch(setChannels(resultList))
}
}
export {fetchChannelList}
const reducer = channelStore.reducer
export default reducer;
src/App.js
import {useSelector, useDispatch} from "react-redux"
function App() {
const {channelList} = useSelector(state => state.channel)
const dispatch = useDispatch()
//使用useEffect触发异步执行
useEffect(() => {
dispatch(fetchChannelList())
}, [dispatch])// 利用dispatch不可变的规则,保证组件第一次渲染时执行。避免了每次组件渲染时都发送请求
return (
<div className="App">
<ul>
{channelList.map(
item => <li key={item.id}>{item.name}</li>
)}
</ul>
</div>
)
}
在这段代码中,useEffect
的使用是为了确保 dispatch(fetchChannelList())
只在组件第一次渲染时执行,而不是在每次渲染时执行。如果不使用 useEffect
,dispatch(fetchChannelList())
会在每次组件渲染时执行,可能会导致不必要的重复请求和渲染。
dispatch
是 Redux 提供的一个函数,它通常是不可变的。在 useEffect
的依赖项数组中包含 dispatch
,能确保 useEffect
中的代码只在组件第一次渲染时执行,而不是在每次渲染时执行。这样做可以减少不必要的请求和操作,提高应用的性能。
通过设置 useEffect
的依赖项数组为 [dispatch]
,你告诉 React 只有当 dispatch
函数发生变化时才重新执行 useEffect
中的代码。但由于 dispatch
函数通常不会变化,所以实际上 useEffect
中的代码只会在组件第一次渲染时执行。这种做法避免了每次组件渲染时都发送请求,节省了网络资源,提高了应用的性能。