vuex | 应用场景
- 用于【共享状态】较多,经常被在各处修改的【大型项目】
- 用于大中型单页应用,简单的 spa 可以使用【中央数据总线 bus 】来管理数据(bus也是一个vue实例)
- 如果不是多人协作或非常复杂的项目,一个简单的 store 模式就足够了
store | 是全局共享状态
- 把组件的共享状态抽取出来,以一个全局单例模式管理
- 组件树构成了一个巨大的“视图”,任何组件都能获取状态或者触发行为
- 通过定义和隔离状态管理,代码将会变得更结构化且易维护
store | 是响应式全局变量
- Vuex 的【状态存储是响应式的】,若 修改store(只能用 commit 或 action 方式),那么相应的组件也会更新数据
commit 和 action 操作
- 应用层级的状态应该集中到单个 store 对象中
- 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation
- 异步逻辑都应该封装到 action 里面
vuex 目录结构
- 一般,一个简单的 store 文件即可
- 大型多人开发应用,Vuex 相关代码要分割到模块中,如下
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
严格模式(不建议设置)
- 设置 strict: true。不要在发布环境下启用严格模式!
三步跑起第一个 vuex 程序
- 1 新建 store.js 文件
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0
},
}
- 2 在根组件 js 文件中注册 store
import store from "./store";
new Vue({
el: "#app",
store,
router: router,
render: h => h(App)
});
- 3 已经可以在所有的组件中使用了。通过【this.$store.state】的方式引用,但注意,如果要在组件中使用 store 的值,就需要将其声明为【计算属性】予以保存
export default {
data() {
return {};
},
computed: {
count() {
return this.$store.state.count;
}
}
};
基础概念的完全理解
state | 存储数据
getters | 加工数据
- 加工并获取派生数据,可以理解为 store 中的计算属性
- 一个Gette中也可以接受另一个Getter
- 在组件中使用时,可将其值保存在组件的计算属性中
//声明
getters: {
countText: state => {
return "the count is " + state.count;
}
},
//调用
//在组件中保存 getters 中的数据
computed: {
countText() {
return this.$store.getters.countText;
}
},
mutations | 修改 state 的唯一方式
- 更改 state 的方法,也是唯一的方法
- ==mutations必须是同步执行==
- 在组件中,==通过 this.$store.commit 方法调用==
- commit 方法第一个参数为mutations方法名,第二个参数可以是一个对象
//声明
mutations: {
setToken(state, newToken) {
state.data.token = newToken;
},
//向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)
//使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数
incrementByAnything(state, payload) {
state.count += payload.more;
}
},
//调用
mounted() {
this.$store.commit("setToken", "bbbbb");
},
actions | 允许异步操作
- action 提交的是 mutation,不能直接变更状态
- action ==可以包含任意异步操作==
- action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,它可以避免写 const that = this 这样的代码
- 在组件中,==通过 this.$store.dispatch 方法调用==
- dispatch 方法第一个参数为mutations方法名,第二个参数可以是一个对象
- 在一个 actions 方法中,可以调用另一个 actions 方法
//先声明mutations
mutations: {
setToken(state, newToken) {
state.data.token = newToken;
},
nameToUppercase(state, friend) {
state.data.fullName = (state.data.fullName + friend).toUpperCase();
},
}
//声明actions,其中只能使用mutations
//actions中可以包括异步操作
//actions方法接受两个参数,第一个可以是对象,第二个是传参(可以是对象)
actions: {
nameToUppercase({ commit,sate }, friend) {
setTimeout(() => {
commit("nameToUppercase", friend);
}, 1500);
},
//也可以这样写,传入vue组件的实例 context
nameToUppercase(context, friend) {
setTimeout(() => {
context.commit("nameToUppercase", friend);
}, 1500);
}
}
//在组件中调用
methods: {
uppercaseName() {
this.$store.dispatch("nameToUppercase", "gs");
},
}
//在一个 actions 方法中,可以调用另一个 actions 方法
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
****==利用 async / await 实现组合 action==****
- 通过使用 async / await 语法和 promise 对象,实现异步操作的同步写法
- 如下,实现2s后给name添加"789",再过1s后将英文字母大写
//在store中定义mutations方法
mutations: {
nameToUppercase(state, friend) {
state.data.fullName = (state.data.fullName + friend).toUpperCase();
},
nameAddmore(state, str) {
state.data.fullName = state.data.fullName + str;
}
}
//在store中定义actions方法
actions: {
async nameAddmore({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("add 789");
commit("nameAddmore", "789");
resolve()
}, 2000)
})
},
async nameToUppercase({ commit, dispatch }, friend) {
await dispatch('nameAddmore');
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("uppercase name");
commit("nameToUppercase", friend);
resolve()
}, 1000)
})
}
}
//在组件中调用,可见已经隐藏了所有内部实现
methods: {
uppercaseName() {
this.nameToUppercase("gsaaaa").then(() => {
console.log("all completed");
});
},
}
//执行结果
//2s打印 add 789,显示 zhangkai789
//再1s打印 uppercase name 显示 ZHANGKAI789
//最后打印 all completed 显示 ZHANGKAI789
mapActions 辅助函数的映射
- 在组件中使用时,需要引入对象 import { mapActions } from "vuex"
- 如下例,将 this.nameToUppercase() 映射为 this.$store.dispatch('nameToUppercase')
- 这样,在组件中的其它方法中,就可以方便引用了
//声明
actions: {
nameToUppercase({ commit,sate }, friend) {
setTimeout(() => {
commit("nameToUppercase", friend);
}, 1500);
},
}
//在组件中调用
methods: {
...mapActions(["nameToUppercase"]),
//在其它方法中调用映射
uppercaseName() {
// do something...
this.nameToUppercase("gsss");
},
}
【...mapActions语法报错的问题】
- 原因是缺乏对 es6 语法 rest-spread 的支持,解决办法如下
#安装依赖
npm install --save-dev babel-plugin-transform-object-rest-spread
#在 .babelrc 配置文件中新增
{ "plugins": ["transform-object-rest-spread"] }
Module | 模块
更多功能参考
- vuex 允许我们将 store 分割成模块
- 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
- 对每个模块设置 namespaced 属性,可以防止多个模块的方法重名。因为如果不设置,除了 state ,mutation 等方法会注册在主模块上
- state 的模块调用方式,例如zk模块:this.$store.state.zk.name
- mutations 的模块调用方式为【目录式】,例如模块zk:this.$store.commit("zk/setName", "lili")
//moduleA
const moduleA = {
namespaced: true,
state: {
name: "zk",
age: 22
},
mutations: {
setName(state, newName) {
state.name = newName;
},
},
}
export default moduleA;
//moduleB
const moduleB = {
namespaced: true,
state: {
name: "gs",
age: 18
},
mutations: {
setName(state, newName) {
state.name = newName;
},
},
}
export default moduleB;
//主模块
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './moduleA'
import moduleB from './moduleB'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
zk: moduleA,
gs: moduleB
}
})
//在Vue根实例上注册 store
import store from "./store";
new Vue({
el: "#app",
store,
router: router,
render: h => h(App)
});
//在组件中使用
computed: {
zkName() {
return this.$store.state.zk.name;//注意 state 的模块调用方式
},
gsName() {
return this.$store.state.gs.name;//注意 state 的模块调用方式
}
},
methods: {
setZkName() {
this.$store.commit("zk/setName", "lili");//注意 mutations 的模块调用方式为【目录式】
},
setGsName() {
this.$store.commit("gs/setName", "bebe");//注意 mutations 的模块调用方式为【目录式】
},
}