我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
🥰了解 Vuex!
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension。
🔮简单来说,Vuex 提供了一种集中管理数据的方式。
🤔为什么是 Vuex?
抛开 Vuex 不谈,实现组件间的数据共享的方式其实不少,在之前的文章有讲过组件之间数据共享方式:props、$emit、eventBus...
为什么还要"多此一举"地开发 Vuex 呢?
想象一下,如果你的项目里有很多页面,页面间存在层层嵌套关系,且它们都共享同一个状态,那么当来自不同视图的行为需要变更同一个状态时,我们常常需要采用父子组件直接引用或通过事件变更,而这些方式都非常脆弱,通常会导致无法维护的代码。
于是,我们想把组件之间的共享状态抽取为全局状态,不论组件身在何处,都可以直接获取。
这时候,Vuex 就诞生了!
Vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中 vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。
🎴建议:小项目不用 vuex,这会额外增加许多页面,比起不使用 vuex 多了许多内容;而大项目建议使用 vuex 对数据进行管理。
🚀快速上手
安装
npm install vuex --save
配置
安装后要配置 vuex,在 src 目录下新建 store 文件夹,然后紧接着创建 index.js 文件。
每一个 Vuex 的核心就是 store (仓库),store 基本上就是一个容器,包含着应用中大部分的状态
state;且 Vuex 中的状态state是响应式存储的。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
name: '掘了~',
books: [
{ id: 1, name: 'Go' },
{ id: 2, name: 'C++' },
{ id: 3, name: 'Python' },
]
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
export default store
state、getters、mutations、actions、modules这都是些什么?别急,下文会逐个讲解。
和 vue-router 一样,还需要将 store 对象挂载到 Vue 示例上,修改 main.js:
import Vue from 'vue'
import App from './App.vue'
// 引入 src/store/index.js 中导出的 store 对象
import store from './store'
Vue.config.productionTip = false
new Vue({
// 将store对象挂载到vue示例上
store,
render: h => h(App)
}).$mount('#app')
使用
通过调用 this.$store.state.XXXX 可以直接获取到 store 仓库中的状态。
<template>
<div>
<h2>{{ name }}</h2>
<h2>{{ books }}</h2>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$store.state.name)
},
computed: {
name() {
return this.$store.state.name
},
books(){
return this.$store.state.books
},
},
}
</script>
启动项目,演示下效果:
🎥先行预告:我们没有办法直接修改 store 中的状态 state,只能通过显示提交 (commit) mutation 改变状态,这样使得我们方便追踪每一个状态的变化。
OK,快速上手 Vuex 就暂告一段落(仅仅为了感受下 Vuex 的便捷),接下来直入主题谈谈 Vuex 的四大核心吧!
👨💻核心概念
Vuex 官网上的这张图几乎囊括了这部分所要讲的核心。
现在先混个眼熟,等看完这部分的内容后再重新回看该图,相信你会对 Vuex 有新的认识并且留下更深刻的印象。
State
相信看到这,你已经能简单使用 Vuex 来共享数据了,而现在所要讲的 State 就是用来存储共享状态的。
其实在上文『快速上手』这一部分已经演示过如何存储和获取 state,这里简单回顾一下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 定义共享状态
state: {
name: '掘了~',
},
})
⭐使用 this.$store.state.XXXX 即可获取仓库中的状态。
<script>
export default {
mounted() {
console.log(this.$store.state.name)
},
}
</script>
官方建议
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在 computed 计算属性中返回某个状态。
所以,上述代码可替换为:
<script>
export default {
computed: {
mounted() {
console.log(this.name)
},
name() {
return this.$store.state.name
}
},
}
</script>
mapState
你是否会感觉每次写 this.$store.state.XXXX 都很冗长且厌烦?官方也提供了它的「解决方案」,简化了使用方式:
<script>
// ES6解构赋值: 从vuex中导入mapState
import { mapState } from 'vuex'
export default {
computed: {
// ... : 展开运算符
// 数组形式
...mapState(['name'])
},
}
</script>
之后便可像访问计算属性 this.name 直接获取共享状态。
⭐甚至可以为该状态取别名,如下:
<template>
<div>
<h2>{{ aliasName }}</h2>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
// 为 name 状态取别名为 aliasName
computed: {
// 对象形式
...mapState({ aliasName: 'name' }),
},
}
</script>
Getters
🙄想象这么一个场景:store 中的 name 已经在各页面渲染了,然后产品经理提了个新需求,在所有 name 前加上 "Hello"。
👩💻行嘛,于是你在每个 this.$store.state.name 前逐一加上 "Hello",就在你花了一小时改好后,产品经理又说不行,还是改成 "Welcome" 比较洋气。
😡你: xxx(此处省略 1W 字)
显然这种方式应付不了需求的变更「可维护较差」。追根溯源,于是你就会想:有没有一种办法能直接加工/过滤 store 中的 name 呢?
这时,官方提供的 Getters 就闪亮登场了!
基本用法
在 store 对象中定义 getters 属性,以此来获取加工后的 name。
⭐注:参数 state 是 getters 中方法的必填参数(没有 state 参数你无法获取共享状态)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: '掘了~',
},
getters: {
modifiedName(state) {
return 'Hello, ' + state.name
}
},
})
组件中通过 this.$store.getters.XXXX 调用:
<template>
<div>
<h2>{{ name }}</h2>
<h2>{{ infoName }}</h2>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['name']),
infoName() {
return this.$store.getters.modifiedName
},
},
}
</script>
来看看演示效果:
😎那可不可以由客户自定义这部分信息呢?当然可以,你只需要让 getters 中返回值之前再嵌套一个 (带参数) 函数即可,代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: '掘了~',
},
getters: {
modifiedName(state) {
// 嵌套返回一个函数
return (diyInfo) => {
return `${diyInfo}, ${state.name}`
}
}
},
})
使用时参入要展示的信息即可。
<template>
<div>
<h2>{{ infoName }}</h2>
</div>
</template>
<script>
export default {
data() {
return {
welcomeInfo: 'Welcome'
}
},
computed: {
infoName() {
return this.$store.getters.modifiedName(this.welcomeInfo)
},
},
}
</script>
看到效果后,产品经理表示很满意!
mapGetters
同样,一直写 this.$store.getters.XXXX 想必也不舒服,官方提供了 mapGetters 辅助函数将 store 的 getters 中的属性映射到计算属性中!
⭐注:以 modifiedName(state) { return 'hello,' + state.name} 为例!
<template>
<div>
<h2>{{ name }}</h2>
<h2>{{ modifiedName }}</h2>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['name']),
...mapGetters(['modifiedName'])
},
}
</script>
同样,还可以如 mapState 一样,用 mapGetters 为其定义别名,用法同上。
computed: {
// aliasModified 是 modifiedName 的别名
...mapGetters({ aliasModified: 'modifiedName' })
}
😁了解以上内容后,我们便学会了 2 种读取值的操作:"原生读 state" 和 "修饰读 getters",接下来学习如何修改值。
Mutations
上文曾经提及修改 state 的方法,不知你是否还记得?相信你已经回头看了一眼,但还是不懂不能直接修改是怎么一回事,往下看:
// 错误的修改方式
this.$store.state.name = '掘掘子!'
🚫以上是一种错误的修改值的方式!因为 Vuex 允许随意地获取值,但不允许随意修改仓库中的值。
看着没错呀!为什么说是错误的修改方式?
👩💻想象这么一个场景:假设你在掘金阅读他人的文章,你可以引用他的内容 (即 state),但如果你发现他的文章中出现错误的地方,你能直接修改他的文章吗?显然不行!你会在他的文章评论区提醒 (commit) 他,然后他收到通知后,由他自己修改文章中的错误。
同理,Vuex 也是如此,于是,Mutations 来了!并且提交 commit mutation 就是官方提供修改值的唯一方式。
虽然看起来似乎有点繁琐,但它能集中监控数据的变化!
基本用法
来吧,展示!
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: '掘了~',
},
getters: {
},
mutations: {
// 官方建议 payload 为一个对象!
alterName(state, payload) {
state.name = payload.name
}
},
})
🌈其中 state 为默认参数,调用时无需占位,而 payload 是第一个需要传入的形参,同时官方建议该 payload 为一个对象!当然,如果不需要参数则无需编写 payload。
然后来看看组件如何提交/修改状态:
关键代码:
this.$store.commit('xxx')
<script>
export default {
methods: {
changeName() {
console.log(`Old: ${this.$store.state.name}`);
// commit-mutation: 调用时也传递一个对象
this.$store.commit('alterName', { name: '掘掘子!' })
console.log(`New: ${this.$store.state.name}`);
},
}
}
</script>
以上实现的是带有参数的 Mutations 方法,也可实现不带参数的。
演示效果:
mapMutations
😏同上,你如果不想一直用 this.$store.commit('方法名', 参数) 这种写法,可以采用 mapMutations 替代。
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
methods: {
// 将 alterName 映射为 methods 中的方法, 可直接调用!
...mapMutations(['alterName'])
}
}
</script>
...mapMutations 直接将 alterName 映射为 methods 中的方法,可直接调用:
<button @click="alterName({name: '掘掘子~'})">修改姓名</button>
你也可以给 mutations 中的方法取别名:
methods: {
// changeName 是 alterName 的别名, 可直接调用: @click=changeName()
...mapMutations({ changeName: 'alterName' })
}
Mutation 必须是同步函数
🎈注意:Mutations 中的 mutation 只能存放同步操作,即 mutation 只能是同步函数!
在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?
所以在 Vuex 中,mutation 都是同步事务:
this.$store.commit('alterName')
// 任何由 "alterName" 导致的状态变更都应该在此刻完成。
⭐为了处理异步操作,让我们来看一看 Actions。
Actions
⭐Actions 类似 Mutations,不同的是 Actions 可以包含异步操作,其本质就是提交 Mutations,而不是直接变更状态。
基本用法
使用 setTimeout 模拟异步任务,延时 1s 提交 mutation 以修改 name:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: '掘了~',
},
getters: {
},
mutations: {
// 同步操作
alterName(state) {
state.name = '掘掘子~'
}
},
actions: {
altering(context) {
// 异步定时操作
setTimeout(() => {
context.commit('alterName')
}, 1000)
}
}
})
组件使用 this.$store.dispatch('xxx') 分发 Actions:
<template>
<div>
<button @click="alter">修改姓名</button>
</div>
</template>
<script>
export default {
methods: {
alter() {
// dispatch altering()
this.$store.dispatch('altering')
}
}
}
</script>
触发效果:
🌅看懂了吧,Actions 本质还是提交 Mutations,异步操作直接在 Actions 中消化。
如果你想模仿 Mutations 传递参数,也可以在 Actions 中添加形参,如下:
mutations: {
alterName(state, payload) {
state.name = payload.name
}
},
actions: {
altering(context, payload) {
setTimeout(() => {
context.commit('alterName', { name: payload.name })
}, 1000)
}
}
调用一下叭
this.$store.dispatch('altering', { name: '掘掘子~' })
官方建议
在 actions 中,可以单独将 commit 解构出来,方便后续操作。
actions: {
// 解构 commit
altering({ commit }, payload) {
setTimeout(() => {
commit('alterName', { name: payload.name })
}, 1000)
}
}
mapActions
如果厌倦了 this.$store.dispatch('xxx') 形式的写法,可以通过解构 mapActions 将相应的方法置于 methods 中。
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
methods: {
// 调用: @click="altering({name: '掘掘子'})
...mapActions(['altering']),
},
computed: {
},
}
</script>
同理,你也可以给它取个别名:
methods: {
// 调用: @click="alter({name: '掘掘子'})
...mapActions({ alter: 'altering'}),
}
组合 Action
Action 通常是异步的,那么我们怎么知道 Action 什么时候结束呢?
更重要的是,我们怎么才能组合 Action 以实现更加复杂的异步流程呢?
🥏这部分内容详见官方:组合 Action
Modules
Vuex 使用单一状态树,用一个对象就包含了全部状态 state。至此它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、getters、mutations、actions、甚至是嵌套子模块。
⭐熟悉
State、Getters、Mutations、Actions之后,Modules大同小异,这部分会加快节奏!
接下来直入正题:如何实现模块化。
import Vue from 'vue'
import Vuex from 'vuex'
// 可单独将cart和good分别抽离成一个js文件, 也可都同放在store/index.js下
// import cart from './cart.js'
// import good from './good.js'
Vue.use(Vuex)
// account模块
const account = {
// 开启命名空间: 方便 ...mapXXX 的使用
namespaced: true,
state: {
who: 'xxx'
},
getters: {
isAdmin(state) { ... } // => getters['account/isAdmin']
},
mutations: {
login(state, payload) { ... } // => commit('account/login')
},
actions: {
login(context, payload) { ... } // => dispatch('account/login')
}
}
// cart模块: 同上
const cart = {
// 开启命名空间: 方便 ...mapXXX 的使用
namespaced: true,
state: {
},
getters: {
},
mutations: {
},
actions: {
}
}
export default new Vuex.Store({
// index.js 中的原生store也可存放!
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... },
// 注册各个模块
modules: {
account,
cart
}
})
👑一般我们都会添加 namespaced: true 以开启命名空间,这样模块被注册后,它的所有 getters、mutations、actions 都会根据模块注册的名字调整命名。
🧩补充:以下内容皆以开启命名空间的 account 模块为例,其它举一反三
读取 state
// 方式一: state
this.$store.state.account.who
// 方式二: 通过mapState读取
...mapState('account', ['who'])
...mapState('account', { whoIs: 'who' }) // 取别名
修饰 getter
// 方式一: getters
this.$store.getters['account/isAdmin']
// 方式二: 通过mapGetters读取
...mapGetters('account', ['isAdmin'])
...mapGetters('account', { whetherAdmin: 'isAdmin' }) // 取别名
修改 mutation
// 方式一: commit
this.$store.commit('account/login', payload)
// 方式二: 通过mapMutations同步修改
...mapMutations('account', ['login'])
...mapMutations('account', { isLogin: 'login'}) // 取别名
异步 action
// 方式一: dispatch
this.$store.dispatch('account/login', payload)
// 方式二: 通过mapActions异步操作
...mapActions('account', ['login'])
...mapActions('account', { isLogin: 'login'}) // 取别名
OK,以上就是有关 Modules 的内容了,相信熟悉 State、Getters、Mutations、Actions 基础后,这部分内容应该可以一笔带过。
🌈学到了什么?
通篇看完后,相信你对 Vuex 已经有一个全局认知了。
你应该也不会陌生 Vuex 的安装、配置,也知道如何从 state 读取值,加工 state 中的数据 (getters),修改状态 (mutations),在遇到异步操作时还会使用 actions 来触发 mutations,甚至你还会分割模块以提高项目的可维护性。
// 巩固一下
export default {
methods: {
// mapMutations
...mapMutations(['alterName']),
...mapMutations({ changeName: 'alterName' }),
// mapActions
...mapActions(['altering']),
...mapActions({ alter: 'altering' }),
},
computed: {
// mapState
...mapState(['name']),
...mapState({ aliasName: 'name' }),
// mapGetters
...mapGetters(['modifiedName']),
...mapGetters({ aliasModified: 'modifiedName' }),
},
}
如果还有疑惑的地方,可以多看几遍文章;学会了,就赶紧到项目中实践起来叭!
🖊结语
🔎因为笔者也是当天学的 Vuex,所以本文如有不妥之处,还望斧正。
🌈如果看完这篇文章还有疑问,或想深入了解 Vuex,推荐一些文章/视频供大家学习:
- Vuex 官网:事无巨细?倒也未必!
- 快速上手 Vuex 到手写简易 Vuex:一步一步教你搭建一个极简 Vuex~
- Vuex 面试题:这篇更是重量级..
- 手把手教你使用 Vuex,猴子都能看懂的教程:看文章感觉晦涩难懂那是因为没看到好文章。
- Vuex 从入门到实战:从 vuex 基础知识点到 TodoList 案例,一应俱全!
👑 / END / 成功,只比未成功,多坚持了一次!