Redux 开发实践 —— 重构前端应用架构

阅读时长 11 分钟读完

当我们大规模开发前端应用时,数据管理成为了一个不可避免的问题。在 Ajax 开始流行的时候,我们通过异步请求数据,并利用回调函数来维护数据的可靠性。当我们的应用规模变大时,这种方式就显得力不从心了。当我们引入 MVVM / MVC 模式时,我们用 Model 层来管理应用状态。这种方式提高了可维护性,但是会降低应用的性能。

Redux 是一个非常流行的状态管理库,它提供了透明的单向数据流机制,使得我们能够统一管理应用状态,轻松地实现应用的可扩展性。本文将详细介绍 Redux 的工作原理,如何优雅地使用 Redux 来管理应用的状态,以及如何在实践中解决一些常见问题。

状态管理与应用架构

随着 Web 应用不断发展,前端开发的规模越来越大,应用越来越复杂。我们经常听到这样的问题:应用的状态管理混乱不堪,导致应用的可维护性和性能变得越来越差。

有些开发者会选择将状态直接绑定到 UI 组件上,这样会使得代码十分难以维护可扩展。有些开发者倾向于利用事件总线:每当需要更新状态时,触发一个事件,然后由所有订阅该事件的组件更新自己的状态。这样虽然看起来比直接将状态绑定到 UI 组件上要好一些,但是仍然难以实现应用的可扩展性。而 Redux 的出现,为我们提供了一种优雅的解决方案。

Redux 采用单一状态树来管理应用的状态。这种方式与传统的 MVC / MVVM 模式不同。在传统的方式中,我们随着应用的规模增大,经常会遇到如何组织入口文件、如何管理组件之间共享的状态等问题,但是在 Redux 中,所有的状态都集中在一个 Store 中维护,而每个组件都可以从这个状态树中获取所需要的状态。

Redux 不仅提供了对状态的管理,同时也提供了一个地方用于存放业务逻辑。例如,我们可以在 Redux 中定义一个 action,该 action 被调用时将触发处理函数,该处理函数中可以执行一些具体的业务逻辑。

Redux 工作原理

Redux 采用了单向数据流,通过一系列的 action 对状态进行修改,从而更新视图。下面是 Redux 的基本工作原理:

  1. Action:描述发生了什么。Action 是一个对象,其中包含了一个 type 和其它需要传递给 state 的数据。
  2. Reducer:负责生成新的 state。Reducer 是一个函数,接收先前的 state 和一个 action 对象,返回新的 state。
  3. Store:整个应用只有一个 Store,用于维护应用的状态。我们可以通过 subscribe 和 dispatch 方法来监听和修改应用的状态。
  4. View:用于展示应用的 state,当 state 发生变化时,进行更新。

我们可以通过如下代码来创建一个 Redux Store:

-- -------------------- ---- -------
------ - ----------- - ---- --------

----- ------------ - -
  ------ -
--

-------- -------------------- - ------------- ------- -
  ------ ------------- -
    ---- ------------
      ------ - ------ ----------- - - --
    ---- ------------
      ------ - ------ ----------- - - --
    --------
      ------ ------
  -
-

-- ---- -----
----- ----- - ----------------------------
展开代码

上面代码中,我们创建了一个初始状态为 value 为 0 的计数器。我们还定义了一个 counterReducer 函数,它接收先前的 state 和一个 action 对象,返回一个新的 state。创建 Store 后,我们可以通过 dispatch 方法来修改 state。

我们还可以使用 subscribe 方法监听 state 的变化:

这个例子是非常简单的,我们可以把它想象成一个小应用的一部分。但是,如果我们的应用变得更加复杂,Store 中的 state 也变得更加复杂,我们需要设计并编写更多的 reducer 和 action,才能达到我们预期的状态管理目的。

Redux 中的状态更新

在 Redux 中,我们只有一个 Store,它维护了整个应用的状态。因此,在更新应用的 state 时,我们必须遵循某些规则:

  • 不要直接修改 Store 中的 state。这样我们无法追踪状态的变化,难以 debug。我们应该通过 action 来修改 state。
  • action 应该是一个对象。属性包括 type 表示某个 action 类型,以及其它需要传递给 reducer 函数的数据。
  • 不要在 reducer 函数中进行异步操作,这会导致 state 的变化是不确定的。任何对异步操作的尝试都应该交给中间件来处理。

下面是一个简单的例子,我们通过 action 更新 Store 中的 state:

在大多数情况下,我们会将 action 定义为函数,这些函数被称为 action creator:

Redux 中的中间件

