理解 React Redux 的 7 个步骤

2025-05-24

理解 React Redux 的 7 个步骤

最初发布在我的博客上


React 太棒了,我们怎么夸都不过分。但说到状态管理,事情就变得棘手了。有太多术语需要记住:状态、存储、动作、reducer、中间件等等。对于中型或大型 React 应用,随着应用规模的增长,管理状态会变得非常困难。我们需要使用 Redux 或其他替代方案(例如 context API、flux 等)来管理状态。在本文中,我们将重点介绍 Redux 以及它如何与 React 协同工作。Redux 是一个独立的库,与框架无关,这意味着您可以将它与其他框架或原生 JavaScript 一起使用。

在本文中,我将引导您通过 7 个步骤,以最简单的方式理解 React-Redux。

先决条件

本文假设你至少对 React 和 ES6 有基础到中级的理解。然后,你需要使用以下命令创建一个新的 React 应用:

npx create-react-app react-redux-example
Enter fullscreen mode Exit fullscreen mode

并通过在 shell 中运行将redux和包添加到您的 React 应用程序react-redux

npm install redux react-redux
Enter fullscreen mode Exit fullscreen mode

然后,我们需要创建一些文件。

  • 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
Enter fullscreen mode Exit fullscreen mode
  • components在 中添加一个文件夹src,然后创建AddArticle/AddArticle.jsArticle/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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • App.js
import React from "react"
import Articles from "./containers/Articles"

function App() {
  return <Articles />
}
export default App
Enter fullscreen mode Exit fullscreen mode

因此,如果您已经满足了先决条件,我们就可以继续并揭开状态的神秘面纱。

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" },
])
Enter fullscreen mode Exit fullscreen mode

现在我们知道了什么是状态,是时候介绍 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
Enter fullscreen mode Exit fullscreen mode

正如我之前所说,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"))
Enter fullscreen mode Exit fullscreen mode

要创建 store,我们首先需要createStore从 redux 包中导入,然后导入我们的 reducer,最后将其作为参数传递给 store createStore(reducer)。这样,我们就成功创建了 store,但还不算完,我们还得把它连接到我们的 React 应用。

5.如何将我们的商店连接到React?

要将 store 连接到 React,我们需要导入一个来自Providerreact-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")
)
Enter fullscreen mode Exit fullscreen mode

然后,我们需要将我们的组件连接到 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)
Enter fullscreen mode Exit fullscreen mode

在这里,我们首先导入connect(),这是一个返回高阶函数并接收组件作为输入的函数。它帮助我们将组件连接到存储并提供获取状态的权限。

然后,我们声明一个名为mapStateToProps()(您可以随意命名)的新函数。它用于从 redux 存储中获取我们的状态。该函数接收state存储在 redux 中的作为参数并返回一个将保存我们的的 JavaScript 对象articles

为了到达存储,我们需要传递mapStateToProps()connect函数。它将获取我们的组件Articles并返回一个带有其注入的 props 的包装器组件。这意味着我们现在可以从存储中获取我们的状态。组件通过 props 接收状态,我们仍然可以像articles以前一样显示,但现在通过 redux。

我们已经成功将我们的存储连接到 react 并从中获取我们的状态。现在,让我们深入了解行动

6.什么是行动?

动作是包含诸如REMOVE_ARTICLEADD_ARTICLE等类型的信息的有效负载。动作从您的组件发出。它将数据从您的 React 组件发送到您的 Redux 存储。动作不会到达存储,它只是传递信息。存储由 Reducer 更改。

要在我们的项目中创建动作,我们需要在store文件夹中创建一个名为 的新文件actionTypes.js

export const ADD_ARTICLE = "ADD_ARTICLE"
Enter fullscreen mode Exit fullscreen mode

然后,我们需要转到我们的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)
Enter fullscreen mode Exit fullscreen mode

然后,我们需要从 导入所有内容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
Enter fullscreen mode Exit fullscreen mode

如你所见,我们导入了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,
  }
}
Enter fullscreen mode Exit fullscreen mode

这里,我们声明了一个名为 的新动作创建器addArticle。它是一个函数,接收article作为参数,并返回动作的类型和值。顺便说一下,article和 相同,只是 ES6 的一个便捷语法。现在我们可以继续修改文件中的article: article函数了mapDispatchToPropsArticles.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)
Enter fullscreen mode Exit fullscreen mode

如你所见,我们首先导入了动作创建器addArticle,然后在mapDispatchToProps函数中更新传递给的参数dispatch。现在,它接收了动作创建器及其值article

但我们还没有完成,我们需要在项目中添加一个新包,redux-thunk以便能够处理异步代码。

npm install redux-thunk
Enter fullscreen mode Exit fullscreen mode

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")
)
Enter fullscreen mode Exit fullscreen mode

在此代码块中,我们首先applyMiddleware从 redux 和thunkredux-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)
  }
}
Enter fullscreen mode Exit fullscreen mode

在本文中,我们将模拟一个 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)
Enter fullscreen mode Exit fullscreen mode

这里我们唯一要做的就是改为addArticlesimulateHttpRequest这样,一切都应该再次工作,现在我们能够通过 redux 处理异步代码。

您可以在这里找到完成的项目

结论

在处理中大型 React 应用时,状态管理可能非常困难。而像 redux 这样的包可以让它变得非常容易。还有一些替代方案,例如 context API (+hooks),它非常有用,而且不需要第三方库,但深入研究 redux 仍然是必要的。

然而,对于像我们这样的简单 React 应用来说,redux 有点过头了,我们不需要 redux 来管理状态,但在一个非常简单的应用中理解 redux 的工作原理更容易。

资源

React Redux 官方文档
Redux devtools
Redux 最佳实践
Redux saga
Context API

文章来源:https://dev.to/ibrahima92/7-steps-to-understand-react-redux-121j
PREV
React Router 初学者完整指南(包括 Router Hooks)
NEXT
6 个强大的 CSS 选择器,真正帮助您编写干净的 CSS。