vuex是什么?
-
Vuex
是一个专为Vue.js
应用程序开发的状态管理库,可以用于各种复杂组件之间的通信,属于vue
全家桶相关的生态工具链
什么情况下需要使用vuex?
- 如果开发过程中存在各种组件或者很多复杂的组件,且各种组件之间需要进行相互通信(个人认为即如果把某一个页面拆成各种小组件时,便需要考虑使用
vuex
进行通讯,提升组件通信间的开发效率)
如果不使用Vuex通信还能使用什么?
- 父子组件通讯
- 兄弟组件通信
- 隔代组件通讯
vuex的工作流程
- 图示如下:
- 工作流程文字描述如下:
- 在
vue
组件里面,通过dispatch
来触发actions
提交修改数据的操作。 - 然后再通过
actions
的commit
来触发mutations
来修改数据。 -
mutations
接收到commit
的请求,就会自动通过Mutate
来修改state
里面的值。 - 最后由
store
触发每一个调用它的组件的更新
如何使用Vuex
使用
vue-cli
生成项目时,可以选择集成vuex
到项目中。此时,vue-cli
会自动安装vuex
,并在src
文件夹下生成store文件下的 store.js
完成vuex
的引入和配置。-
手动引入安装
npm install vuex //或者 yarn add vuex
(备注)如果是
vue-cli
创建的项目,且在最开始集成了vuex
则不需要进行如下配置,否则请参考如下配置
vuex的五大核心要素
Start
-
state
表示状态,类似于vue
中的data
,用来存放状态值,里面存放的数据是响应式的
// 代码参考
// store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
text: "hello word",
},
getters: {},
mutations: {},
actions: {},
modules: {},
});
//某组件代码(其他页面访问方式相同)
<template>
<div>
<h2>我是某组件代码</h2>
//页面通过 $store.state.text 可以获取相关值
<h3>这是vuex中的数据:{{ $store.state.text }}</h3>
</div>
</template>
<script>
export default {
name: "allHome",
methods: {
getVuex() {
//这里通过 this.$store.state.text 可以获取相关值
console.log("这是vuex中的数据:" + this.$store.state.text);
},
},
mounted() {
this.getVuex();
},
};
</script>
<style></style>
Getter
- 类似于 Vue 中的 计算属性(可以认为是 store 的计算属性),getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
// 代码参考 ... 表示省略这里的代码(因为这里代码是相同的),省略部分请参考对照上方代码
// store.js
....
state: {
students: [
{ id: 0, name: "张三", age: 25 },
{ id: 1, name: "李四", age: 30 },
{ id: 2, name: "王二", age: 19 },
{ id: 3, name: "麻子", age: 18 },
],
},
getters: {
greaterAgeCount: (state) => {
return state.students.filter((s) => s.age > 20).length;
},
},
//也可以采取下面这种写法,不过这种写法需要通过方法访问
// getters: {
// greaterAgeCount: (state) => {
// return function (age) {
// return state.students.filter((s) => s.age > age).length;
// };
// },
// },
....
});
//某组件代码(其他页面访问方式相同)
<template>
<div>
<h2>我是某组件代码</h2>
<h3>这是vuex中的 getter 返回值的数据:{{ stundetConut }}</h3>
<!-- 也可以采用方法的方式访问,参考如下 -->
<!-- <h3>
我是属性写法的得到的getter 返回值 {{ $store.getters.greaterAgeCount(10) }}
</h3> -->
</div>
</template>
<script>
export default {
name: "allHome",
computed: {
stundetConut() {
return this.$store.getters.greaterAgeCount;
},
},
// 也可以采用方法的方式访问,参考如下
// methods: {
// gevue() {
// console.log(this.$store.getters.greaterAgeCount(10));
// },
// },
// mounted() {
// this.gevue();
// },
};
</script>
<style></style>
Mutaion
- (只能处理同步)可以理解为官方规定修改
Vuex
的store
中的状态的唯一方法,类似于事件,如果要修改state
的值,需要通过它进行,才能更好的使用devtools
追踪状态变化。当然你也可以不在这里修改,因为这里只是官方推荐的规范和vuex
架构设计的概念。
// 代码参考 ... 表示省略这里的代码(因为这里代码是相同的),省略部分请参考对照上方代码
// store.js
....
mutations: {
//state 表示数据 value 表示传递过来的值
addText(state,value) {
state.text = state.text + value;
},
},
....
});
//某组件代码(其他页面访问方式相同)
<template>
<div>
<h2>我是某组件代码</h2>
<h3>这是vuex中的数据:{{ $store.state.text }}</h3>
<button @click="changeVue('good')">点击变化vuex值</button>
</div>
</template>
<script>
export default {
name: "allHome",
methods: {
getVuex() {
console.log("这是vuex中的数据:" + this.$store.state.text);
},
changeVue(text) {
/**
* 这里通过 this.$store.commit("a",b)可以触发对应事件 a 表示需要vuex中 mutations定义的 事件名,b表示需要传递给vuex的值,如果是多个值请使用对象表示
* text{
name:"aaa"
age:18
}
*/
this.$store.commit("addText", text);
},
},
mounted() {
this.getVuex();
},
};
</script>
<style></style>
Action
- (可以处理异步),这里可以理解为
action
是用来分发将要进行的操作事件的,它将要做的事情,提交给mutation
,再由mutations
进行修改。 - 当然这里也可以直接修改
state
的值(不过在vue2
的vuex
中官方不是很推荐,vue3
中的推荐pinia
状态管理库已经是使用这个修改State
值了)
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
//某组件代码(其他页面访问方式相同)
<template>
<div>
<h2>我是某组件代码</h2>
<div>{{ $store.state.count }}</div>
<!-- 这里使用 $store.dispatch('a') 触发 mutation 中的事件,再由 mutation 进行修改,其中 a 表示mutation 中的事件名字, -->
<button @click="$store.dispatch('increment')">修改count</button>
</div>
</template>
<script>
export default {
name: "allHome",
};
</script>
<style></style>
Module
- 用于在存在很多状态值的时候,进行模块划分的,特别是在大型应用中,如果使用单一状态树(只写一个文件),应用的所有状态会集中到一个比较大的对象。可能会导致代码不好维护和store 对象臃肿。这时我们可以将 store 分割为模块(module),每个模块拥有自己的
state
、getters
、mutations
、actions
、甚至是嵌套子模块——从上至下进行同样方式的分割。
// 主状态store.js
import Vuex from 'vuex';
import moduleA from './modules/modulesA';
import moduleB from './modules/modulesB';
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
moduleA
moduleB,
},
});
// 拆分的模块状态一 moduleA.js
export default {
//这里使用的命名空间,避免内部如果存在名字一样方法的时候触发其它模块的同名方法
//如果使用的命名空间后,如果要触发模块中的的 mutation 就需要加上路径才能调用 store.commit('moduleA/print');
namespaced: true,
state: () => ({
a1: 'aaa',
}),
mutations: {
print(state) {
console.log(state.a1);
},
},
actions: {},
getters: {},
};
// 拆分的模块状态一 moduleB.js
export default {
//这里使用的命名空间,避免内部如果存在名字一样方法的时候触发其它模块的同名方法
//如果使用的命名空间后,如果要触发模块中的的 mutation 就需要加上路径才能调用 store.commit('moduleA/print');
namespaced: true,
state: () => ({
a2: 'bbb',
}),
mutations: {
print(state) {
console.log(state.a2);
},
},
actions: {},
getters: {},
};
vuex辅助函数
- 主要是为了节约代码书写
- (备注)辅助函数具体是否使用,可以根据自己需要进行选择
mapState
// 原写法
<template>
<div>
<span>{{ customerName }}</span>
</div>
</template>
<script>
export default {
computed: {
customerName() {
return this.$store.state.customerName;
}
}
}
</script>
// 使用 mapState 辅助函数写法
<template>
<div>
<span>{{ customerName }}</span>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: mapState({
// 箭头函数可使代码更简练
count: (state) => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: "count",
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState(state) {
return state.count + this.localCount;
},
}),
// 个人推荐这种写法,给 mapState 传一个字符串数组
// 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
// 映射 this.count 为 store.state.count
// computed: mapState([
// // 映射 this.count 为 store.state.count
// 'count'
// ])
}
</script>
mapGetters
// 原写法
<template>
<div>
<span>Shopping Cart ({{ cartItemCount }} items)</span>
</div>
</template>
<script>
export default {
computed: {
cartItemCount() {
return this.$store.getters.cartItemCount;
}
}
}
</script>
// 使用 mapGetters 辅助函数写法
<template>
<div>
<span>Shopping Cart ({{ cartItemCount }} items)</span>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(['cartItemCount'])
}
}
</script>
mapMutations
// 原写法
<template>
<div>
<p>{{ customerName }}</p>
<input type="text" @input="updateName" :value="customerName" />
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Example",
computed: {
...mapState(['customerName'])
},
methods: {
updateName(event) {
this.$store.commit('setCustomerName', event.target.value);
}
}
}
</script>
// 使用 mapMutations 辅助函数写法
import { mapState, mapMutations } from 'vuex';
export default {
name: "Example",
computed: {
...mapState(['customerName'])
},
methods: {
...mapMutations(['setCustomerName']),
updateName(event) {
this.setCustomerName(event.target.value);
}
}
}
mapActions
// 原写法
<template>
<div>
<p>{{ customerName }}</p>
<input type="text" @input="updateName" :value="customerName" />
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Example",
computed: {
...mapState(['customerName'])
},
methods: {
updateName(event) {
this.$store.dispatch('setCustomerName', event.target.value);
}
}
}
</script>
// 使用 mapActions 辅助函数写法
import { mapState, mapActions } from 'vuex';
export default {
name: "Example",
computed: {
...mapState(['customerName'])
},
methods: {
...mapActions (['setCustomerName']),
updateName(event) {
this.setCustomerName(event.target.value);
}
}
}
createNamespacedHelpers
- 如果要使用辅助函数引入
vuex
模块化中的内容,写起来会比较繁琐 - 一般解决方法有如下2两种
// 原写法,引入模块化后相对繁琐
// Component.vue
computed: {
...mapState({
a1: (state) => state.moduleA.a1,
a2: (state) => state.moduleA.a2,
})
},
methods: {
...mapActions({
foo: 'moduleA/foo',
boo: 'moduleA/boo',
}),
}
//解决方法一:可以将模块化的空间名称作为第一个参数传入辅助函数,这样就会自动添加 前缀名 于是上面的例子可以简化为如下写法:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
// 解决方法二:通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。进行自动绑定,使其中能自动查找绑定的模块名
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
在vuex请求数据
-
之前网上看到有这种写法,本人不是很喜欢这种写法,建议还是按照正常接口方案写
//vuex 状态文件 import Vue from "vue"; import Vuex from "vuex"; import axios from "axios"; Vue.use(Vuex); export default new Vuex.Store({ state: { users: [], isLoading: false, }, mutations: { setLoadingTrue(state) { state.isLoading = true; }, setLoadingFalse(state) { state.isLoading = false; }, setUsers(state, users) { state.users = users; }, setCustomerName(state, name) { state.customerName = name; } }, actions: { getUsers(context) { context.commit('setLoadingTrue'); axios.get('/api/users') .then(response => { context.commit('setUsers', response.data); context.commit('setLoadingFalse'); }) .catch(error => { context.commit('setLoadingFalse'); // handle error }); } } }); //组件文件 <template> <div> <div id="spinner" v-if="isLoading"> <img src="spinner.gif" /> </div> <ul v-else> <li v-for="(user, index) in users" :key="index" >{{ user }}</li> </ul> </div> </template> <script> import { mapActions, mapState } from "vuex"; export default { computed: { ...mapState([ 'isLoading', 'users' ]) }, methods: { ...mapActions(['getUsers']) }, created() { this.getUsers(); } } </script>
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 # 产品模块