学习下Vuex

本文详细介绍了Vuex的状态管理库的安装与使用,包括State、Getters、Mutations和Actions的核心概念。通过实例展示了如何在Vue组件中使用mapState、mapGetters、mapMutations和mapActions辅助函数。同时,探讨了Vuex的命名空间和模块化,以及如何处理异步操作。文章强调了Vuex如何通过响应式机制将状态映射到组件并保持状态更新。
摘要由CSDN通过智能技术生成


官网地址

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'
......

使用

  1. 新建 store.js 文件
    在这里插入图片描述
  2. 新建 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++
    }
  }
})
  1. 在 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')
  1. 在组件中使用
<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>
  1. 点击按钮发现 $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)

  1. 使用 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. 运行结果展示
    在这里插入图片描述
  2. 点击按钮:所有的计算属性的值都成功 +1
    在这里插入图片描述

Getters

getters 可以认为是 store 的计算属性

  1. 定义: 用法与 计算属性 用法一致(也是具有缓存的。如果依赖的 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)
    }
},
......
  1. 使用:getters 会 暴露为 对象,可以通过 store.getters 以属性的方式来访问这些值
// 在组件中访问
this.$store.getters.doneTodos
  1. 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
  1. 给 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)

  1. 使用 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>
  1. 运行结果
    在这里插入图片描述

Mutations

更改 Vuex 的 store 中的状态的唯一方法就是提交 mutation Mutations 中的每一个 mutation 都有一个字符串的 事件类型(type) 和一个 回调函数(handler) 这个回调函数就是用来给我们进行状态更改的地方,并且会接受 state 为第一个参数

注意:mutation 中的方法必须是同步的。假如调用了两个异步 mutation 来改变状态,状态改变的不能区分是那个回调触发

  1. 定义 用法与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++
    }
  }
}
  1. 调用 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>
  1. 运行结果,点击 按钮,执行结果
// 控制台展示
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 调用

  1. 使用 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>
  1. 执行效果
    • 点击 updateCount 按钮,count 值 += 10
    • 点击 updateTodos 按钮,todos 值 push 了一个对象
    • 点击 addCount 按钮,count 值 += 1
    • 点击 addTodos 按钮,todos 值 push 了一个对象

Actions

Actions 类似于 Mutations,不同的地方在于

  • Action 提交的是 mutation,而不是直接变更状态
  • Action 可以包含任意异步操作

注意:因为 actions 可以处理异步函数,所以也可以返回 promise

  1. 使用 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)
    }
}
  1. 在组件中使用 (通过 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 调用_

  1. 使用 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',
			......
		})
	}
}
......
  1. 使用方式与 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 实现数据实时监

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值