概念理解
- 什么是Vuex?
一个状态管理器,管理所有组件的状态。
- 为什么使用Vuex?
1.多个组件依赖于同一个状态时。
2.不同的组件需要变更同一个状态。
以上都会导致组件之间的传参变得非常繁琐。
Vuex使用
- 安装
npm i vuex --save
- 项目目录
├── src/
│ ├── main.js # 入口
│ ├── App.vue # 根组件
│ ├── store # vuex配置
│ │ ├── index.js # vuex入口
- index.js中创建实例并导出
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production'
// 创建实例
export default new Vuex.Store({
strict:debug,// 严格模式,不要再发布模式下启用严格模式!
state:{
},
getter:{
},
mutations:{
},
actions:{
}
})
- 在mian.js中引入
import store from './store/index'
import Vue from 'vue';
import App from './App.vue';
const app = new Vue({
store,
render: h => h(App)
}).$mount('#app');
Vuex的核心属性
- Vuex的5个核心属性是?
state, mutations, getters, actions, modules
一个简单的vuex已经搭建完成,接下来,我们依次来理解一下Vuex的5个属性,以便于我们知道要怎样使用。
state
这是状态管理中的存储库,所有的数据都会在这里被定义和设定初始值。
state:{
count:0
}
- 如何访问state?
<p>{{$store.state.count}}</p>
mutations
- 获取state很容易,那么如何改变state?
改变state的唯一方式,就是commit mutation(提交 mutation)。简而言之,mutation就是封装了很多方法的一个集合,而这些方法可以用来改变state状态。
mutations:{
// 加法
add(state,n){
state.count += n
},
// 减法
reduce(state){
state.count -= 1
}
}
- 如何使用mutations?
使用$store.commit提交。
<div @click="$store.commit('add',10)"></div>
<p>{{$store.state.count}}</p>
commit可以接收多个参数,第一个参数必须是moutations中定义的方法名,第二参数是外部传入方法中的参数。而方法add中,第一个参数,是全局的state状态,第二个参数则是外部传入参数。
如上代码,点击div元素,触发commit,接着触发moutations中的add函数,使得count与传入的参数相加,count状态改变。
值得注意的是,mutations中的函数,必须只能是同步函数。
getters
getters是对state做处理计算,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。
- 如何使用getters?
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters:{
getCount:function(state){
return state.count + 100
},
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
页面组件中使用:
<p>{{$store.getters.getCount}}</p>
<p>{{$store.getters.doneTodos}}</p>
getters接收一个参数state:这是全局的state。
- 怎么通过getters,实现在组件内通过特定条件来获取state的状态?
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters:{
getByID: (state) => {
return function(id){
return state.todos.filter(todo => todo.id === id)
}
}
}
页面组件中使用:
computed:{
getTodoById(){
return this.$store.getters.getByID
}
},
mounted:{
this.getTodoById(2)
}
actions
actions也是方法的封装,同mutations功能是一样的,不同的是,actions可以异步改变状态,而mutations只能同步改变状态。
- 如何使用actions?
actions:{
addCount(context, params){
context.commit('add', params)
},
reduceAction({ commit }, params){
if(params){
commit('reduce')
}
}
}
actions中的方法,可以接收多个参数,第一个参数是指当前上下文,第二个参数则是从外部提交传递过来的参数。
context指的是当前当前上下文,也就是store,其中包括它的许多属性:state, commit, dispath, getters等
可以用解构赋值
{commit},将当前上下文的commit,指向参数,方便使用。
在页面组件中使用:
<div @click="$store.dispatch('addCount',10)"></div>
<div @click="$store.dispatch('reduceAction',true)"></div>
从上可以看出,action提交的是mutation,而不是直接改变状态。
- action和mutation有何区别?
1.mutation直接改变状态,action是提交mutaion
2.mutation是同步操作,action可以异步操作。
3.提交方式,mutation使用commit,action使用dispatch
4.接收参数,mutation第一个参数是state,action第一个参数是context。
- 如何使用action进行异步操作?
actions:{
set_number_A({commit},data) {
return new Promise((resolve,reject) => {
setTimeout(() => {
commit('set_num',data)
resolve();
},3000)
})
}
}
// 组件中使用:
this.$store.dispatch('set_number_A',10).then(()=>{
})
- Vuex中有两个action,分别是actionA和actionB,其内都是异步操作,在actionB要提交actionA,需在actionA处理结束再处理其它操作,怎么实现?
可以用ES6的 async 和 await 来实现。
actions:{
async actionA({commit}){
//...
},
async actionB({dispatch}){
await dispatch ('actionA')//等待actionA完成
// do something
}
}
以上是vuex的四大核心属性,那么modules这个属性比较复杂,在下一小节分开讲,接着往下看。
模块化处理
- 为什么要使用模块化?
使用单一的状态树时,应用的所有状态都会集中在一个比较大的对象上,如果应用非常复杂,store就会变得非常臃肿,所以,我们可以把store分割成模块,每个模块中都拥有自己的state, mutations, actions, getters,甚至是往下嵌套的子模块。
- 如何使用模块化?
我们来调整一下我们的目录结构:
├── src/
│ ├── main.js # 入口
│ ├── App.vue # 根组件
│ ├── store # vuex配置
│ │ ├── modules # vuex模块管理
│ │ │ ├── moduleA.js # 模块A
│ │ │ ├── moduleB.js # 模块B
│ │ ├── index.js # vuex入口
moduleA.js,moduleB.js中写入:
const state={
//...
}
const getters={
//...
}
const mutations={
//...
}
const actions={
//...
}
export default{
state,
getters,
mutations,
actions
}
index.js文件中重写:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
import moduleA from './module/moduleA'
import moduleB from './module/moduleB'
const debug = process.env.NODE_ENV !== 'production'
// 创建实例
export default new Vuex.Store({
strict:debug,
modules:{
moduleA,
moduleB
}
})
- 在模块中,getter和mutation和action中怎么访问全局的state和getter?
在模块中,getter和mutation接收的第一个参数state,是模块的state,也就是局部的state。
mutations:{
// 加法
add(state,n){
state.count += n
}
}
getters:{
getCount:function(state,getters,rootState,rootGetters){
return state.count + 100
}
}
actions:{
addCount(context, params){
context.commit('add',params)
}
}
1.mutation中不可以访问全局state,只能访问模块state。
2.模块getters中,可接收多个参数,第一个state是模块的state,第二个参数是其他的getters,第三个参数rootState是全局的state,第四个参数rootGetters是全局的getters。
3.模块action中,第一个参数是context,其中,context.rootState访问全局state,context.rootGetters访问全局的getter。
- 模块的命名空间
默认情况下,模块内部的action、mutation和getter是注册在全局命名空间,如果多个模块中action、mutation的命名是一样的,那么提交mutation、action时,将会触发所有模块中命名相同的mutation、action。
这样有太多的耦合,如果要使模块具有更高的封装度和复用性,可以通过添加namespaced: true 的方式使其成为带命名空间的模块。
export default{
namespaced: true,
state,
getters,
mutations,
actions
}
在带命名空间的模块中提交全局的mutation和action
将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
this.$store.dispatch('actionA', null, { root: true })
this.$store.commit('mutationA', null, { root: true })
在带命名空间的模块内注册全局的action
actions: {
actionA: {
root: true,
handler (context, data) { ... }
}
}
在组件中提交modules中的带命名空间的moduleA中的mutationA
this.$store.commit('moduleA/mutationA',data)
辅助函数map
mapState
当一个组件需要获取多个状态的时候,如果将每一个状态都声明为计算属性,会有些重复和冗余。
我们可以使用mapState辅助我们生成计算属性:
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
computed: mapState(['count', 'text'])
由于 mapState 函数返回的是一个对象,在ES6的写法中,我们可以通过对象展开运算符,可以极大的简化写法:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
mapGetters
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
mapMutations
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')`
})
}
}
mapActions
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')`
})
}
}
v-model的处理
使用带有 setter 的双向绑定计算属性:
// 组件中
<input v-model="message">
// 计算属性中
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
// mutation中
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
严格模式的作用
在严格模式下,无论何时发生了状态变更且不是由 mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
在Vuex.Store 构造器选项中开启,如下
const store = new Vuex.Store({
strict:true,
})
vuex插件
我们来调整一下我们的目录结构:
├── src/
│ ├── main.js # 入口
│ ├── App.vue # 根组件
│ ├── store # vuex配置
│ │ ├── plugins # vuex插件管理
│ │ │ ├── logger.js # 插件
│ │ ├── modules # vuex模块管理
│ │ │ ├── moduleA.js # 模块A
│ │ │ ├── moduleB.js # 模块B
│ │ ├── index.js # vuex入口
Vuex插件就是一个函数,它接收 store 作为唯一参数。在Vuex.Store构造器选项plugins引入。
export default function createPlugin(param){
return store =>{
//...
}
}
在store/plugin.js文件中写入
import createPlugin from './plugin.js'
const myPlugin = createPlugin()
const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})
例子,引入持久化插件:
import createPersistedState from 'vuex-persistedstate' // 引入持久化插件
const store = new Vuex.Store({
// ...
plugins: [createPersistedState({ storage: window.sessionStorage })],
})