官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说:对于多层嵌套的组件使用父子通讯兄弟通讯太繁琐,因此vuex就是把组件共享状态抽取出来以一个全局单例模式管理,把共享的数据函数放进vuex中,任何组件都可以进行使用。
一.安装
npm install vuex@next --save
二.配置 store
vue2
store/index.js store配置文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
main.js 入口文件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
vue3
store/index.js store配置文件
import { createStore } from 'vuex'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
main.js 入口文件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
三.核心概念
vuex有五大核心概念: State Getter Mutation Action Module
四.State
提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,与data相似。
在vuex中state中定义数据,可以在任何组件中进行调用。
state: {
name: 'yujiabao',
age: 18,
count: 100
},
访问方式:
方式一:js中访问
{
this.$store.state.name
}
方式二:标签中访问
<span>{{ $store.state.name }}</span>
方式三:计算属性
computed: {
name() {
return this.$store.state.name;
},
},
方式四:mapState辅助函数
方式三如果有多个数据需要映射的话,就需要写很多个计算属性比较麻烦,所以我们可以使用mapState函数。
先按需引入
import { mapState } from "vuex";
把store中的数据映射到计算属性中,mapState函数参数可以接受一个对象,返回值也是一个对象。我们这里直接给计算属性 computed 赋值 mapState 的返回值。
data() {
return {
// 用户名
userName: "",
// 密码
password: "",
};
},
computed: mapState({
// 键值对形式 表示 this.name 为 this.$store.state.name
name: "name",
// 箭头函数形式
age: (state) => state.age,
// 普通函数形式,可以使用 this 实例
count(state) {
return state.count;
},
fenshu: function (state) {
return this.name + state.count;
},
}),
上述写法就相当于
computed: { // 这个对象就是 mapState 的返回值
name() {
return this.$store.state.name;
},
age() {
return this.$store.state.age;
},
count() {
return this.$store.state.count;
},
fenshu() {
return this.$store.state.name + this.$store.state.count;
},
},
把这个对象赋值给computed不就和原始的写法一样了吗,所以mapState起到了简化的作用。
但是这样直接给计算属性 computed 赋值的话,那么computed中就只有对vuex状态的获取,而不能写别的计算属性了,所以我们使用对象的扩展运算符。
(其实也可以写,直接通过this获取当前组件数据就行,但是总觉得不太规范)
data() {
return {
// 用户名
userName: "",
// 密码
password: "",
};
},
computed: mapState({
name: "name",
age: (state) => state.age,
count(state) {
return state.count;
},
fenshu: function (state) {
return this.name + state.count;
},
// 与 Vuex 数据无关的变量,也能写在mapState里面,但是总觉得不太规范
user() {
return this.userName
}
}),
还是使用对象的扩展运算符。
data() {
return {
// 用户名
userName: "",
// 密码
password: "",
};
},
computed: {
...mapState({
name: "name",
age: (state) => state.age,
count(state) {
return state.count;
},
fenshu: function (state) {
return this.name + state.count;
},
}),
user() {
return this.userName;
},
},
mapState 函数参数也可以接受一个数组
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
computed: {
...mapState([
// // 映射 this.name 为 store.state.name
"name",
"age",
"count",
]),
user() {
return this.userName;
},
},
就相当于
computed: {
...mapState({
name: "name",
age: "age",
count: "count",
}),
user() {
return this.userName;
},
},
最终演化为
computed: {
name() {
return this.$store.state.name;
},
age() {
return this.$store.state.age;
},
count() {
return this.$store.state.count;
},
user() {
return this.userName;
},
},
计算属性里可以有多个 mapState 函数
computed: {
...mapState([
"name",
"age",
"count",
]),
...mapState({
count(state) {
return state.count;
},
}),
user() {
return this.userName;
},
},
五.Getter
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
注意:从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来。这是一个已知的问题,将会在 3.1 版本中修复。
Getter 接受 state 作为其第一个参数:
可以通过调用该参数取访问 vuex.state 的数据
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done) // -> [{ id: 1, text: '...', done: true }]
}
}
Getter 也可以接受其他 getters 作为第二个参数:
可以通过调用该参数取访问 vuex.gettets 的数据
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done) // -> [{ id: 1, text: '...', done: true }]
},
doneTodosCount (state, getters) {
return getters.doneTodos.length // -> 1
}
}
访问方式:
方式一:js中访问
{
this.$store.getter.doneTodos
}
方式二:标签中访问
<span>{{ $store.state.doneTodos }}</span>
方式三:计算属性
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
方式四:通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
其实就相当于是这样:getTodoById 返回一个函数,我们访问 getTodoById 的时候可以像方法一样传个参数过来。
getters: {
// ...
getTodoById: (state) => {
return (id) => {
return state.todos.find(todo => todo.id === id)
}
}
}
调用的时候传个参数:
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
方式五:mapGetters
辅助函数(同 state)
六.Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
state: {
age: 18,
},
mutations: {
addAge(state) {
state.age++
},
},
你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 addAge
的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:
this.$store.commit('addAge')
你可以向 store.commit
传入额外的参数,即 mutation 的载荷(payload):
mutations: {
addAge(state) {
state.age++
},
delAge(state, num) {
state.age = state.age - num
},
},
this.$store.commit("delAge", 10);
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
addAge(state) {
state.age++
},
delAge(state, payload) {
state.age = state.age - payload.num
},
},
this.$store.commit("delAge", {
num: 10,
});
对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type
属性的对象:
this.$store.commit({
type: "delAge",
num: 10,
});
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变:
mutations: {
addAge(state) {
state.age++
},
delAge(state, payload) {
state.age = state.age - payload.num
},
},
通过辅助函数mapMutations
触发 ,基本上就类似于 State,不过一个是在computed中,一个在methods中
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
<button @click="increment"></button>
<button @click="incrementBy(100)"></button>
<button @click="add"></button>
使用常量替代 Mutation 事件类型(不重要)
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// index.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Mutation 必须是同步函数
mutations: {
someMutation (state) {
api.callAsyncMethod(() => { // 异步,不可取
state.count++
})
}
}
Mutation 中的回调函数必须是同步的,这是因为在 Vuex 中,状态的变化必须是可追踪且同步的。如果需要执行异步操作,应该使用 Action。
七.Action
Action 类似于 mutation,不同在于:
-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
实践中,我们会经常用到 ES2015 的 参数解构 (opens new window)来简化代码:
actions: {
increment ({ commit }) {
commit('increment')
}
}
Action 通过 store.dispatch
方法触发:
this.$store.dispatch('increment')
也像Mutation函数那样可以额外接受一个参数,即载荷:
this.$store.dispatch('increment', 10)
this.$store.dispatch('increment', {num: 10})
actions: {
increment ({ commit },payload) {
commit('increment')
}
}
也像Mutation函数那样可以用对象风格的提交方式:
this.$store.dispatch({
type: 'increment',
amount: 10
})
可以用辅助函数mapActions
触发,参考Mutation
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
<button @click="increment"></button>
<button @click="incrementBy(100)"></button>
<button @click="add"></button>
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation:
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求
// 然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。