Vuex 基础教程系列 🎉
概述
Vuex 是专门为 Vue.js 开发的用于全局状态管理的状态管理库。它是对 Flux 架构的实现工具之一,类似的例如 redux。与 redux 不同的是 Vuex 中存储的状态具有响应式特性。
【何为全局状态?】
- 多个组件共享一个状态。
- 一个状态会被多个组件变更。
通常而言,对于跨组件的状态共享和变更,我们常用的手段主要有以下几种:
- 父子孙组件层层传递、显得会很繁琐。
- 兄弟组件会将共享状态抽离到父级组件中,并通过事件来变更状态。
- 不相邻组件或者是具有跨越层级较多的父组件,一般就会将状态直接挂载在 Vue 的根应用实例上。
不论以上的那种方式,它们都有自己的繁琐性、模式的脆弱性以及带来的代码难以维护灯问题。
【Vuex 的全局状态管理思想】
总之、如果与组件层级具有耦合关系,那么共享状态的管理便具有局限性。为此,Vuex 采用了集中式存储管理应用所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简而言之、就是将会被多个组件共享的状态抽取出来,以一个全局的单例模式进行管理,在这种方式下,组件树构成了一个巨大的视图,但是不论树的那个节点,都可以轻易的从全局单例的状态管理中读取或变更状态。
通过将“视图”与“状态”强制隔离,从而让我们的代码变得更结构化且易维护,同时也会让我们的应用更加健壮。
从一个例子走起!
以“ Vuex 的思想”为灯塔,让我们来一步步走近它。
在学习 Vue 基础的时候我们说过 Vue 应用的根实例的 data 属性可以是一个对象,但是普通组件的 data 属性必须要是一个函数,其原因就是防止错误的用法将一个状态被多个组件共享,从而产生意料之外的影响。
现在,基于 Vuex 的全局状态管理思想,我们要反其道而行,主动将多个组件共享的状态抽离出来,进行集中式管理:
const blogData = {
title: 'blog title',
post: 'blog content'
};
Vue.component("blog-item", {
name: 'BlogItem',
data() {
return { blogData }
}
});
Vue.component("blog-content", {
name: 'BlogContent',
data() {
return { blogData }
}
});
现在如果有其它的组件或者行为更改了 blogData 中的值,那么 blog-title 与 blog-content 都将自动地更新它们的视图。
虽然我们通过非常简单的方式实现了跨组件、多组件的数据共享,但是这也为调试带来了噩梦,因为我们的应用在任何时间、任何部分都可能会变更 blogData 的值,我们无法准确的定位正在发生变更的地方,因此也无法留下变更过的记录。
为了解决这一问题,我们必须对共享的数据进行规范,例如采用一个简单的 store 模式
var store = {
debug: true,
state: {
blogData: {
title: 'blog title',
post: 'blog content'
}
},
setBlogDataAction(newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.blogData = { ...this.state.blogData, ...newValue }
},
clearBlogDataAction() {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = {
title: '',
post: ''
}
}
}
现在 store 中的 state 变更,必须要基于自生的 action 去管理,这样的好处就是有一个统一的状态管理方法,我们可以在这个方法中基于状态变更的前后提供更多的扩展能力,例如记录状态变更的日志。
注意,你不应该在 action 中 替换原始的状态对象 - 组件和 store 需要引用同一个共享对象,变更才能够被观察到。
此外,每个实例/组件仍然可以拥有和管理自己的私有状态。
Vue.component("blog-item", {
name: 'BlogItem',
data() {
return {
blogData: store.state,
privateState: {}
}
}
});
Vue.component("blog-content", {
name: 'BlogContent',
data() {
return {
blogData: store.state,
privateState: {}
}
}
});
下图是对上述方式的图解:
上面的方式还有一个很严重的缺陷,那就是我们还需要手动将状态赋值给组件的 data 属性,从而让组件实例初始化时将其进行代理然后转变为响应式对象。
要想解决这个问题,我们可以使用 Vue 提供的了另一个 API Vue.observable ,它赋予了我们自定义创建独立于组件之外的响应式数据的能力。
import Vue from "vue";
let num = 0;
const store = {
state: Vue.observable({ count: 0 }),
setState(action, payload) {
if (action in this.state) {
this.state[action] = payload;
}
},
};
const CompA = Vue.extend({
name: "CompA",
render(c) {
return c(
"button",
{
on: {
click: function () {
store.setState("count", ++num);
},
},
},
["click"]
);
},
});
const CompB = Vue.extend({
name: "CompB",
render(c) {
return c("div", [store.state.count]);
},
});
export default Vue.extend({
name: "HelloWorld",
components: { CompA ,CompB},
});
当然 Vuex 的官方源码中并没有使用这种方式,Vuex 的实现思路非常简单,通过重新创建一个 Vue 实例,借助组件实例来初始化响应式数据,将传入的 state 转变为响应式对象。
var oldVm = store._vm;
store._vm = new Vue({
data: {
$$state: state //此处的 state 就是我们 new Vuex.Store({state:{...}}) 时传入的值。
},
});
if (oldVm) {
//会在下一个 tick 将这个实例销毁。
Vue.nextTick(function () { return oldVm.$destroy(); });
}
以上便是对 Vuex 集中式管理状态思想的非常粗暴简单的实现,但是思想内核是一致的,旨在加深我们对 Vuex 的认识。
来自 Vuex 的约定
Vuex 会以相应的规则保证状态按照一种可预测的方式发生变化,那么这里的规则便是我们要说的“约定”:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而能让一些工具帮助我们更好地了解我们的应用,例如保存状态快照以及时间旅行等。
- 异步的状态变更必须要在 action 中 (commit) mutation。并使用
dispatch来分发 action。
Vuex 的周边
- Vuex 与 Vue-devtools 结合使用,可以提供更高级的调试功能,例如时间旅行(time travel)、状态快照导入导出等。
- vuex-persistedstate :Vuex 的持久化插件,它会使用浏览器的本地存储持久化保存 state,这在一些特殊场景下非常有用,例如购物车、表单填入的值。
- vuex-shared-mutations :持久化存储并可以跨浏览器标签同步状态。
- vuex-i18n:允许你轻松地用多种语言存储内容。让你的应用切换语言时更容易。
什么时候使用 Vuex?
Flux 架构就像眼镜:您自会知道什么时候需要它。 ——Dan Abramov 【redux 作者】
具体来说就是要评估自己产品或项目的大小程度,如果是小型项目,一个简单的 store 模式就足够您所需,但是对于中大型应用,那么非常推荐使用 Vuex 来跨组件层级的方式进行状态管理。
Vuex 还有存在的必要吗?
随着 Vue3 的发布,在见识了 Composition API 强大的灵活性,有很多人开始质疑 Vuex 是否还有存在的必要性。对此,我个人的看法是需要使用 Vuex 解决问题的场景从未改变:
理由很简单,可以简单的概括为以下几点:
- 早在 Vue2.x 版本 Vue 就已经暴露了其底层的响应式系统,例如
Vue.observableAPI,利用它我们可以手动的创建响应式数据。 - Vuex 不单单只是状态管理,它还可以记录日志、追踪变更。
- 活跃的社区,众多开发者的参与,让它更稳定、更可扩展、更强大,更可靠,符合大型应用的状态管理,这是个人封装或小团队封装的状态管理库难以企及的。
- 丰富的周边配套,例如一些状态持久化插件,并且结合
vue-devtools可以获得更好的调试体验,比如时间旅行,再比如状态快照导入导出。
还是那句话,小型项目一个简单的 store 模式或者借助 Vue.observable API 即可,但是中大型项目,则强烈建议使用 Vuex。