你甚至可以用 Vue Router 来做状态管理
引言
Vuex 是 Vue 生态中一个流行的状态管理工具,它与 React 生态中的 Redux 类似,有简单优雅的设计。
然而事情总不会完美,要体验 Vuex/Redux 的优雅却是需要付出代价的:
- 可能面临大量的模板代码
- 可能需要手动管理 Vuex/Redux 中数据的生命周期
- 可能过多地暴露 view 的数据细节(因为 Vuex/Redux 上的数据是全局可访问的)
Vue Router 是 Vue 官方钦定的 SPA 路由实现。对它加以“妙用”,甚至可以实现简易的类似 Vuex 的状态管理。
URL 作为初始化状态
在讨论 SPA 之前,我们先回到传统的多页面应用场景。
在多页应用中,无论对于服务端还是客户端,页面 URL 是个非常重要的初始化信息。因此页面之间的流转,状态主要都是通过 URL 来进行传递。这是一个非常简单且有效的机制,带来两个好处:
- 用户刷新页面后,页面可以回到当前页面的初始状态
- 使用相同的 URL 可以获得相同的页面初始状态,有助于问题排查
SPA 中利用 URL 来管理状态
如果在 SPA 中使用 URL 来传递页面之间(或组件之间)的流转信息,还能额外获得的好处:
- 获得了一个很显式的、很容易 debug 的机制,来解除页面之间、组件之间交互的耦合
- 将长流程分解为可以“暂存”的短流程,降低流程逻辑的复杂性
- 获得了组件间的通信能力
- URL 与页面的生命周期同步
解耦组件交互
给一个简单的页面,包含一个“搜索框”和“结果列表”:
两者的组织关系可以有:
- 直接引用:Search 组件获得 Results 组件的“引用”,直接发送 keyword 信息来命令 Results 组件获取查询列表;这是一种很差的设计方式,组件耦合严重,Results 还泄露了内部抽象
- 有状态的容器组件:Search 和 Results 组件由容器组件管理,容器组件获得 Search 组件输出的 keyword 信息,再输出给 Results 组件;这很不错,是典型的组件通信方式
路由:Search 组件将 keyword 发送到 URL 上,Results 组件响应 URL 的变化,获得其中的 keyword 信息;这种方式的好处是
a. 无需一个有状态的容器组件
b. keyword 信息不在内存中,而是直观地展现在 URL 上
长流程拆解为短流程
给一个长业务流程,其中包含子流程 A B C:
流程之间的跳转需要额外的信息,例如 A → B 需要 order id,B → C 需要 customer id。如果跳转信息记录在内存中,会导致 B C 流程无法单独测试,都必须先从 A 流程开始,依次流转到 B C。
若将 order id 和 customer id 记录在 URL 中,则流程 A B C 都可以进行单独测试,降低了整个流程的测试难度。
组件间通信
在使用 Vue Router 的项目中,任何组件都可以通过 this.$route 和 this.$router 访问当前路由和路由器。前者包含了 URL 中的参数信息,后者提供了修改 URL 的方法。借助于两者,组件之间可以实现通信。
使用 Vue Router 来管理状态
整体思路与 Vuex/Redux 是一致的:
由于涉及到 URL 读写,Vue Router 的方案中需要额外处理状态的序列化和反序列化问题。对于使用 query string 来保存状态的场景来说,好消息是 Vue Router 支持覆盖默认的 parseQuery stringifyQuery 以应对状态的初始化,坏消息是 this.$router.push/replace 一个 query object 时,需要确保刷新页面后可以获得同样的 query object 内容不变,即
_.isEqual(queryObject, parseQuery(stringifyQuery(queryObject))) === true
适用场景与局限性
使用 Vue Router 来做状态管理,本质上与 Vuex/Redux 的数据流理念是一致的。需求由强到弱,甚至到不适用的场景依次有:
- 需要刷新页面保留状态的场景,URL 是比 LocalStorage Cookie 等更简单更易用的方式
- 需要页面/组件间简单的通信,URL 仍是一种简单有效的选择
- 状态数据大、变化频度高的场景,使用 URL 不仅会让 URL 难读,而且页面性能也会受到影响