本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷
vuex 是什么
- github 站点: https://github.com/vuejs/vuex
- 在线文档: https://vuex.vuejs.org/zh-cn/
- 简单来说: 对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
现在先写个vue版的代码,之后来看看vuex会是什么样。
main.js
/* 入口JS */
import Vue from 'vue'
import App from './App.vue'
/* eslint-disable no-new */
new Vue({
el: '#app',
components: {App}, // 映射组件标签
template: '<App/>' // 指定需要渲染到页面的模板
})
App.vue
<template>
<div>
<p>click {{count}} times, count is {{evenOrOdd}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
data () {
return{
count: 0
}
},
computed: {
evenOrOdd(){
return this.count%2===0 ? '偶数' : '奇数'
}
},
methods: {
increment(){
this.count = this.count + 1
},
decrement(){
this.count = this.count - 1
},
incrementIfOdd(){
if(this.count % 2 === 1){
this.count = this.count + 1
}
},
incrementAsync(){
setTimeout(() => {
this.count = this.count + 1
},1000)
}
}
}
</script>
<style>
</style>
状态自管理应用
- state: 驱动应用的数据源(就相当于上面代码中的data,count)
- view: 以声明方式将 state 映射到视图(就相当于上面代码中的
template
部分) - actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的函数)
即:在模板界面上(View),通过事件触发某个Actions函数,再去修改状态数据(State),然后状态数据再去让界面(View)更新显示。
多组件共享状态的问题
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
- 以前的解决办法
a. 将数据以及操作数据的行为都定义在父组件
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
c. 但是这样做只能处理简单的情况,更复杂的情况比较棘手,比如更多路由。 - vuex 就是用来解决这个问题的
vuex 核心概念和 API
state
- vuex 管理的状态对象
- 它应该是唯一的
const state = {
xxx: initValue
}
mutations
- 包含多个直接更新 state 的方法(回调函数)的对象
- 谁来触发: action 中的 commit(‘mutation 名称’)
- 只能包含同步的代码, 不能写异步代码
const mutations = {
yyy (state, {data1}) {
// 更新 state 的某个属性
}
}
actions
- 包含多个事件回调函数的对象
- 通过执行: commit()来触发 mutation 的调用, 间接更新 state
- 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1)
- 可以包含异步代码(定时器, ajax)
const actions = {
zzz ({commit, state}, data1) {
commit('yyy', {data1})
}
}
getters
- 包含多个计算属性(get)的对象
- 谁来读取: 组件中: $store.getters.xxx
const getters = {
mmm (state) {
return ...
}
}
modules
- 包含多个 module
- 一个 module 是一个 store 的配置对象
- 与一个组件(包含有共享数据)对应
向外暴露 store 对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
组件中
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['xxx']),
...mapGetters(['mmm']),
}
methods: mapActions(['zzz'])
}
{{xxx}} {{mmm}} @click="zzz(data)"
映射 store
import store from './store'
new Vue({
store
})
store 对象
- 所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
- 属性:
state: 注册的 state 对象
getters: 注册的 getters 对象 - 方法:
dispatch(actionName, data): 分发调用 action
vuex简例
项目结构:(对最开始的代码进行修改)
首先安装 npm i --save vuex
store.js
/*
vuex最核心的管理对象store
*/
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
/*
相当于data对象的状态对象
*/
const state = {
count: 0 // 指定初始化数据
}
/*
包含了n个直接更新状态的方法的对象
*/
const mutations = {
INCREMENT (state) {
state.count++
},
DECREMENT (state) {
state.count--
}
}
/*
包含了n个间接更新状态的方法的对象
*/
const actions = {
//增加的action
increment ({commit}) {
// 提交一个mutation请求
commit('INCREMENT')
},
//减少的action
decrement ({commit}) {
// 提交一个mutation请求
commit('DECREMENT')
},
//带条件的action
incrementIfOdd ({commit, state}) {
if(state.count%2===1) {
// 提交一个mutation请求
commit('INCREMENT')
}
},
//异步的action
incrementAsync ({commit}) {
//在action中直接就可以执行异步代码
setTimeout(() => {
// 提交一个mutation请求
commit('INCREMENT')
}, 1000)
},
}
/*
包含多个getter计算属性的对象
*/
const getters = {
evenOrOdd (state) { // 当读取属性值时自动调用并返回属性值
return state.count%2===0 ? '偶数' : '奇数'
}
}
/*
向外暴露 store 对象
*/
export default new Vuex.Store({
state, //状态对象
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
main.js
/*
入口js
*/
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
new Vue({
el: '#app',
components: { Counter },
template: '<Counter/>',
store // 注册vuex的store: 所有组件对象都多一个属性$store
})
Counter1.vue(原始版本,Counter为对Counter1的改进版,在之后)
<template>
<div>
<p>click {{count}} times, count is {{evenOrOdd}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
mounted () {
console.log(this.$store)
},
computed: {
count () {
return this.$store.state.count
},
evenOrOdd () {
return this.$store.getters.evenOrOdd
}
},
methods: {
//增加
increment () {
//通知vuex去增加
this.$store.dispatch('increment') //触发store中对应的action调用
},
//减少
decrement () {
this.$store.dispatch('decrement')
},
//如果是奇数才增加
incrementIfOdd () {
this.$store.dispatch('incrementIfOdd')
},
//过1s才增加
incrementAsync () {
this.$store.dispatch('incrementAsync')
}
}
}
</script>
<style>
</style>
它的整体思路就是:
首先建一个store.js,用来存放Vuex相关内容,在其中:
①引入Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
②向外暴露store对象(基本上每次都会暴露这几个对象)
export default new Vuex.Store({
state, //状态对象
mutations, //包含多个更新state函数的对象
actions, //包含多个对应事件回调函数的对象
getters //包含多个getter计算属性函数的对象
})
③格式比较固定,暴露之后,肯定要对以上所有对象进行实现:
/*
相当于data对象的状态对象
*/
const state = { //初始化数据
count: 0
}
/*
包含了n个直接更新状态的方法的对象
*/
const mutations = {
INCREMENT (state) {
state.count++
}
}
/*
包含了n个间接更新状态的方法的对象
*/
const actions = {
increment ({commit}) {
// 提交一个mutation请求
commit('INCREMENT')
},
incrementIfOdd ({commit, state}) {
if(state.count%2===1) {
// 提交一个mutation请求
commit('INCREMENT')
}
}
}
/*
包含多个getter计算属性的对象
*/
const getters = {
evenOrOdd (state) { // 当读取属性值时自动调用并返回属性值
return state.count%2===0 ? '偶数' : '奇数'
}
}
那么这里面最基本的用法就是,先在组件中调用actions中的方法,然后在actions中去调用mutations中的方法,用来直接对state数据进行改变(更新状态)。
全部都弄好后,即然要在组件中使用,就要想办法创建个store对象,即在main.js中:
①导入组件和store
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
②注册vuex的store,这样一来所有组件对象都会多一个属性$store
,这样之后就能使用store中的方法了。
new Vue({
el: '#app',
components: { Counter },
template: '<Counter/>',
store // 注册vuex的store: 所有组件对象都多一个属性$store
})
注册好之后,就要考虑如何去使用了,进入组件中。因为上面说到,我们在组件中调用actions中的方法,在actions中设置时会去帮我们调用mutations中的方法,所以在组件中,使用store对象目前只涉及到三种用法:
①this.$store.state.count
②this.$store.getters.evenOrOdd
③this.$store.dispatch('increment')
分别可以调用store对象中的state、getters和actions。注意调用actions用的是dispatch,这个看上面的图就发现了,规定用的就是dispatch。
这样一来就可以连接起每个部分了。
在上面还说到有个简化版,Counter.vue:
<template>
<div>
<p>click {{count}} times, count is {{evenOrOdd}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
mounted () {
console.log(this.$store)
},
computed: {
...mapState(['count']),
...mapGetters(['evenOrOdd'])
},
methods: {
...mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync'])
}
}
</script>
<style>
</style>
因为store里的每个对象可能会有多个方法,我们一个一个去调用还是很麻烦的,现在就可以一次性全部导入。
首先要引入import {mapState, mapGetters, mapActions} from 'vuex'
,然后使用。分别为:
①...mapState([])
②...mapGetters([])
③...mapActions([])
注意这时候调用actions,可不是dispatch了。
最后再通过结构图理解一下:
vuex里有四个结构,state、mutations、actions、getters。
更新state的是mutations、用state进行计算的是getters。
操作mutations的是actions。
这四个都是对象。
state里面包含的数据原本是从vue组件中转移过来的。
而组件要想获取到state里的数据,有四种方式:
$store.state
或 mapState()
$store.getters
或mapGetters()
在vue中对标签 / 按钮进行操作,分发事件,触发action调用,用到的就是dispatch()
或mapActions()
。action不能直接对数据进行操作,所以去请求mutation,用到的就是commit()
。这时候可能需要获取数据后再commit,所以就涉及到后台backend,用到的就是ajax请求,也就是说在vuex里可以直接进行异步操作。而到了mutation就可以直接更新状态。mutation里还有个开发工具devtool,专门用来监视mutation。
在最后再说几个问题:在正常项目中,vuex的四个结构,即store.js中的四个对象包含的数据 / 方法可能会比较多,都放在store.js中,不太好处理(当然都放在一起也行,还是需要看习惯)。所以,我们可以把它们分开来存放。
index.js:
/*
vuex核心管理模块store对象
*/
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
state.js
/*
状态对象模块
*/
export default {
todos: []
}
getters.js
/*
包含n个基于state的getter计算属性方法的对象模块
*/
export default {
// 总数量
totalSize (state) {
return state.todos.length
}
}
actions.js
/*
包含n个用于间接更新状态的方法的对象模块
*/
import {ADD_TODO, DELETE_TODO} from './mutation-types'
export default {
addTodo ({commit}, todo) {
// 提交一个comutation请求
commit(ADD_TODO, {todo}) // 传递给mutation的是一个包含数据的对象
},
deleteTodo ({commit}, index) {
commit(DELETE_TODO, {index})
}
}
mutation-types.js
/*
包含n个mutation名称常量
*/
export const ADD_TODO = 'add_todo' // 添加todo
export const DELETE_TODO = 'delete_todo' // 删除todo
mutations.js
/*
包含n个用于直接更新状态的方法的对象模块
*/
import {ADD_TODO, DELETE_TODO} from './mutation-types'
export default {
[ADD_TODO] (state, {todo}) { // 方法名不是ADD_TODO, 而是add_todo
state.todos.unshift(todo)
},
[DELETE_TODO] (state, {index}) {
state.todos.splice(index, 1)
}
}
还有就是,我们在main.js里,之前我们做的操作,需要映射组件标签+指定渲染到页面的模板:
/*
入口JS
*/
import Vue from 'vue'
import App from './App.vue'
import store from './store'
/* eslint-disable no-new */
new Vue({
el: '#app',
components: {App}, // 映射组件标签
template: '<App/>', // 指定需要渲染到页面的模板
store
})
有一个简便写法:
/*
入口JS
*/
import Vue from 'vue'
import App from './App.vue'
import store from './store'
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App), //简化
store
})
这个render是一个渲染函数,它完整写是这样的:
render: function(createElement){
return createElement(App) //返回的是<App/>
},
就相当于它会返回一个App标签,然后插入到el里就可以了。