在 React SPA 应用中使用 Redux 的最佳实践

Redux 是一种流行的状态管理工具,用于在大规模 React 应用中管理复杂的状态。在 React 单页面应用(SPA)中使用 Redux 可以帮助我们更好地组织状态,使代码更加可靠和可维护。这篇文章将深入探讨 Redux 在 React SPA 中的最佳实践和指导,其中包括如何设计 Redux 数据流和如何优化性能。

Redux 入门

Redux 是一个单向数据流工具,将应用程序的状态保存在一个全局仓库中,组件通过派发异步 Action 或同步数据行为到 store 中改变仓库状态。状态变更之后,所有的依赖 store 数据的组件都会 rerender,从而当前组件用户界面响应后端或用户交互的数据操作。

Redux 的基本概念:

  1. Store:保存整个应用的状态的对象。
  2. Reducer:State 的计算逻辑。
  3. Action:描述事件的对象。
  4. Dispatcher:派发 Action 更新 State。

正如上图所示,当组件接收(dispatch)一个action,Reducer 将计算出更新后的 State,再更新 store 中的 State。State 中更新之后,所有的组件都将重新渲染,使得应用的状态和用户界面的变化一直保持同步。

重要概念

在正确理解 Redux 运作的过程后,我们来谈谈它的核心概念:Store、Reducer 和 Action。

Store

Store 是存储应用内所有 State 的 JavaScript 对象,一个应用通常只有一个 Store。通过 Redux 提供的 createStore 函数可以创建一个 Store,代码如下:

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

在上面的代码中,reducer 是一个可传入 createStore 函数的纯函数(接受 Action 和当前 State,并返回新 State 的函数)。

Reducer

Reducer 是 State 树的一个更新函数,它接收一个 Action 和当前 State,并返回一个新的 State,因为它是一个纯函数,所以它不能直接修改 State。相反,它应该返回一个状态的副本,同时将更新部分合并到副本中。代码如下:

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

在上面的代码中,我们定义了一个 reducer,当接收到 'INCREMENT' 或 'DECREMENT' 的 Action 时会更新 State,否则会返回当前 State。

Action

Action 是 store 数据的唯一来源,它是一个简单的对象,它描述了当前发生的事情。例如,在一个 TODO 应用中,当用户创建一项新任务时,Action 可能如下所示:

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

在上面的代码中,Action 包含 'ADD_TODO' 类型和新应添加的 todo 对象。我们可以配置 Store 来接受 Action 的更新,代码实现如下:

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

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

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

在上面的代码中,我们创建了一个 todos reducer,它将 'ADD_TODO' 类型的 Action 与 todo 对象合并到 State 中。我们使用 createStore 函数创建 Store,然后将 todo Action 传递给 Store.dispatch() 函数。此时,State 已更新,且所有应用程序中的组件都会显示新的 todo。

设计 Redux 数据流

在一个典型的 React SPA 中使用 Redux,我们需要了解如何设计应用程序的 Redux 数据流。我们需要考虑大小,形状(Shape),要使用的规则以及何时将状态存储在 Redux store 内部。

设计思路

  • 是否添加真的需要 Redux 编写的全局 State ?
  • 是否需要随时监视 State 的变化?
  • 是否需要复杂的非同步数据流 ?(如 fetch, websocket 等)。

对于第一个问题,我们需要根据应用程序的大小和复杂性来决定什么时候添加全局 State。在构建一个小型和简单的应用程序时,仅使用局部 state 可能足够了,但随着应用程序的扩展和复杂性增加,全局状态可能更具优势。

对于第二个问题,我们必须了解哪些组件关联到 store,因为小型应用程序中的所有组件都访问 store,这会带来不必要的渲染和性能问题。所以在将组件连接到 store 之前,我们必须调整组件层次结构,避免不必要的一切渲染,保持性能的优化。

对于第三个问题,我们必须了解 action 和 Reducer 如何进行异步操作,并且组合异步事件和同步事件以正确更新全局 State。Redux 可以通过多种方式处理异步事件,包括 Redux-Saga,Redux-Observable 和 Redux-Thunk 等库。

规则

首先,考虑仅使用完全形成的对象表示其状态树,而不是使用单个 JS 基元值。当 application 状态可用(perform Reduce)时,避免 serialize/deserialize,无论您使用该状态以何种方式加工其子组件。

其次,最好遵循 Flux/Redux 的“单一的源码真理”原则,即在 State 树内只使用一个逻辑分支进行单向的 action dispatch 和 store 更新流。反之,如果您的应用正在刻意设计多个分支,则多 store 架构或 Redux Nest 树等库可能会被视为最佳实践。

最后,您的 State 对象应该是那种只有必要数据(可以自我描述),最大化响应性和可操作性。可以考虑将存储函数或类实例等变量拆分,并将他们移动到 Redux store 的副作用中(例如,app 注册表),因为它们可以被序列化和激活后应用程序恢复和重建。

示例:

