目录
- 1.背景
- 2.一个常见的多个组件共享状态的问题
- 3.MVC 架构
- 4.Flux 架构
- 5.MVC 与 Flux 的对比
- 6.Vue.js 的 Vuex 方案
- 7.使用 Vuex 的正确姿势
- 8.只有 Vuex 还不够...
- 9.结语
- 10.推荐阅读
1.背景
Web 页面开发的应用化趋势
随着 Web 能力的不断提升,用户的 Web 页面体验的要求,要求像原生一样,无刷新、离线缓存等和原生应用的功能。
Web 开发已经进入组件化时代
2.一个常见的多个组件共享状态的问题
在下图中播放器组件上点赞以后,如何同步更新其他地方有关这个视频的点赞信息,比如右侧信息区组件、详情页?
可能的解决方案
- 方案 1:刷新页面
- 方案 2:把所有涉及到的接口都重新请求一遍
- 方案 3: 直接更改 Dom
...
这些方案的问题
- 方案 1: 体验差,会丢失用户的当前操作状态
- 方案 2: 多余的请求,网络请求需要耗时
- 方案 3:很难维护,也容易遗漏
问题的根源:
不同视图上的相同数据没有一个统一的来源,如果点赞信息能统一存储,只需要更新同一个地方,并且都从这个地方获取信息显示到视图上,就不会有这个问题了
3.MVC 架构
MVC 模式(Model–view–controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
比如点赞信息作为一个 Model, 每个视图都可以读取这个 Model,然后视图通过通知 Controller 去更新 Model,视图通过监听 Model 的变化再进行同步。
4.Flux 架构
Flux 是由 Facebook 提出的,用于组织应用的一种架构,它基于一个简单的原则:数据在应用中单向流动。这就是所谓的“单向数据流”
,简单的记法是把数据比作鲨鱼:鲨鱼只能向前游。
Flux 试图通过强制单向数据流来解决这个复杂度。在这种架构当中,Views 查询 Stores(而不是 Models),并且用户交互将会触发 Actions,Actions 则会被提交到一个集中的 Dispatcher 当中。当 Actions 被派发之后,Stores 将会随之更新自己并且通知 Views 进行修改。这些 Store 当中的修改会进一步促使 Views 查询新的数据。
5.MVC 与 Flux 的对比
EventBus 数量
- MVC: 一个 model 一个
- Flux: 只有一个
视图同步数据方式
- MVC:view 监听 model 的事件
- Flux: model 的改变自动同步到视图
查询和写入数据的区别
- MVC:Model 暴露给 View,View 有可能会更新 model,像 backbone 就可以在 View 里调用
this.model.set()
- Flux: View 从 Store 获取的数据是只读的, Stores 只能通过 Actions 被更新
数据改变的可感知性
- MVC:由于有多个 EventBus,所以很难去定位改变了 model 的具体来源
-
Flux: 任何状态的变化都必须通过 action 触发,而 action 又必须通过 dispatcher 走,所以整个应用的每一次状态变化都会从同一个地方流过
6.Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
借鉴了 Flux、Redux、和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
多个组件共享状态的问题
由于 Flux 架构有多个 store,我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
问题的解决
- 对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
- 对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
最终解决方案
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。
这就是 Vuex 背后的基本思想。
7.使用 Vuex 的正确姿势
背景:前端数据来源的多样性
不同端的数据来源多样性
- 服务端接口
- 前端用户的输入
- 客户端信息(比如设备信息等)
同一端数据来源的多样性
如果服务端的接口不是基于 restful 设计,或者接口内容是一些聚合的接口,比如服务端设计一个获取用户信息接口,返回了用户的 vip 信息、关注信息、粉丝信息等,同时设计了分别的获取 vip 信息接口、关注信息、粉丝信息接口,这样同一个内容在服务端这一端会有不同的来源。
如果前端组件之间对同一个状态的读取和操作没有做到复用,假设一个评论列表组件和弹幕组件之间,都用到了用户的评论信息,如果没有做到统一读取和更新,就会导致同样的状态信息有两个数据来源,最终导致同步问题。
数据读取,保证读取单一状态来源
需要保证不同的 View 或者组件读取某个状态信息都是从同一个地方读取,所以,回归到 Vuex 设计的本意上来,我们应该把同一状态信息集中管理。
数据存储,从聚合到原子
思路
- 服务端尽量提供原子化的接口,比如 restful 风格
- 前端数据层设计成原子化,不存储聚合信息,所有的聚合信息都拆开存储
前端数据存储原子化设计
下面是一个组队加速需求的状态设计:
{
users: {
[uid]: {
userid: String,
nickName: String,
avatar: String,
// 存储team的索引
teams: String[]
}
},
teams: {
[groupId]: {
teamId: String,
taskId: String,
// 存储user的索引
users: String[]
}
},
tasks: {
[taskID]: {
taskID: String,
name: String,
gcid: String,
status: String,
suffix: String,
teams: String[]
}
},
increases: {
[gcid]: {
[userId]: {
uploadAmount: String,
increaseAmount: String
}
}
}
}
数据结构设计原则
- 避免嵌套数据,数据结构扁平化
- 双向关系存储索引,适当的冗余来提升读取的效率
8.有了 Vuex 还需要什么?
数据校验设计
Vuex 对 state 的写入没有统一的校验机制,复杂的单页面应用应保证 state 内存储的数据结构和类型都是一致的,可以在 Vuex 这一层做一层类似组件的 props 验证那样的验证机制。
数据缓存设计
需要一套完整的缓存机制,包括缓存的读、写及过期清理逻辑。
- 持久化存储
- localStorage
- 会话期的缓存
- 内存
- sessionStorage
- 跨页面会话期存储
- localStorage
数据同步设计
- 独立同步
- 合并同步
- 聚合同步
数据同步频率控制设计
就像一窝蜂的人去排队看演出,队伍很乱,看门的老大爷每隔 1 秒,让进一个人,这个叫 throttle,如果来了这一窝蜂的人,老大爷一次演出只让进一个人,下次演出才让下一个人进,这个就叫 debounce
- throttle:节流,控制单位时间内对特定内容的同步请求的最大次数
- debounce:去抖,连续的对同一个内容的同步请求我们只需要执行一次