1. 概念
明确 vuex 是什么,应用场景,优势
-
是什么:
vuex 是一个 vue 的
状态管理工具,状态就是数据。大白话:vuex 是一个插件,可以帮我们管理 vue 通用的数据 (多组件共享的数据) 例如:购物车数据 个人信息数据
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
-
场景:
某个状态 在
很多个组件来使用 (个人信息)多个组件
共同维护一份数据 (购物车) -
优势:
共同维护一份数据,
数据集中化管理响应式变化
操作简洁 (vuex提供了一些辅助函数)
-
原理图:
2. 搭建vuex环境(多组件数据共享环境)
基于脚手架创建项目,构建 vuex 多组件数据共享环境
创建一个空仓库
-
安装 vuex:npm install vuex@3 或 yarn add vuex@3 -
新建 vuex 模块文件,创建文件:src/store/index.js,专门存放 vuex -
创建仓库Vue.use(Vuex)
创建仓库 new Vuex.Store()
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state }) -
main.js 导入挂载,在main.js中创建vm时传入store配置项//引入store import store from './store' //创建vm new Vue({ el:'#app', render: h => h(App), store })
检验:this.$store
3. 核心概念
明确如何给仓库 提供 数据,如何 使用 仓库的数据
3.1 state 状态
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。
在 state 对象中可以添加我们要共享的数据。
state 状态,即数据, 类似于vue组件中的data
区别:
- data 是组件自己的数据
- state 是所有组件共享的数据
3.1.1 提供数据
//创建仓库
const store = new Vuex.Store({
state: {
count: 101
}
})
3.1.2 使用数据
3.1.2.1 store 直接访问
-
(1) this.$store
模板中: {{ $store.state.xxx }}
组件逻辑中: this.$store.state.xxx
-
(2) import 导入 store
JS模块中: store.state.xxx
3.1.2.2 辅助函数 mapState
mapState是辅助函数,帮助我们把 state 中的数据 自动 映射到 组件的计算属性中
-
导入mapState:import { mapState } from 'vuex' -
数组方式引入 state:mapState(['count']) 或对象方式引入 mapState({newCount: "count"}) (数组方式不常用,了解即可) -
展开运算符映射computed: { //数组写法(常用) ...mapState(['count', 'sum']) //对象写法,若页面上有和state同名的data或者computed数据,可使用对象写法重命名一下,否则用数组写法,用着简洁 ...mapState({countNewName:'count'}), }
3.2 mutations-处理同步
明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据(修改了vue默认也不会报错、监测的,因为监测需要成本),state数据的修改只能通过 mutations,可以通过 strict: true 开启严格模式进行调试,上线的时候不需要开启严格模式,否则影响性能
-
封装 mutation 函数(定义 mutations 对象,对象中存放修改 state 的方法)
const store = new Vuex.Store({ // 严格模式(有利于初学者,检测不规范代码=>上线需要关闭) strict: true, state: { count: 0 }, // 定义mutations mutations: { // 第一个参数是当前store的state属性 //不带参数 addCount (state) { state.count += 1 }, //带参数 addCountNum (state, n) { state.count += n } } }) -
组件中使用commit提交调用 mutations
// 不带参数 this.$store.commit('addCount') // 带参数 this.$store.commit('addCountNum', 参数)
注意:提交参数只能一个,如果有多个参数,包装成一个对象传递
3.2.1 辅助函数 - mapMutations
mapMutations 和 mapState 很像,它是把位于 mutations 中的方法提取了出来,映射到组件 methods 中(用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数)
mutations: {
subCount (state, n) {
state.count -= n
},
}
import { mapMutations } from 'vuex'
methods: {
//数组形式
...mapMutations(['subCount'])
//对象形式
...mapMutations({subCountNewName:'subCount'}),
}
//相当于
methods: {
subCount (n) {
this.$store.commit('subCount', n)
},
}
//调用,数组写法
this.subCount(10)
//调用,对象写法
this.subCountNewName(10)
3.3 actions-处理异步
需求:一秒钟之后,修改 state 的 count 成 666。
说明:mutations 必须是同步的 (便于监测数据变化,记录调试)
-
提供 action 方法
actions: { setAsyncCount (context, num) { // 一秒后, 给一个数, 去修改 num setTimeout(() => { context.commit('changeCount', num) }, 1000) } }注意:actions 不能直接操作 state,操作 state,还是需要 commit mutations
-
页面中 dispatch 调用
this.$store.dispatch('setAsyncCount', 200)
3.3.1 辅助函数 - mapActions
mapActions 是把位于 actions 中的方法提取了出来,映射到组件 methods 中(用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数)
actions: {
changeCountAction (context, num) {
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
}
import { mapActions } from 'vuex'
methods: {
//数组形式
...mapActions(['changeCountAction'])
//对象形式
...mapActions({changeCountActionNewName:'changeCountAction'})
}
// 相当于
methods: {
changeCountAction (n) {
this.$store.dispatch('changeCountAction', n)
},
}
// 调用,数组形式
this.changeCountAction(666)
// 调用,对象形式
this.changeCountActionNewName(666)
3.4 getters-类似计算属性
说明:除了 state 之外,有时我们还需要从 state中 派生出一些状态,这些状态是依赖 state 的,此时会用到 getters
概念:当 state 中的数据需要经过加工后再使用时,可以使用 getters 加工。
-
定义 getters:在store.js中追加getters配置//用于将state中的数据进行加工 getters: { filterList(state){ return state.list.filter(item => item > 5) } }注意:
getters函数的第一个参数是 state
getters函数必须要有返回值
-
访问getters:组件中读取数据- 通过 store 访问 getters: $store.getters.filterList
- 通过辅助函数 mapGetters 映射
computed: { ...mapGetters(['filterList']) }, //页面上调用 {{ filterList }}
3.4.1 辅助函数 mapGetters
用于帮助我们映射getters中的数据为计算属性
import { mapGetters } from 'vuex'
computed: {
//数组写法
...mapGetters(['bigSum'])
//对象写法
...mapGetters({bigSum:'bigSum'}),
},
3.5 总结
备注:mapActions 与 mapMutations 使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。
具体案例:
<template>
<div>
<h1>当前求和为:{{ sum }}</h1>
<h3>当前求和放大10倍为:{{ bigSum }}</h3>
<h3>年龄:{{ age }}</h3>
<h3>姓名:{{name}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<!-- 用了mapActions 和 mapMutations 的话要主动传参,如果不主动传参,传的是默认的event -->
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
name: "Count",
data() {
return {
n: 1, //用户选择的数字
};
},
computed: {
...mapState(['sum', 'age', 'name']),
...mapGetters(['bigSum'])
},
methods: {
...mapActions({incrementOdd: 'sumOdd', incrementWait: 'sumWait'}),
...mapMutations({increment: 'sum', decrement: 'reduce'})
},
mounted() {
console.log("Count", this);
},
};
</script>
<style lang="css">
button {
margin-left: 5px;
}
</style>
-
初始化数据、配置
actions、配置mutations,操作文件store.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, }) -
组件中读取vuex中的数据:
$store.state.sum -
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)或$store.commit('mutations中的方法名',数据)备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch,直接编写commit
具体案例:
index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
/* jia(context,value){
console.log('actions中的jia被调用了')
context.commit('JIA',value)
},
jian(context,value){
console.log('actions中的jian被调用了')
context.commit('JIAN',value)
}, */
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
}
//准备mutations——用于操作数据(state)
const mutations = {
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
//准备state——用于存储数据
const state = {
sum:0 //当前的和
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
Count.vue
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
}
},
methods: {
increment(){
// commit 是操作 mutations
this.$store.commit('JIA',this.n)
},
decrement(){
// commit 是操作 mutations
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
// dispatch 是操作 actions
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
// dispatch 是操作 actions
this.$store.dispatch('jiaWait',this.n)
},
},
mounted() {
console.log('Count',this)
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
4. 模块 module (进阶语法)
由于 vuex 使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越难以维护)
目的:让代码更好维护,让多种数据分类更加明确。
模块拆分: user模块: store/modules/user.js
const state = {
userInfo: {
name: 'zs',
age: 18
}
}
const mutations = {}
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
import user from './modules/user'
const store = new Vuex.Store({
modules: {
user
}
})
4.1 state
尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名
使用模块中的 state 数据:
-
直接通过模块名访问 $store.state.模块名.xxx
开启不开启命名空间都是这样访问的
-
通过 mapState 映射
默认根级别的映射 mapState([ 'xxx' ]):比如访问 user 为 mapState([ 'user' ]),页面上访问 {{user.userinfo}} 【开启不开启命名空间都是这样访问的】//子模块user,开启不开启命名空间都是这样访问的 import { mapState } from 'vuex' computed: { ...mapState(['user']) }, //页面上访问:{{user.userinfo}}子模块的映射 mapState('模块名', ['xxx']) - 需要开启命名空间(在当前子模块内添加 namespaced: true)//user模块: store/modules/user.js export default { //user子模块开启命名空间 namespaced: true, state, mutations, actions, getters }import { mapState } from 'vuex' computed: { //数组写法 ...mapState('user',['userinfo']), //对象写法 ...mapState('user',{userinfo: 'userinfo'}), }, // 使用 // 在页面上直接 {{userinfo}}
4.2 getters
注意:默认模块中的 getters 和 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
子模块未开启命名空间(和主模块访问方式一样)
-
通过 store 直接访问 $store.getters.xxx
-
通过 mapGetters 映射 mapGetters['xxx']
//通过 store 访问 子模块的 getters: $store.getters.filterList //通过辅助函数 mapGetters 映射 computed: { ...mapGetters(['filterList']) }, //页面上调用 {{ filterList }}
子模块开启命名空间
-
直接通过模块名访问 $store.getters['模块名/xxx ']
-
通过 mapGetters 映射
子模块的映射 mapGetters('模块名', ['xxx'])
//user模块: store/modules/user.js export default { //子模块开启命名空间 namespaced: true, state, mutations, actions, getters }//自己直接读取 this.$store.getters['user/filterUserlist'] //通过 mapGetters 映射 import { mapGetters } from 'vuex' computed: { //数组写法 ...mapGetters('user', ['filterUserlist']), //对象写法 ...mapGetters('user', {filterUserlistNewName: 'filterUserlist'}) } // 使用 // 数组写法在页面上直接 {{filterUserlist}} // 对象写法在页面上直接 {{filterUserlistNewName}}
4.3 mutations
注意:默认模块中的 getters 和 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
不开启命名空间的访问方式和读取方式和主模块一样,这里只展示开启命名空间方式的例子
调用子模块中 mutation:
-
直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
-
通过 mapMutations 映射
子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON', person)
//方式二:借助mapMutations:
import { mapMutations } from 'vuex'
methods: {
...mapMutations('personAbout', ['ADD_PERSON']),
}
4.4 actions
注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
不开启命名空间的访问方式和读取方式和主模块一样,这里只展示开启命名空间方式的例子
调用子模块中 action :
-
直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
-
通过 mapActions 映射
子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang', person)
//方式二:借助mapActions:
...mapActions('personAbout', ['addPersonWang'])
4.5 模块化+命名空间总结
目的:让代码更好维护,让多种数据分类更加明确。
具体案例: count.js
//求和相关的配置
export default {
namespaced:true,
actions:{
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
},
mutations:{
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
},
},
state:{
sum:0, //当前的和
school:'尚硅谷',
subject:'前端',
},
getters:{
bigSum(state){
return state.sum*10
}
},
}
person.js
//人员管理相关的配置
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
namespaced:true,
actions:{
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert('添加的人必须姓王!')
}
},
addPersonServer(context){
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
response => {
context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
},
error => {
alert(error.message)
}
)
}
},
mutations:{
ADD_PERSON(state,value){
console.log('mutations中的ADD_PERSON被调用了')
state.personList.unshift(value)
}
},
state:{
personList:[
{id:'001',name:'张三'}
]
},
getters:{
firstPersonName(state){
return state.personList[0].name
}
},
}
index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
modules:{
countAbout: countOptions,
personAbout: personOptions
}
})
count.vue
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h3>当前求和放大10倍为:{{bigSum}}</h3>
<h3>我在{{school}},学习{{subject}}</h3>
<h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
}
},
computed:{
//借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState('countAbout',['sum','school','subject']),
...mapState('personAbout',['personList']),
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('countAbout',['bigSum'])
},
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
},
mounted() {
console.log(this.$store)
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
person.vue
<template>
<div>
<h1>人员列表</h1>
<h3 style="color:red">Count组件求和为:{{sum}}</h3>
<h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
<input type="text" placeholder="请输入名字" v-model="name">
<button @click="add">添加</button>
<button @click="addWang">添加一个姓王的人</button>
<button @click="addPersonServer">添加一个人,名字随机</button>
<ul>
<li v-for="p in personList" :key="p.id">{{p.name}}</li>
</ul>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'Person',
data() {
return {
name:''
}
},
computed:{
personList(){
return this.$store.state.personAbout.personList
},
sum(){
return this.$store.state.countAbout.sum
},
firstPersonName(){
return this.$store.getters['personAbout/firstPersonName']
}
},
methods: {
add(){
const personObj = {id:nanoid(),name:this.name}
this.$store.commit('personAbout/ADD_PERSON',personObj)
this.name = ''
},
addWang(){
const personObj = {id:nanoid(),name:this.name}
this.$store.dispatch('personAbout/addPersonWang',personObj)
this.name = ''
},
addPersonServer(){
this.$store.dispatch('personAbout/addPersonServer')
}
},
}
</script>