上图展示了一个典型的 React SPA,其中有不同的 Redux 提供符合组局部之间通信解决方案。使用 Flux 的原则产生了一个简单的架构,使用单一的 State 数传递信息,从 APP 传递到组件中。通过推导 formly 的状态,以及使用路由时的参数和配置,Routing 和 Zoning 可以排除掉应用中的某些状态。

Redux 提供了单项数据流的服务,并通过接收新的 state 并整合新 state 以作为 store 的属性,随后更新所有 listening component 来适应 state 的变化。

在上图中,Redux store 介于组件通信之间以 supports shared context 的方式,和 React context API 配合使用,将 store 直接传递给组件的情况。

但是,在实际开发中,如果您不太确定哪种跨组件通信方案更适合您,请考虑使用 Redux 直接 dispatch 一个 redux的 action(例如 dispatch new 我们的 state 对象)或是直接使用 plain React context API 来替代 Redux 的存储机制。

避免不必要的渲染

在大型的应用程序中,性能是关键,因此我们需要优化 Componet 的性能,并避免不必要的复渲染。Renders 通常是应用程序性能的最大瓶颈,因此每个组件只应在必要时重新渲染。

shouldComponentUpdate

React 组件提供了 shouldComponentUpdate 方法,该方法在组件即将进行渲染操作时被调用,它接受 next props 和 next state,如果返回 true,则为将组件进行re-render 操作。shouldComponentUpdate 可以用来避免在不必要的情况下 re-rendering。例如:

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

在上面的代码中,组件仅在 this.props.value 或 this.state.value 发生更改时重新渲染,从而避免不必要的操作。

PureComponents

React 还提供了 PureComponent,它使用浅比较在 props 和 state 发生变化时触发 re-rendering。对于非嵌套且不可变数据的组件,PureComponent 就非常适合了。

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

在上面的代码中,组件 pureComponent 会自动监测数据变化而重渲染。

避免すべて的 component 都连接 Redux store

在大型的应用程序中,所有的 component 都连接到 Redux store 是不必要的,并且会使应用程序的性能受到影响。因此,我们应该只连接需要访问 store 状态的 component。

以下是一些可以减少 component 的连接数量的技巧:

  1. 高阶组件:使用 high-order component 以类似于 withTranslation(component) 的方式将 store 中的状态包装到组件中。这个方法可以减少连接到 store 的 component 数量。

  2. React Context API:使用 React Context API 将 store 保存在上层组件的上下文中,从而使需要的 component 可以通过 context 使用 store。这种方法更进一步减少了需要连接到 store 的 component 数量。

  3. 选择 connect 张帆的 component: 不是所有的 component 都需要连接 store。当 component 只需要访问 store 中的一部分状态时,可以使用 connectSeletor 函数连接该 component,而不是使用 connect 连接整个 store 。这样可以减轻不必要的操作。

性能优化

在开发大型应用程序时,我们不仅需要避免不必要的渲染,还需要优化领域逻辑(包括 Action 和 Reducer 设计)以提高应用程序的性能。

设计合适的 Reducer 和 Action

在设计应用程序的 reducer 时,应该尽量将 reducer 保持纯净,不要修改状态本身。Reducer 只是通过 action 更新状态并返回新的状态。从而 React 在需要绘制新的数据时能够更快地检测到状态变化。例如,下面的代码中,我们在 reducer 中对状态进行操作:

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

在上面的代码中,我们直接使用 Array.prototype.push()修改数组,这会直接影响仓库状态,从而导致 store 响应更新时,难以检测到 state 变化。

正确的 reducer 实现方法应该像这个样子:

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

在上面的代码中,我们将 State 副本合并到一起,并返回新的 State 对象,从而状态可以正确地更新,从而更清晰地表示出 state 变化。

懒加载或分解 reducer

当应用程序规模变得庞大时,Reducer 的性能可能会变得很糟糕。这时候,我们可以使用懒加载或拆分 reducer,将不相关的 reducer 分解出来,形成一个单独的状态树,从而提高 Reducer 的执行效率和应用程序的性能。

避免过多的细节更新

在更新组件时,React 会比较当前组件的 DOM 树来检测是否需要重新绘制。针对小的修改,React diff 算法执行得很快,但是当组件的复杂度和更新频率增加时,算法性能也会下降。

为了避免这些性能问题,我们应该尽可能少的执行组件重渲染,并避免对应用程序进行过多的修改。

下面示例中,使用 React.memo 避免了过多的重新渲染:

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

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

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

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

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

在上面的代码中,我们使用了 React.memo 包装了 Items 组件,使得组件只在 items 发生变化时才进行重渲染。

结论

在 React SPA 应用中,Redux 是一种非常有用的工具,可以帮助我们更好地组织状态和简化应用程序的开发过程。通过使用 Redux,我们可以更好地管理状态,并提高应用程序的性能和可维护性。

在应用程序中使用 Redux,我们应该设计好 Redux 数据流和应用程序的 reducer 和 action,避免不必要的渲染和进行性能优化。运用正确的 Redux 最佳实践是很重要的。若操作顺利,那么 Redux 具有很高的生产效果,并可以保持代码的整洁,让整个应用的开发过程变得更加愉悦。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/672c829eddd3a70eb6d86ed6