Vuex
zKing 2019-02-26 Vue.js
摘要
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。献上官网
# 安装
vue-cli2x 默认不会安装 Vuex,所以需要单独去安装。另外,vue-cli3x 已经支持默认安装了。
cnpm install vuex -S
# 目录结构
关于 Vuex 的代码主要在 store 目录下
├─src
| ├─App.vue
| ├─main.js
| ├─store
| | └index.js
| ├─router
| | └index.js
| ├─components
| | ├─A.vue
| | ├─B.vue
| | ├─HelloWorld.vue
| | └notFound.vue
| ├─assets
| | └logo.png
# 基本配置
# 配置 store
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
}
})
# 在入口文件进行引用
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
# 在组件内获取数据
HelloWorld.vue
<template>
<div>
<router-link to='/A'>toA</router-link>
<router-link to='/B/1' tag='p'>toB</router-link>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
mounted () {
console.log(this.$store.state.count) //通过 this.$store.state 即可获取数据
}
}
</script>
<style scoped>
</style>
# 核心概念
首先看下 Vuex 的原理图
# state && getters
state
初始化一些数据,作为"仓库"存储使用,组件中可以使用this.$store.state
来获取数据getters
是为了生成在应用中可以使用的数据,类似一层“过滤网”可以理解相当于在vue
中的计算属性computed
,组件中可以使用this.$store.getters
来获取数据
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
arr: [1, 5, 4, 3, 2]
},
getters: {
newArr: state => {
return state.arr.filter(item => {
return item < 5
})
}
},
mutations: {
increment (state) {
state.count++
}
}
})
HelloWorld.vue
<template>
<div>
<router-link to='/A'>toA</router-link>
<router-link to='/B/1' tag='p'>toB</router-link>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
mounted () {
console.log(this.$store.state.count)
console.log(this.$store.getters.newArr)
}
}
</script>
<style scoped>
</style>
# mutation && action
mutation
是专门用来修改 state 数据的,**不要在组件用 this.store 来修改数据,不规范!**可以在组件中使用this.$store.commit()
来触发action
用来处理异步方法,在组件中,使用this.$store.dispatch()
来触发
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- mutation 是同步的
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
arr: [1, 5, 4, 3, 2]
},
getters: {
newArr: state => {
return state.arr.filter(item => {
return item < 5
})
}
},
mutations: {
increment (state) {
state.count++
},
popArr (state) {
state.arr.pop()
}
},
actions: {
popArrAction ({commit, state}) {
commit('popArr')
}
}
})
HelloWorld.vue
<template>
<div>
<router-link to='/A'>toA</router-link>
<router-link to='/B/1' tag='p'>toB</router-link>
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
mounted () {
console.log(this.$store.state.count)
console.log(this.$store.getters.newArr)
},
methods: {
handleClick () {
this.$store.commit('increment')
this.$store.dispatch('popArrAction')
}
}
}
</script>
<style scoped>
</style>
# module
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
var moduleA = {
state: {
count: 0,
arr: [1, 5, 4, 3, 2]
},
getters: {
newArr: state => {
return state.arr.filter(item => {
return item < 5
})
}
},
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
},
popArr (state) {
state.arr.pop()
}
},
actions: {
popArrAction ({commit, state}) {
commit('popArr')
}
}
}
var moduleB = {
state: {
arr: [1, 4, 5, 5, 4]
},
getters: {
newArr2: state => {
return state.arr.filter(item => {
return item < 5
})
}
}
}
export default new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
HelloWorld.vue
<template>
<div>
<router-link to='/A'>toA</router-link>
<router-link to='/B/1' tag='p'>toB</router-link>
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
mounted () {
console.log(this.$store.state.a.count) // 状态可以使用相同的命名,所以需要使用 state.a
console.log(this.$store.getters.newArr) // getters 使用相同的命名
},
methods: {
handleClick () {
this.$store.commit('increment')
this.$store.dispatch('popArrAction')
}
}
}
</script>
<style scoped>
</style>
另外,modules
还有以下这些特性,但是太过复杂,建议直接去官网查阅
- 模块中可以设置 namespaced:true 来设置作用域
- 在组件调用的时候,需要声明模块来调用
- 在模块内要使用全局的数据,可以在方法中传一个 rootState,在 commit 中传一个 {root:true}
- 在模块中也可以再注册一个模块
- 在入口文件中,也可以动态地注册一个模块
# 解耦
在上述的方法中,开发都是使用 this.$store
这个钩子来获取和更改数据,但是这样子代码耦合性较高,Vuex
提供另外的 API 来更好地使用。
使用时需要安装更高级的 babel 转译模块,Vue-CLI 默认已安装
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
var moduleA = {
state: {
count: 0,
arr: [1, 5, 4, 3, 2]
},
getters: {
newArr: state => {
return state.arr.filter(item => {
return item < 5
})
}
},
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
},
popArr (state) {
state.arr.pop()
}
},
actions: {
popArrAction ({commit, state}) {
commit('popArr')
}
}
}
var moduleB = {
state: {
arr: [1, 4, 5, 5, 4]
},
getters: {
newArr2: state => {
return state.arr.filter(item => {
return item < 5
})
}
}
}
export default new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
HelloWorld.vue
<template>
<div>
<router-link to='/A'>toA</router-link>
<router-link to='/B/1' tag='p'>toB</router-link>
<button @click="handleClick">click</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
name: 'HelloWorld',
computed: {
// 如果不是两个模块,而是只是一个模块,直接这样即可 ...mapState(['count'])
...mapState({
count: state => state.a.count
}),
...mapGetters(['newArr'])
},
methods: {
...mapMutations(['increment']),
...mapActions({
popArr: 'popArrAction'
}),
handleClick () {
this.increment()
this.popArr()
}
},
mounted () {
console.log(this.count)
console.log(this.newArr)
}
}
</script>
<style scoped>
</style>
# 细节优化
- 在入口文件中,store 还有其他的
API
- watch
- subscribe 提供 mutation 的回调函数
- subscribeAction 监听 action
- 使用 Vuex 时可以发现页面并没有热更新,官方有部署热更新的方法
- 在开发环境下,可以开启严格模式,在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。注意,生产环境必须关掉严格模式!
- 当在严格模式中使用 Vuex 时,对于表单处理,可以使用 vue 中带有
getter
和setter
的双向绑定计算属性 - 对于大型应用的目录分割建议
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
# 总结
Vuex 的学习笔记就总结到这里。Vuex 还有一些API,这里还没有涉及到,而且还有 Vuex 的插件使用,以及 Action 的进一步提升。