在复杂的应用场景中,Redux 中的 reducer 仅仅是更新应用 state 的一种方式。我们可能需要执行一些额外的操作,如异步 I/O 操作,路由等。在 Redux 中,我们可以通过中间件来扩展 Redux 的功能。

中间件是一个函数,它拦截 action,并在处理流程中扩展其功能。redux-thunk 和 redux-saga 是 Redux 最受欢迎的中间件之一,它们可以用来处理异步 I/O 操作。

下面是一个简单的中间件示例:

-- -------------------- ---- -------
----- ------ - ----- -- ---- -- ------ -- -
  -------------------------- --------
  ----- ------ - -------------
  ----------------- ------- ------------------
  ------ -------
--

----- ----- - ------------
  --------
  -----------------------
--
展开代码

我们可以在控制台中看到每个 action 的调用日志:

实践中的 Redux

在实践中使用 Redux,我们要了解一些概念,例如:

  • 一般来说,我们考虑将应用的 state 根据业务逻辑划分为多个部分。所以,我们可以为每个部分创建一个 reducer,然后将它们组合成一个 rootReducer。
  • 在组件中,我们通常使用 connect 函数来将组件与 Redux Store 关联起来。通过 mapDispatchToProps 函数,我们可以将 action creator 作为 props 传递给组件,从而即使在普通组件中也能够直接调用 reducer。
  • 对于异步操作,我们可以用 redux-thunk 中间件来处理。通过 dispatch 一个异步 action 来触发一个异步操作,在这个异步操作完成后,我们会通过在 reducer 中对应的 action 来更新应用的 state。

最后,让我们通过一个稍微复杂的例子来展示 Redux 在实践中的应用:

-- -------------------- ---- -------
------ - ------------ ---------------- --------------- - ---- --------
------ ----- ---- --------------

-- -- ------ ---------
----- --------- - ------------
----- --------- - ------------

-- -- -------
-------- ----------- -
  ------ -
    ----- ---------
  --
-

-------- ----------- -
  ------ -
    ----- ---------
  --
-

-- -- --------
-------- -------------------- - -- ------- -
  ------ ------------- -
    ---- ----------
      ------ ----- - --
    ---- ----------
      ------ ----- - --
    --------
      ------ ------
  -
-

-------- -------------------- - --- ------- -
  ------ ------------- -
    ---- ----------
      ------ --------------
    ---- ----------
      ------ --------------
    --------
      ------ ------
  -
-

-- -- -----------
----- ----------- - -----------------
  -------- ---------------
  -------- --------------
---

-- -- -----
----- ----- - ------------
  ------------
  ----------------------
--

-- ------
----- ------- ------- --------------- -
  -------- -
    ----- - -------- ------------ ------------ ------- - - -----------
    ------ -
      -----
        ----------------
        ----------------
        ------- ----------------------------------------
        ------- ----------------------------------------
      ------
    --
  -
-

-- ---- ----- ----- ----
------ - ------- - ---- --------------

----- --------------- - ----- -- --
  -------- --------------
  -------- -------------
---

----- ------------------ - -------- -- --
  ------------ -- -- ----------------------
  ------------ -- -- ---------------------
---

----- ------------ - --------
  ----------------
  ------------------
-----------
展开代码

在这个例子中,我们首先定义了两个 action:INCREMENT 和 DECREMENT。他们对应对应的自增和自减操作。然后我们定义了两个 reducer:counterReducer 和 messageReducer。它们分别负责更新 State Tree 中的 counter 和 message 字段。

接着,我们把 counterReducer 和 messageReducer 通过 combineReducers 进行组合,并创建了一个 rootReducer,在最后创建 Store 时,我们将 rootReducer 作为 reducer 参数进行传递。

接下来,我们创建了一个 Counter 组件,它接收 counter, message, onIncrement 和 onDecrement 属性。我们为 Counter 组件编写常规的 React 组件代码,并将 onIncrement 和 onDecrement 通过某些途径传递到普通组件中。

最后,我们通过调用 connect 函数,将 Counter 组件关联到 Redux Store 中,将 counter 和 message 作为 props 传递给组件。通过 mapDispatchToProps 函数,我们将 increment 和 decrement 函数转化为 props 函数。

这样,我们就完成了一个完整的 Redux 应用。每次单击“Increment”和“Decrement”按钮时,将分别调用 increment 和 decrement 函数。Redux Store 将呈现相应的操作和更改后的状态。然后,更改会反映到 Counter 组件中,并且在组件中渲染出。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67c2a487314edc2684c110d8

纠错
反馈

纠错反馈