这里我们来看看官方网站是如何介绍 Vuex 的:
提示
这是与 Vue 3 匹配的 Vuex 4 的文档。如果您在找与 Vue 2 匹配的 Vuex 3 的文档,请在这里查看。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
让我们从一个简单的 Vue 计数应用开始:
const Counter = {
// 状态
data () {
return {
count: 0
}
},
// 视图
template: `
<div>{{ count }}</div>
`,
// 操作
methods: {
increment () {
this.count++
}
}
}
createApp(Counter).mount('#app')
这个状态自管理应用包含以下几个部分:
以下是一个表示“单向数据流”理念的简单示意:
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux 和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
如果你想交互式地学习 Vuex,可以看这个 Scrimba 上的 Vuex 课程,它将录屏和代码试验场混合在了一起,你可以随时暂停并尝试。
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
我们可以使用下面的方式来安装 Vuex:
使用 npm 包管理器安装:
npm install vuex@next --save
使用 Yarn 包管理器安装:
yarn add vuex@next --save
在这里我们选择 npm 安装:
安装完成之后,就可以开始使用 vuex 了。我们先在 /src
中创建 /store
文件夹,并创建 js 文件 action.js
, index.js
, mutations-types.js
, mutations.js
。如下图所示:
新建目录结构如上图。从文件名可以看出其对应的作用,下面我们来编写对应的代码。
即图中的 action.js
文件。里面存放着各个 action,每个 action 都对应一个 mutation,下面是 action.js
文件的完整代码:
export default {
// 展示下拉框
showDropList({ commit }, data) {
commit("SHOW_DROP_LIST", data);
},
// 更新编辑区内容
updateContent({ commit }, data) {
commit("UPDATE_CONTENT", data);
},
// 更新选择的值
updateSelectValue({ commit }, data) {
commit("UPDATE_SELECTED_VALUE", data);
},
// 更新菜单状态
updateMenuStatus({ commit }, data) {
commit("UPDATE_MENU_STATUS", data);
},
// 执行命令
execCommand({ commit }, data) {
commit("EXEC_COMMAND", data);
},
// 获取节点位置
getNodePosition({ commit }, data) {
commit("NODE_POSITION", data);
},
// 切换视图
changeView({ commit }, data) {
commit("CHANGE_VIEW", data);
},
};
为了让整个项目的 mutation 可以很方便的查看和管理,我们将全部的 Mutation 事件类型的名字使用常量来代替,并将它们单独存放在一个文件中,对外提供导出接口。
mutations-types.js
代码内容如下:
export const SHOW_DROP_LIST = "SHOW_DROP_LIST";
export const UPDATE_CONTENT = "UPDATE_CONTENT";
export const UPDATE_SELECTED_VALUE = "UPDATE_SELECTED_VALUE";
export const UPDATE_MENU_STATUS = "UPDATE_MENU_STATUS";
export const EXEC_COMMAND = "EXEC_COMMAND";
export const CHANGE_VIEW = "CHANGE_VIEW";
export const NODE_POSITION = "NODE_POSITION";
在 action 中提交的 mutation 的名字,和这里的名字是保持一致的。
在这个文件中,定义了很多 mutation 用于改变 store 中的数据。在 action 中提交的 mutation 都将在这里真正生效。
首先,我们需要从mutations-types
中导入 mutation 的事件名称。这里使用对象结构的方式操作:
mutations.js
代码内容如下:
// 导入事件类型
import {
// 下拉框事件类型
SHOW_DROP_LIST,
// 更新编辑区内容事件类型
UPDATE_CONTENT,
// 更新选择的值事件类型
UPDATE_SELECTED_VALUE,
// 更新菜单状态事件类型
UPDATE_MENU_STATUS,
// 执行命令事件类型
EXEC_COMMAND,
// 获取节点位置事件类型
NODE_POSITION,
// 切换视图事件类型
CHANGE_VIEW,
} from "./mutations-types";
// 定义处理各个事件类型的回调函数
export default {
[SHOW_DROP_LIST]({ menuBar }, data) {
// 下拉框事件类型对回调函数
for (let menu in menuBar) {
if (menuBar[menu].showDropList !== undefined) {
if (data && data.name === menu) {
menuBar[menu].showDropList = data.display;
} else {
menuBar[menu].showDropList = false;
}
}
}
},
[UPDATE_CONTENT](state, data) {
// 更新编辑区内容事件类型
state.content = data;
},
[UPDATE_MENU_STATUS]({ menuBar }, data) {
// 更新菜单状态事件类型
if ("all" in data) {
for (let menu in menuBar) {
menuBar[menu].status = data.all;
}
return;
}
for (let name in data) {
if (menuBar[name].showStatus) {
menuBar[name].status = data[name];
} else {
menuBar[name].status = "default";
}
}
},
[UPDATE_SELECTED_VALUE]({ menuBar }, data) {
// 更新选择的值事件类型
menuBar[data.name].value = data.value;
},
[EXEC_COMMAND](state, data) {
// 执行命令事件类型
state.command = data;
},
[CHANGE_VIEW](state, data) {
// 切换视图事件类型
state.sourceView = data;
},
[NODE_POSITION](state, data) {
// 获取节点位置事件类型
state.position = {
top: data.top,
right: data.right,
bottom: data.bottom + document.body.scrollTop,
left: data.left,
};
},
};
这个文件是保存数据的地方,状态数据的改变最终都会作用到里面的数据。 store 对象的实例也由这个文件导出。
index.js
的代码内容如下:
import { createStore } from "vuex";
// 导入一些依赖模块
import mutations from "./mutations";
import action from "./action";
import Menu from "@/config/menu";
import Config from "@/config/index";
let menuBar = {}; // 菜单栏对象
let menu = Menu.getMenu(); // 获取全部菜单项配置信息
let config = Config.getConfig(); // 获取全部配置项
let viewMenu = config.viewMenu; // 获取可见的菜单项
viewMenu.forEach(function (name) {
menuBar[name] = {};
// 是否有下拉框
if (menu[name].dropList) {
menuBar[name].value = "";
menuBar[name].showDropList = false;
} else {
// 是否展示状态
if (menu[name].showStatus) {
menuBar[name].showStatus = true;
}
menuBar[name].status = "default";
}
});
export default createStore({
state: {
// 编辑区域内容
content: config.container.content,
// 菜单栏对象,包含菜单项状态数据
menuBar,
// 是否源码视图
sourceView: false,
//执行的命令
command: {
name: "",
value: "",
},
//存放位置信息
position: {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
},
getters: {},
mutations: mutations,
actions: action,
modules: {},
});
以上所有操作完成之后,我们的 vuex 就配置完成了。不过目前还不能在组件中使用,因为还没将它注入到组件中去。在组件中使用 vuex 的方式有两种,一种是在每个单独的组件中都实例化一个 store 对象,然后对 store 实例对象进行操作。另外一种方式是通过在根组件处注入 store 实例,其他所有子组件可通过 this.$store
来访问到 store 实例对象,并进行操作。推荐使用第二种方式,更加简单方便。
我们修改一下入口文件 src/main.js
,注册 Vuex 实列 srore 对象:
import { createApp } from 'vue'
import App from './App.vue'
import store from './store';
createApp(App).use(store).mount('#app')
ok,这样我们的项目就引入了 Vuex 了。