什么是vuex?
vuex是专为 Vue.js 应用程序开发的状态管理模式,它可以对状态进行集中管理,并且状态是响应式的,在状态改变时,会同时改变其他页面的该数据的状态。vuex其实是一个对象。
什么时候用vuex?
当多个界面需要共享同一个状态时使用。
例如,登录状态,用户需要登录才能使用页面,多个页面都是需要登录状态的
例如,地理位置信息,美团饿了么,首页需要位置信息,商家界面,送餐界面都需要位置信息
例如,收藏,购物车,多个页面都有收藏按钮,收藏加购的状态也应该是响应的
如果只是父子组件之间需要共同的状态,不应该使用vuex,这样反而会导致vuex变得臃肿,不方便管理。
vuex的使用方法
- 安装vuex:npm install vuex --save
- 引入和安装
// store文件夹中的index.js
// 1.导入vuex
import Vue from 'vue'
import Vuex from 'vuex'
// 2.安装vuex
Vue.use(Vuex)
// 创建vuex实例(注意是使用store)
const store = new Vuex.Store({
// 4. 配置vuex
state: {}
})
// 导出vuex实例store
export default store
// main.js
// 导入store 挂载到vue实例上
import store from './store'
new Vue({
// 挂载后,内部会生成$store
store,
render: h => h(App)
}).$mount('#app')vuex使用图例
Actions:用来监测异步操作
Mutations:用来监测同步操作
State:vuex.Store中的state,用来存放多个界面公共使用的状态(数据)
Devtools:监测同步数据的改变
Backend API:后端接口
执行流程:
state中的数据可以渲染到vue的组件中,但是组件要更改state中的数据的话,需要通过actions,actions连接着后端API,主要处理异步的操作,当actions传入mutations时,会转为同步操作,此时,这个同步操作,就会被devtools检测到,我们就可以知道是哪个组件更改了数据。
如果我们直接跳过前面的过程修改state,那么devtools就无法监测到是哪个组件更改了数据。如果是同步的操作,可以直接通过mutations修改,devtools是可以监测到的。异步的操作必须通过actions,否则devtools无法监测到异步操作。
在vuex中改变数据图例
vuex的核心概念
1. State
state用于储存多个界面公共使用的状态(数据)
state是单一状态树,也叫做单一数据源。意思是只使用一个state管理全部的数据。也就是说,每个应用只包含一个vuex的实例。
在state中定义的数据,都会被放入响应式系统。也就是说,如果我们在之后修改了state中的数据,也会动态地响应到页面上。如果数据是后添加的,不会有响应式,需要使用特定的,vue包装好的响应式方法。
state中状态的获取
this.$store.state.count2. Getter
getter可以理解为state的计算属性,它和vue中的计算属性一样,可以返回计算后的结果,并且这个结果也会被缓存起来,只有发生改变才会重新计算。
getter数据的获取:属性 和 方法,区别在于一个直接返回计算后的值,一个返回函数
1. 通过属性来获取,是存在缓存的,但是不能对数据进行进一步的读取。在这个例子中,不能通过TodoList读取到TodoList中的id/text/done等。
<template>
<div id="app">
<!-- 显示整个对象,无法进一步获取TodoList中的值 -->
{{this.$store.getters.TodoList}}
</div>
</template>const store = new Vuex.Store({
state: {
count: 0,
todos: [
{ id: 1, text: '123', done: true },
{ id: 2, text: '456', done: false }
]
},
getters: {
TodoList (state) {
return state.todos.filter(todo => todo.done === true)
}
}
})2. 通过方法来获取,不存在缓存,每次都会调用一次方法,可以通过TodoList读取到TodoList中的id/text/done等。
<template>
<div id="app">
<!-- 显示整个对象 -->
{{this.$store.getters.getTodoById(2)}}
<!-- 显示getTodoById中的text的值456 -->
{{this.$store.getters.getTodoById(2).text}}
</div>
</template>const store = new Vuex.Store({
state: {
count: 0,
todos: [
{ id: 1, text: '123', done: true },
{ id: 2, text: '456', done: false }
]
},
getters: {
// getter返回方法的简写方式
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
},
// getter返回方法的完整方式
getTodoById (state) {
return function (id) {
return state.todos.find(todo => todo.id === id)
}
}
}
})3. Mutation
mutation用于更改同步的状态。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
事件类型指的是mutation中的函数名,回调函数则是mutation中函数的执行内容,由于这个函数是在commit之后触发的,所以是一个回调函数。
export default {
name: 'App',
created () {
// 触发mutation中的increment方法,执行该回调,这个increment就是事件类型(type)
this.$store.commit('increment')
}
}const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count ++
}
}
})mutation的参数传递,这个传递的参数叫做载荷——payload,不用在意,这只是一个名字而已。
// 1.传递一个参数
this.$store.commit('increment', 10)
// 2.传递多个参数——对象
this.$store.commit('increment', {
count: 10,
newName: 'mia'
})
// 3.对象风格的提交方式——就是将事件类型也放入对象中传递
this.$store.commit({
type: 'increment',
count: 10
})传递的参数如何使用呢?只要在state后面添加一个payload形参就可以接收到了。
mutations: {
increment (state, payload) {
// 如果只穿了一个值,那就直接使用
state.count += payload
// 如果传入了对象,就按照对象的方法取值即可
state.count += payload.count
}
}- 需要注意的是,由于vuex是响应式的,所以mutation也要遵循vue的规则,只有这样才能保证在mutation更改了数据后,该数据能够响应式地发生改变。
- 和vue只有一开始在data中声明的数据才会变成响应式的类似,mutation中更改的值,也最好事先在state中进行声明,如果未声明,和vue中一样,可以通过Vue.set()进行添加对象的新属性,或者使用新对象替换老对象等。
- 另外,state中的值,即使不通过mutation,页面中也是会发生改变的,但是这种改变不会被devtools检测到,不利于我们的开发。类似的,如果mutation中是异步的操作,当我们触发mutation中的函数时,异步的操作还未执行,那么devtools就无法检测到这个数据的变化,因此在mutation中,一定是同步的操作。那该如何执行异步操作并被有效检测到数据的变化呢?那就要使用到Action了。
4. Action
action用于异步状态的更改。
action同样可以传递参数,和mutation中是一样的,不再赘述。
与mutation有所不同的是,action接收的第一个参数是context,context 是与 store 实例具有相同方法和属性的对象。因此可以使用解构赋值或者对象的方法选取到getter、state,也可以直接拿到store对象的commit、dispatch方法。
action需要先通过commit提交mutation,接着可以通过dispatch触发action中的函数。dispatch 的好处是可以处理 被触发的 action 中的函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。方便了我们对数据的处理。
export default {
name: 'App',
created () {
this.$store.dispatch('someMutation').then((res) =>{})
}
}const store = new Vuex.Store({
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
})5. Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。由于这个原因,就有了module,module可以让我们将状态分为一个个模块,便于代码的管理。
每个模块都有自己的state、mutations、actions、getters属性,也可以在模块中进一步嵌套模块。模块内都属于局部状态,对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。对于模块内部的 action,context也是局部的,根节点状态则为 context.rootState。rootState对象中包含了所有模块的state数据,还有一个rootGetters对象,储存了所有getter中的数据。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: {
actionFn (context) {
console.log(context.rootState, context.rootGetters)
}
},
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
this.$store.state.a // -> moduleA 的state
this.$store.getters // -> moduleA 的getters默认情况下,这些模块都是都是注册在全局状态的,如果希望模块注册在局部,可以添加namespaced: true。
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
state: () => ({ ... }),
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
}
}
}
})辅助函数 map...
<template>
<div id="app">
{{count}}
<hr/>
{{countAlias}}
</div>
</template>// 引入mapState
import { mapState } from 'vuex'
// 1. 对象方法,自定义名称和state中的属性一一对应
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count'
})
// 2. 数组方法,适用于名称和state中的属性名一模一样的情况
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])