当我们大规模开发前端应用时,数据管理成为了一个不可避免的问题。在 Ajax 开始流行的时候,我们通过异步请求数据,并利用回调函数来维护数据的可靠性。当我们的应用规模变大时,这种方式就显得力不从心了。当我们引入 MVVM / MVC 模式时,我们用 Model 层来管理应用状态。这种方式提高了可维护性,但是会降低应用的性能。
Redux 是一个非常流行的状态管理库,它提供了透明的单向数据流机制,使得我们能够统一管理应用状态,轻松地实现应用的可扩展性。本文将详细介绍 Redux 的工作原理,如何优雅地使用 Redux 来管理应用的状态,以及如何在实践中解决一些常见问题。
状态管理与应用架构
随着 Web 应用不断发展,前端开发的规模越来越大,应用越来越复杂。我们经常听到这样的问题:应用的状态管理混乱不堪,导致应用的可维护性和性能变得越来越差。
有些开发者会选择将状态直接绑定到 UI 组件上,这样会使得代码十分难以维护可扩展。有些开发者倾向于利用事件总线:每当需要更新状态时,触发一个事件,然后由所有订阅该事件的组件更新自己的状态。这样虽然看起来比直接将状态绑定到 UI 组件上要好一些,但是仍然难以实现应用的可扩展性。而 Redux 的出现,为我们提供了一种优雅的解决方案。
Redux 采用单一状态树来管理应用的状态。这种方式与传统的 MVC / MVVM 模式不同。在传统的方式中,我们随着应用的规模增大,经常会遇到如何组织入口文件、如何管理组件之间共享的状态等问题,但是在 Redux 中,所有的状态都集中在一个 Store 中维护,而每个组件都可以从这个状态树中获取所需要的状态。
Redux 不仅提供了对状态的管理,同时也提供了一个地方用于存放业务逻辑。例如,我们可以在 Redux 中定义一个 action,该 action 被调用时将触发处理函数,该处理函数中可以执行一些具体的业务逻辑。
Redux 工作原理
Redux 采用了单向数据流,通过一系列的 action 对状态进行修改,从而更新视图。下面是 Redux 的基本工作原理:
- Action:描述发生了什么。Action 是一个对象,其中包含了一个 type 和其它需要传递给 state 的数据。
- Reducer:负责生成新的 state。Reducer 是一个函数,接收先前的 state 和一个 action 对象,返回新的 state。
- Store:整个应用只有一个 Store,用于维护应用的状态。我们可以通过 subscribe 和 dispatch 方法来监听和修改应用的状态。
- View:用于展示应用的 state,当 state 发生变化时,进行更新。
我们可以通过如下代码来创建一个 Redux Store:
-- -------------------- ---- ------- ------ - ----------- - ---- -------- ----- ------------ - - ------ - -- -------- -------------------- - ------------- ------- - ------ ------------- - ---- ------------ ------ - ------ ----------- - - -- ---- ------------ ------ - ------ ----------- - - -- -------- ------ ------ - - -- ---- ----- ----- ----- - ----------------------------展开代码
上面代码中,我们创建了一个初始状态为 value 为 0 的计数器。我们还定义了一个 counterReducer 函数,它接收先前的 state 和一个 action 对象,返回一个新的 state。创建 Store 后,我们可以通过 dispatch 方法来修改 state。
store.dispatch({ type: 'INCREMENT' });
我们还可以使用 subscribe 方法监听 state 的变化:
store.subscribe(() => { console.log(store.getState()); });
这个例子是非常简单的,我们可以把它想象成一个小应用的一部分。但是,如果我们的应用变得更加复杂,Store 中的 state 也变得更加复杂,我们需要设计并编写更多的 reducer 和 action,才能达到我们预期的状态管理目的。
Redux 中的状态更新
在 Redux 中,我们只有一个 Store,它维护了整个应用的状态。因此,在更新应用的 state 时,我们必须遵循某些规则:
- 不要直接修改 Store 中的 state。这样我们无法追踪状态的变化,难以 debug。我们应该通过 action 来修改 state。
- action 应该是一个对象。属性包括 type 表示某个 action 类型,以及其它需要传递给 reducer 函数的数据。
- 不要在 reducer 函数中进行异步操作,这会导致 state 的变化是不确定的。任何对异步操作的尝试都应该交给中间件来处理。
下面是一个简单的例子,我们通过 action 更新 Store 中的 state:
// 定义 action const incrementAction = { type: 'INCREMENT' }; // 更改 state store.dispatch(incrementAction);
在大多数情况下,我们会将 action 定义为函数,这些函数被称为 action creator:
function increment() { return { type: 'INCREMENT' }; } store.dispatch(increment());
Redux 中的中间件
在复杂的应用场景中,Redux 中的 reducer 仅仅是更新应用 state 的一种方式。我们可能需要执行一些额外的操作,如异步 I/O 操作,路由等。在 Redux 中,我们可以通过中间件来扩展 Redux 的功能。
中间件是一个函数,它拦截 action,并在处理流程中扩展其功能。redux-thunk 和 redux-saga 是 Redux 最受欢迎的中间件之一,它们可以用来处理异步 I/O 操作。
下面是一个简单的中间件示例:
-- -------------------- ---- ------- ----- ------ - ----- -- ---- -- ------ -- - -------------------------- -------- ----- ------ - ------------- ----------------- ------- ------------------ ------ ------- -- ----- ----- - ------------ -------- ----------------------- --展开代码
我们可以在控制台中看到每个 action 的调用日志:
dispatching {type: 'INCREMENT'} next state {value: 1}
实践中的 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