Vuex
官网地址
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
vuex设计思想,借鉴了 Flux,Redux,将数据存放到全局的store,再将 store挂载到每个 vue实例组件中,利用 vue.js 的细粒对数据响应机制来进行高效的状态更新
修改状态的唯一途径就是显示的提交(commit) mutation。这样状态的变化才是可控的
安装与使用
安装 通过 npm / yarn 都可
// 只介绍通过 npm 安装
npm i vuex
使用
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
如果需要兼容不适配 Promise 的(IE),可以使用一个 polyfill 的库,比如 es6-promise
// npm i es6-promise -S
// 直接在最上方引入
import 'es6-promise'
......
使用
- 新建 store.js 文件
- 新建 store。在 store/index.js 编辑
import Vuex from 'vuex'
import Vue from 'vue'
// 这里必须要先注册,不让会报错。。,必须要先注册 再 new Vuex.Store
Vue.use(Vuex)
// 导出一个 sore 实例
export default new Vuex.Store({
// 存储状态 (可以理解为 data)
state: {
count: 0,
},
// 修改状态 (可以理解为 methods)
mutations: {
// 修改 state 的值
updateCount(state) {
state.count++
}
}
})
- 在 main.js 中引入 store
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import { Button, Input } from 'element-ui'
Vue.config.productionTip = false
// 修改 elementUI 默认配置大小
Vue.prototype.$ELEMENT = { size: 'small', zIndex: 2021 };
Vue.use(Button).use(Input)
new Vue({
render: h => h(App),
// 将 store 挂载到实例上(Vue.prototype.$store = store)
// 在组件中 通过 this.$sotre 来访问 store 对象
store
}).$mount('#app')
- 在组件中使用
<template>
<div class="children">
<pre>{{$store.state}}</pre>
<el-button type="primary" @click="showStore">点击展示</el-button>
</div>
</template>
<script>
export default {
name: 'children',
methods: {
showStore () {
// 通过 commit 提交 给 updateCount 方法,告诉 vuex 我们需要修改状态
this.$store.commit('updateCount')
}
}
}
</script>
- 点击按钮发现 $store.state.count 的值 每次点击都会 +1
核心概念
State
state 与 组件中的 data 用法一致
......
state: {
// 需要定义的变量
count: ''
}
......
在组件中使用 state,需要先使用 Vue.use 方法组册,组册成功之后,可以通过 this.$store.state 访问 vuex 的状态
......
Vue.use(store)
// 组件中使用
.....
{
.....
computed: {
count () {
// 通过计算属性返回 count,从而达到响应式
return this.$store.count
}
}
}
mapState 辅助函数
可以通过 mapState 辅助函数,将 state 映射到计算属性中,帮我们生成计算属性
-
mapState(namespace?: string, map: Array | object<string | function>): Object
-
为组件创建计算属性以返回 Vuex stotre 中的状态
-
第一个参数为可选,为命名空间(Modules)
- 使用 mapState 辅助函数
<template>
<div class="children">
<div>组件自带的 getCount: {{ getCount }}</div>
<br>
<div>箭头函数生成的 arrawCount: {{ arrawCount }}</div>
<br>
<div>不使用箭头函数生成的 countPlusLocalState: {{ countPlusLocalState }}</div>
<br>
<div>使用别名加载 countAlias: {{ countAlias }}</div>
<br>
<el-button type="primary" @click="showStore">点击展示</el-button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'children',
data () {
return {
localCount: 100
}
},
methods: {
showStore () {
this.$store.commit('updateCount')
}
},
computed: {
// 组件自己的计算属性
getCount () {
return this.$store.state.count
},
// 使用扩展运算符将 mapState 与 其他 计算属性混用
...mapState({
// 使用箭头函数生成 arrawCount 计算属性,返回 count。等同于: arrawCount () { return this.$store.state.count }
// 使用箭头函数可以保证 this 的指向
arrawCount: state => state.count,
// 自定义计算属性名字返回 状态。不使用箭头函数,可以访问当前实例中的状态/属性
countPlusLocalState (state) {
return state.count + this.localCount
},
// 使用别名加载 count 生成一个 countAlias 计算属性。等同于: countAlias () { return this.$store.state.count }
countAlias: 'count'
})
}
}
</script>
- 运行结果展示
- 点击按钮:所有的计算属性的值都成功 +1
Getters
getters 可以认为是 store 的计算属性
- 定义: 用法与 计算属性 用法一致(也是具有缓存的。如果依赖的 state 的值未发生改变,则不会更新值)
......
// 存储状态 (可以理解为 data)
state: {
count: 0,
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
// 用法与 计算属性 用法一致(也是具有缓存的。如果依赖的 state 的值未发生改变,则不会更新值)
doneTodos (state) {
// 返回 state.todos 中 done 的值为 true 的项
return state.todos.filter(todo => todo.done)
}
},
......
- 使用:getters 会 暴露为 对象,可以通过 store.getters 以属性的方式来访问这些值
// 在组件中访问
this.$store.getters.doneTodos
- getter 也可以接受其他 getter 作为第二个参数。也就是说 getters 中的方法,第二个参数默认就是 getters
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
},
doneTodosCount (state, getters) {
return getters.doneTodos.length
}
}
.....
// 组件中使用
this.$store.getters.doneTodosCount
- 给 getter 传递参数: 传递参数的方式与计算属性一致,返回一个函数
注意:如果通过方法访问(传递参数),那么每次都会去掉用,而不会缓存结果
getters: {
getTodosWithState (state) {
// status 就是传递过来的参数,通过 status 来判断需要获取的数据
return status => state.todos.filter(todo => todo.id === status)
}
}
......
// 在组件中使用
<div>id为1的项:{{ $store.getters.getTodosWithState(1) }}</div>
<br>
<div>id为2的项:{{ $store.getters.getTodosWithState(2) }}</div>
运行结果:
mapGetters 辅助函数
可以通过 mapGetters 辅助函数,将 state 映射到计算属性中,帮我们生成计算属性
-
mapGetters(namespace?: string, map: Array | Object): Object
-
为组件创建计算属性已返回 getter 的返回值
-
第一个参数为可选,为命名空间(Modules)
- 使用 mapGetters 辅助函数
<template>
<div class="children">
<div>组件自带的 getCount: {{ getCount }}</div>
<br>
<div>箭头函数生成的 arrawCount: {{ arrawCount }}</div>
<br>
<div>不使用箭头函数生成的 countPlusLocalState: {{ countPlusLocalState }}</div>
<br>
<div>使用别名加载 countAlias: {{ countAlias }}</div>
<br>
<div>$store.getters.doneTodos:{{ doneTodos }}</div>
<br>
<div>id为1的项:{{ getTodosWithState(1) }}</div>
<br>
<div>id为2的项:{{ getTodosWithState(2) }}</div>
<br>
<div>doneCount: {{ doneCount }}</div>
<br>
<div>doneCountWithState: {{ doneCountWithState(1) }}</div>
<br>
<el-button type="primary" @click="showStore">点击展示</el-button>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'children',
data () {
return {
localCount: 100
}
},
methods: {
showStore () {
this.$store.commit('updateCount')
}
},
computed: {
// 组件自己的计算属性
getCount () {
return this.$store.state.count
},
// 使用扩展运算符将 mapState 与 其他 计算属性混用
...mapState({
// 使用箭头函数生成 arrawCount 计算属性,返回 count。等同于: arrawCount () { return this.$store.state.count }
// 使用箭头函数可以保证 this 的指向
arrawCount: state => state.count,
// 自定义计算属性名字返回 状态。不使用箭头函数,可以访问当前实例中的状态/属性
countPlusLocalState (state) {
return state.count + this.localCount
},
// 使用别名加载 count 生成一个 countAlias 计算属性。等同于: countAlias () { return this.$store.state.count }
countAlias: 'count'
}),
// 可以通过数组来将 getter 与 computed 混合
...mapGetters(['doneTodos', 'getTodosWithState']),
// 通过对象形式传递
...mapGetters({
doneCount: 'doneTodos',
doneCountWithState: 'getTodosWithState'
})
}
}
</script>
- 运行结果
Mutations
更改 Vuex 的 store 中的状态的唯一方法就是提交 mutation Mutations 中的每一个 mutation 都有一个字符串的 事件类型(type) 和一个 回调函数(handler) 这个回调函数就是用来给我们进行状态更改的地方,并且会接受 state 为第一个参数
注意:mutation 中的方法必须是同步的。假如调用了两个异步 mutation 来改变状态,状态改变的不能区分是那个回调触发
- 定义 用法与methods 相同。只是他有默认的第一个参数为 state
{
state: {
count: 0,
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
// 用法与 计算属性 用法一致
doneTodos (state) {
// 返回 state.todos 中 done 的值为 true 的项
return state.todos.filter(todo => todo.done)
},
getTodosWithState (state) {
return status => state.todos.filter(todo => todo.id === status)
}
},
// 修改状态 (可以理解为 methods)
mutations: {
// 修改 state 的值
updateCount(state) {
console.log('state的值修改了')
state.count++
}
}
}
- 调用 mutation 中的方法
<template>
<div class="children">
<div>count: {{ $store.state.count }}</div>
<br>
<el-button type="primary" @click="showStore">点击展示</el-button>
</div>
</template>
<script>
export default {
name: 'children',
methods: {
showStore () {
// 通过 commit 调用 updateCount 方法
this.$store.commit('updateCount')
}
}
}
</script>
- 运行结果,点击 按钮,执行结果
// 控制台展示
console.log('state的值修改了')
提交载荷(Payload)。。就是给 mutation 传递参数。大多数情况下,传递参数应该是个对象,这样会更易读
......
// 修改 store 中的 updateCount 方法
mutations: {
updateCount (state, num) {
// count 的值 += unm
state.count += unm
}
}
......
在组件中调用
......
<el-button type="primary" @click="showStore(10)">点击展示</el-button>
......
methods: {
showStore (num) {
// 通过 commit 调用 updateCount 方法
this.$store.commit('updateCount', num)
}
}
......
点击按钮展示页面
对象风格的提交方式
提交 mutation 的另一种方式。将 type 的值设置为 mutation 的名字(type)
methods: {
showStore (num) {
// 通过 commit 调用 updateCount 方法
this.$store.commit({
type: 'updateCount',
num: 10
})
}
}
在 store.js 中就需要更改接受方式了
{
......
mutations: {
// 这个 payload 就会包含传递的参数。可以通过 payload.属性名 来获取传递的参数
updateCount (state, payload) {
state.count += payload.num
}
}
......
}
Mutations 需遵守的响应式规则
- 提前在你的 store 中初始化好所有的属性。(为了实现响应式)
- 如果需要给对象新增属性。使用 Vue.$set方法 (为了响应式)
mapMutations 辅助函数
mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用
- 使用 mapMutations 辅助函数
<template>
<div class="children">
<div>count: {{ $store.state.count }}</div>
<br>
<div style="width: 100%">{{ $store.state.todos }}</div>
<br>
<el-button type="primary" @click="updateCount(10)">updateCount</el-button>
<br><br>
<el-button type="primary" @click="updateTodos({id: 3, text: '...', done: true})">updateTodos</el-button>
<br><br>
<el-button type="primary" @click="addCount(undefined)">addCount</el-button>
<br><br>
<el-button type="primary" @click="addTodos({id: 4, text: '...', done: false})">addTodos</el-button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'children',
methods: {
showStore (num) {
this.$store.commit('updateCount', num)
},
// 通过数组 将 updateCount 映射为 this.$store.commit('updateCount') / updateTodos 一样
...mapMutations(['updateCount', 'updateTodos']),
...mapMutations({
// 通过别名将 addCount 映射为 this.$store.commit('updateCount')
addCount: 'updateCount',
addTodos: 'updateTodos'
})
}
}
</script>
- 执行效果
- 点击 updateCount 按钮,count 值 += 10
- 点击 updateTodos 按钮,todos 值 push 了一个对象
- 点击 addCount 按钮,count 值 += 1
- 点击 addTodos 按钮,todos 值 push 了一个对象
Actions
Actions 类似于 Mutations,不同的地方在于
- Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
注意:因为 actions 可以处理异步函数,所以也可以返回 promise
- 使用 Actions
......
// 修改状态 (可以理解为 methods)
mutations: {
// 修改 state 的值
updateCount(state, num) {
state.count += num ?? 1
},
updateTodos (state, item) {
state.todos.push({...item})
}
},
actions: {
// content 参数 为一个与 sotre实例 具有相同方法和属性的 context 对象,也就是包含 context.store/context.getters/context.commit.....等
addCount (context) {
// 通过 context.commit 提交 updateCount mutation
context.commit('updateCount')
},
addTodos ({ commit }, item) {
commit('updateToodos', item)
},
// action 中可以进行异步操作
asyncAddCount ({ commit }, num) {
setTimeout(() => commit('updateCount', num), 1000)
},
// 可以返回 promise
promiseAddCount ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('updateCount')
resolve()
}, 1000)
})
},
// actions 中的 action 也可以互相调用
actionCount ({ dispatch, commit }) {
return dispatch('promiseAddCount').then(res => {
commit('updateCount', 10)
})
},
// 也可以使用 async 与 await
async awaitAddCount ({ dispatch, commit }) {
await dispatch('promiseAddCount')
commit('updateCount', 10)
}
}
- 在组件中使用 (通过 vm.$store.dispatch 调用)
actions 中传递参数的方式 与 mutations 一致
// 不传递参数掉用
this.$store.dispatch('addCount')
// 传递参数调用 传递参数的方式与 mutations 一致。有多种
this.$store.dispatch('addTodos', {id: 5, text: '...', done: true})
// 调用异步操作也是可以的
this.$store.dispatch('asyncAddCount', 10)
// 调用 promise action
this.$store.dispatch('promiseAddCount').then(() => console.log('值修改成功了'))
// action 可以互相调用。这个方法先调用 promiseAddCount += 1。修改成功之后再次commit += 10
this.$store.dispatch('actionCount')
// 使用 async 与 await
await this.$store.dispatch('awaitAddCount')
mapActions 辅助函数
mapActions 辅助函数将组件中的 methods 映射为 store.dispatch 调用_
- 使用 mapActions 辅助函数
......
<el-button type="primary" @click="addCount">addCount</el-button>
<br><br>
<el-button type="primary" @click="addTodos({id: 3, text: '...', done: true})">addTodos</el-button>
<br><br>
<el-button type="primary" @click="addCountMethod">addCountMethod</el-button>
......
import { mapActions } from 'vuex'
export default {
......
methods: {
// 通过数组将 actions 映射到 methods 中。调用时 直接 this.addCount 等价于 this.$store.dispatch('addCount')
...mapActions(['addCount', 'addTodos', 'asyncAddCount']),
// 通过别名将 addCountMethod 映射为 this.$store.dispatch('addCount')
...mapActions({
addCountMethod: 'addCount',
......
})
}
}
......
- 使用方式与 mutations 类似
文章结尾有句话为:一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
Actions 链接
这句话的意思是说:如果在同一个命名空间下,有多个地方触发同一个 action,那么会等所有的 action 都完成之后返回的 promise 才会执行
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
就是说:如果 store 数据多了会导致不好管理 比较臃肿。需要通过 模块 将其划分。可以理解为 每一个模块就是一个命名空间,每个命名空间管理自己的 store
模块的用法
let modelA = {
// 注意:模块中的 state 使用 函数返回一个对象,因为这个东西是复用的,会有引用关系
state () {
return {
count: 100
}
},
getters: {},
mutations: {},
actions: {}
}
const store = new Vuex.Store({
state: {
rootCount: 1
},
modules: {
// 模块可以有多个
a: moduleA
......
}
})
对于模块里面的 getters,第一个参数的 state 是局部模块中的 state,第三个参数为根节点状态
getters: {
/**
* state: 局部模块中的 state
* getters: 局部模块中的 getters
* rootState: 根节点中的 state (就是 new Vuex.store 中的 state 比如:rootState.rootCount)
*/
getTods (state, getters, rootState) {
}
}
对于模块里面的 acitons,可以通过 context.rootState 获取跟节点状态
actions: {
// 或者通过解构赋值 { rootState }
actionFn (context) {
// 可以通过 context.rootState 获取根节点的状态
......
}
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
通过添加 namespaced: true 使得其成为带命名空间的模块,当模块被注册后,它所有的 getter、action、mutation 都会自动根据模块注册的调整命名
const store = new Vuex.Store({
modules: {
account: {
// 将命名空间设置为 true
namespaced: true,
// 下面是模块的内容
// 因为模块内部的 state 是嵌套的,所以不会对其产生影响
state () { return {} },
getters: {
// 调用方式:getters['account/getCount']
getCount () {}
},
mutations: {
// 调用方式:commit('account/updateCount')
updateCount () {}
},
actions: {
// 调用方式:dispatch('account/actions')
actions () {}
}
}
}
})
- getters 调用方式为: getters[‘命名空间/getter名字’]
- mutations 调用方式为: commit(‘命名空间/mutation名字’)
- actions 调用方式为: dispatch(‘命名空间/action名字’)
模块内部可以嵌套多个模块
- 如果在同一个命名空间下的任何模块都可以使用 命名空间/type名字 调用
- 如果命名空间下嵌套了其他命名空间,那么调用的时候就需要换成 命名空间1/命名空间2/type名字 。有几层命名空间就写几个,从上往下
带命名空间的模块访问全局内容
如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
getters: {
/**
* state: 局部模块中的 state
* getters: 局部模块中的 getters
* rootState: 根节点中的 state (就是 new Vuex.store 中的 state 比如:rootState.rootCount)
* rootGetters: 根节点中的 getters
*/
getTods (state, getters, rootState, rootGetters) {}
}
对于模块里面的 acitons,可以通过 context.rootState 获取根节点状态
actions: {
// 或者通过解构赋值 { rootState, rootGetters }
actionFn (context) {
// 可以通过 context.rootState 获取根节点的状态
// 可以通过 context.rootGetters 获取根节点的 getters
......
// 通过设置 root:true 来反问根节点的 actions
dispatch('action名字', '参数', {root: true})
// mutation 也是一样
commit('mutation名字', '参数', {root: true})
}
}
在带命名空间的模块注册全局 action
若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。
注册方式变为对象了。。
......
namespaced: true,
actions: {
// 这个方法是当前命名空间内的
updateCount () {},
// 这个是组册在全局的
publicUpdateCount: {
root: true,
/**
* namespacedContext: context
* payload: 传递的参数
*/
handler (namespacedContext, payload) {.....}
}
}
......
带有命名空间的module 使用辅助函数
调用方式有多种,只列举一个
官网链接
- mapState: mapState(‘命名空间’, [‘state’])
- mapState: mapMutations(‘命名空间’, [‘mutation’])
- mapState: mapActions(‘命名空间’, [‘action’])
可以使用 createNamespacedHelpers 方法创建某个命名空间下的辅助函数,这样调用的时候和调用全局达到方法一致了
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('命名空间/命名空间。。')
// mapState/mapActions 使用方式与全局的一致
注册模块
通过 registeModule 方法注册模块
import Vuex from 'vuex'
const store = new Vuex.Store({.....})
// 注册某个模块
store.registerModule('模块名称', {.....})
// 注册到某个模块下面 命名空间1/命名空间2
store.registerModule(['命名空间1', '命名空间2'], {...})
如何挂载
vuex的store是如何挂载注入到组件中的呢?
在vue 项目中先安装 vuex
利用vue 的插件机制,使用 vue.use(vuex)时,会调用 vuex 的install方法,安装 vuex
applyMixin 方法使用 vue 混入机制,vue的生命周期 beforeCreate 钩子函数混入 vuexInit 方法
vuex是利用 vue 的 mixin 混入机制,在beforeCreate 钩子函数混入 vuexInit 方法,vuexInit 方法实现了 store 注入 vue 组件实例,并注册了 vuex store 的引用属性 $store
vuex 的state 和 getter 是如何映射到各个组件实例中响应式更新状态的?
vuex 的state 状态是响应式,是借助 vue的data是响应式,将 state存入vue实例组件的data中;
vuex 的getters则是借助 vue的计算属性 computed 实现数据实时监