理解 React Redux 的 7 个步骤
最初发布在我的博客上
React 太棒了,我们怎么夸都不过分。但说到状态管理,事情就变得棘手了。有太多术语需要记住:状态、存储、动作、reducer、中间件等等。对于中型或大型 React 应用,随着应用规模的增长,管理状态会变得非常困难。我们需要使用 Redux 或其他替代方案(例如 context API、flux 等)来管理状态。在本文中,我们将重点介绍 Redux 以及它如何与 React 协同工作。Redux 是一个独立的库,与框架无关,这意味着您可以将它与其他框架或原生 JavaScript 一起使用。
在本文中,我将引导您通过 7 个步骤,以最简单的方式理解 React-Redux。
- 先决条件
- 1. 什么是国家?
- 2.什么是redux以及为什么我们需要它?
- 3.什么是减速器?
- 4.什么是商店?
- 5.如何将我们的商店连接到React?
- 6.什么是动作?
- 7.如何使用redux处理异步代码?
- 结论
- 资源
先决条件
本文假设你至少对 React 和 ES6 有基础到中级的理解。然后,你需要使用以下命令创建一个新的 React 应用:
npx create-react-app react-redux-example
并通过在 shell 中运行将redux
和包添加到您的 React 应用程序react-redux
npm install redux react-redux
然后,我们需要创建一些文件。
containers
在中添加一个文件夹src
,然后创建Articles.js
文件。
import React, { useState } from "react"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
const Articles = () => {
const [articles, setArticles] = useState([
{ id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
{ id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
])
const saveArticle = e => {
e.preventDefault()
// the logic will be updated later
}
return (
<div>
<AddArticle saveArticle={saveArticle} />
{articles.map(article => (
<Article key={article.id} article={article} />
))}
</div>
)
}
export default Articles
components
在 中添加一个文件夹src
,然后创建AddArticle/AddArticle.js
和Article/Article.js
。- 在
Article.js
import React from "react"
import "./Article.css"
const article = ({ article }) => (
<div className="article">
<h1>{article.title}</h1>
<p>{article.body}</p>
</div>
)
export default article
- 在
AddArticle.js
import React, { useState } from "react"
import "./AddArticle.css"
const AddArticle = ({ saveArticle }) => {
const [article, setArticle] = useState()
const handleArticleData = e => {
setArticle({
...article,
[e.target.id]: e.target.value,
})
}
const addNewArticle = e => {
e.preventDefault()
saveArticle(article)
}
return (
<form onSubmit={addNewArticle} className="add-article">
<input
type="text"
id="title"
placeholder="Title"
onChange={handleArticleData}
/>
<input
type="text"
id="body"
placeholder="Body"
onChange={handleArticleData}
/>
<button>Add article</button>
</form>
)
}
export default AddArticle
- 在
App.js
import React from "react"
import Articles from "./containers/Articles"
function App() {
return <Articles />
}
export default App
因此,如果您已经满足了先决条件,我们就可以继续并揭开状态的神秘面纱。
1. 什么是国家?
每个 React 状态组件的核心都是其状态。它决定了组件的渲染方式和行为方式。要真正理解状态,我们必须将其应用于实际案例。“用户是否已通过身份验证?” 是一个控制用户是否通过身份验证的状态;“模态框是否打开?” 也是一个用于查看给定模态框是否打开的状态,与文章列表或计数器等类似。
// Class based component
state = {
articles: [
{ id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
{ id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
],
}
// React hooks
const [articles, setArticles] = useState([
{ id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
{ id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
])
现在我们知道了什么是状态,是时候介绍 redux 并深入研究它了。
2.什么是redux以及为什么我们需要它?
在没有 Redux 或其他替代方案的情况下管理状态可能会非常困难。想象一下,我们必须在每个组件中检查用户是否已通过身份验证。为了处理这种情况,我们必须向每个组件传递 props,但随着应用程序的增长,以这种方式管理状态几乎是不可能的。而这正是 Redux 的真正优势所在。Redux
是一个独立的库,它通过一个中央存储,允许组件访问其所需的状态,从而帮助我们管理状态。Redux 将我们应用程序的整个状态存储在一个不可变的对象树中。
另一个广义的术语是“存储”,为了更好地理解它,我们首先需要解释什么是 Reducer?
3.什么是减速器?
Reducer 是一个纯函数,它接收旧(先前)状态和一个 action 作为参数,然后返回更新后的状态。Reducer 只处理同步代码,这意味着它不会像 HTTP 请求之类的副作用。我们仍然可以使用 Redux 处理异步代码,稍后我们将学习如何操作。顺便说一句,如果你对“action”这个词感到困惑,不用担心,稍后会更清楚。那么,让我们创建第一个 Reducer。
文件的结构完全由你决定,不过,我将遵循惯例,store
在项目中创建一个文件夹来存放 Reducer、action 等文件。然后,创建一个reducer.js
文件。
- 在
reducer.js
const initialState = {
articles: [
{ id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
{ id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
],
}
const reducer = (state = initialState, action) => {
return state
}
export default reducer
正如我之前所说,Reducer 只是一个函数,它接收先前的状态和一个动作作为参数,并返回更新后的状态。这里,我们没有先前的状态,所以它是 undefined,因此我们需要用它来初始化它,initialState
并保存我们预定义的文章。
现在我们已经设置好了 Reducer,是时候创建 Store 了。
4.什么是商店?
Store 保存着我们 React 应用的整个状态树。它是我们应用状态的存放地。你可以把它看作一个巨大的 JavaScript 对象。要创建 Store,我们需要一个 Reducer 作为参数传递。我们已经有一个 Reducer,让我们将它连接到 Store。
- 在我们的
index.js
档案里。
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"
import "./index.css"
import App from "./App"
import reducer from "./store/reducer"
const store = createStore(reducer)
ReactDOM.render(<App />, document.getElementById("root"))
要创建 store,我们首先需要createStore
从 redux 包中导入,然后导入我们的 reducer,最后将其作为参数传递给 store createStore(reducer)
。这样,我们就成功创建了 store,但还不算完,我们还得把它连接到我们的 React 应用。
5.如何将我们的商店连接到React?
要将 store 连接到 React,我们需要导入一个来自Provider
react-redux 包的辅助函数。然后App
用 包裹我们的组件Provider
,并将 的值作为 props 传递,store
该 props 的值是我们当前的 store。
- 在我们的
index.js
档案里。
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"
import { Provider } from "react-redux"
import "./index.css"
import App from "./App"
import reducer from "./store/reducer"
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
然后,我们需要将我们的组件连接到 redux 存储。
- 在
Articles.js
import React from "react"
import { connect } from "react-redux"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
const Articles = ({ articles }) => {
const saveArticle = e => {
e.preventDefault()
// the logic will be updated later
}
return (
<div>
<AddArticle saveArticle={saveArticle} />
{articles.map(article => (
<Article key={article.id} article={article} />
))}
</div>
)
}
const mapStateToProps = state => {
return {
articles: state.articles,
}
}
export default connect(mapStateToProps)(Articles)
在这里,我们首先导入connect()
,这是一个返回高阶函数并接收组件作为输入的函数。它帮助我们将组件连接到存储并提供获取状态的权限。
然后,我们声明一个名为mapStateToProps()
(您可以随意命名)的新函数。它用于从 redux 存储中获取我们的状态。该函数接收state
存储在 redux 中的作为参数并返回一个将保存我们的的 JavaScript 对象articles
。
为了到达存储,我们需要传递mapStateToProps()
给connect
函数。它将获取我们的组件Articles
并返回一个带有其注入的 props 的包装器组件。这意味着我们现在可以从存储中获取我们的状态。组件通过 props 接收状态,我们仍然可以像articles
以前一样显示,但现在通过 redux。
我们已经成功将我们的存储连接到 react 并从中获取我们的状态。现在,让我们深入了解行动
6.什么是行动?
动作是包含诸如REMOVE_ARTICLE
或ADD_ARTICLE
等类型的信息的有效负载。动作从您的组件发出。它将数据从您的 React 组件发送到您的 Redux 存储。动作不会到达存储,它只是传递信息。存储由 Reducer 更改。
要在我们的项目中创建动作,我们需要在store
文件夹中创建一个名为 的新文件actionTypes.js
。
export const ADD_ARTICLE = "ADD_ARTICLE"
然后,我们需要转到我们的Articles.js
文件并添加以下代码。
import React from "react"
import { connect } from "react-redux"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import * as actionTypes from "../store/actionTypes"
const Articles = ({ articles, saveArticle }) => (
<div>
<AddArticle saveArticle={saveArticle} />
{articles.map(article => (
<Article key={article.id} article={article} />
))}
</div>
)
const mapStateToProps = state => {
return {
articles: state.articles,
}
}
const mapDispatchToProps = dispatch => {
return {
saveArticle: article =>
dispatch({ type: actionTypes.ADD_ARTICLE, articleData: { article } }),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Articles)
然后,我们需要从 导入所有内容actionTypes.js
。并创建一个mapDispatchToProps
接收函数dispatch
作为参数的新函数。mapDispatchToProps
返回一个具有属性 的对象saveArticle
。它是对将在我们的商店中分派操作的函数的引用。saveArticle
包含一个匿名函数,该函数接收我们的article
作为参数并返回该dispatch
函数。它接收类型和要更新的数据作为参数。正如您所猜测的,它将在我们的商店中分派操作。
最后,我们需要将mapDispatchToProps
作为第二个参数传递给connect
函数。为了使其工作,我们需要更新我们的reducer并添加操作ADD_ARTICLE
。
- 在
store/reducer.js
import * as actionTypes from "./actionTypes"
const initialState = {
articles: [
{ id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
{ id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
],
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_ARTICLE:
const newArticle = {
id: Math.random(), // not really unique but it's just an example
title: action.article.title,
body: action.article.body,
}
return {
...state,
articles: state.articles.concat(newArticle),
}
}
return state
}
export default reducer
如你所见,我们导入了actionTypes
。然后,我们在reducer
函数中检查动作的类型是否等于ADD_ARTICLE
。如果是,则首先创建一个保存文章的新对象,然后将其附加到文章数组中。在返回状态之前,我们复制旧状态,然后将concat
新文章复制到旧状态中。这样,我们就能保证状态的安全且不可变。
7.如何使用redux处理异步代码?
正如我之前所说,Reducer 只能处理同步代码。要执行异步代码,我们需要使用 Action Creator。它是一个返回函数(或者应该说是 Action)的函数。因此,要在项目中使用它,我们需要创建一个新文件actionCreators.js
。
- 在
store/actionCreators.js
import * as actionTypes from "./actionTypes"
export const addArticle = article => {
return {
type: actionTypes.ADD_ARTICLE,
article,
}
}
这里,我们声明了一个名为 的新动作创建器addArticle
。它是一个函数,接收article
作为参数,并返回动作的类型和值。顺便说一下,article
和 相同,只是 ES6 的一个便捷语法。现在我们可以继续修改文件中的article: article
函数了。mapDispatchToProps
Articles.js
- 在
Articles.js
import React from "react"
import { connect } from "react-redux"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { addArticle } from "../store/actionCreators"
const Articles = ({ articles, saveArticle }) => (
<div>
<AddArticle saveArticle={saveArticle} />
{articles.map(article => (
<Article key={article.id} article={article} />
))}
</div>
)
const mapStateToProps = state => {
return {
articles: state.articles,
}
}
const mapDispatchToProps = dispatch => {
return {
saveArticle: article => dispatch(addArticle(article)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Articles)
如你所见,我们首先导入了动作创建器addArticle
,然后在mapDispatchToProps
函数中更新传递给的参数dispatch
。现在,它接收了动作创建器及其值article
。
但我们还没有完成,我们需要在项目中添加一个新包,redux-thunk
以便能够处理异步代码。
npm install redux-thunk
redux-thunk
是一个帮助我们处理异步代码的中间件。中间件提供了一种在到达 Reducer 之前与已分发到 Store 的 Action 进行交互的方法。现在,让我们将它应用到我们的项目中。
- 在
index.js
import React from "react"
import ReactDOM from "react-dom"
import { createStore, applyMiddleware } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import "./index.css"
import App from "./App"
import reducer from "./store/reducer"
const store = createStore(reducer, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
在此代码块中,我们首先applyMiddleware
从 redux 和thunk
redux-thunk 导入。然后,为了使其工作,我们需要传递createStore
第二个参数或增强器来接收我们的中间件thunk
。通过这样做,我们现在可以处理异步代码了。现在让我们更新我们的 action creator。
- 在
store/actionCreators.js
import * as actionTypes from "./actionTypes"
export const addArticle = article => {
return {
type: actionTypes.ADD_ARTICLE,
article,
}
}
export const simulateHttpRequest = article => {
return dispatch => {
setTimeout(() => {
dispatch(addArticle(article))
}, 3000)
}
}
在本文中,我们将模拟一个 HTTP 请求。
这里,我们有一个新的 action creator simulateHttpRequest
,它接收article
作为输入并返回一个函数。由于thunk
中间件的存在,我们可以访问,dispatch
因为我们的中间件在 action 的调度和 action 到达 reducer 的时间点之间运行。因此,我们可以将dispatch
作为一个参数获取。然后,等待 3 秒钟,在setTimeout
调度 action 并将文章添加到文章数组之前,仅模拟一个 HTTP 请求。
我们对 action creator 进行了一些修改,为了使其再次工作,我们需要更新Articles.js
。
- 在
Articles.js
import React from "react"
import { connect } from "react-redux"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { simulateHttpRequest } from "../store/actionCreators"
const Articles = ({ articles, saveArticle }) => (
<div>
<AddArticle saveArticle={saveArticle} />
{articles.map(article => (
<Article key={article.id} article={article} />
))}
</div>
)
const mapStateToProps = state => {
return {
articles: state.articles,
}
}
const mapDispatchToProps = dispatch => {
return {
saveArticle: article => dispatch(simulateHttpRequest(article)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Articles)
这里我们唯一要做的就是改为addArticle
,simulateHttpRequest
这样,一切都应该再次工作,现在我们能够通过 redux 处理异步代码。
您可以在这里找到完成的项目
结论
在处理中大型 React 应用时,状态管理可能非常困难。而像 redux 这样的包可以让它变得非常容易。还有一些替代方案,例如 context API (+hooks),它非常有用,而且不需要第三方库,但深入研究 redux 仍然是必要的。
然而,对于像我们这样的简单 React 应用来说,redux 有点过头了,我们不需要 redux 来管理状态,但在一个非常简单的应用中理解 redux 的工作原理更容易。
资源
React Redux 官方文档
Redux devtools
Redux 最佳实践
Redux saga
Context API