VueX
在之前写的文章中,已经对Vue组件通信的N种方式进行了总结,VueX 作为Vue组件通信的最终解决方案,今天就来总结一下VueX。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension ,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
引用官方文档中对VueX的介绍,其主要设计思想主要为集中式,可预测,同时他也是与Vue有着强耦合的。
引用官方文档中的一张数据流向图
在图中,有3个核心的概念:
State:作为所有数据的存放位置。
Mutations:修改数据的 唯一 方式。
Actions:扩展mutation存在的不足(必须为同步事件),在action中可以使用异步的事件,最终在异步事件完成后调用mutation去修改数据
VueX的使用
接下来简单介绍VueX的使用,更多详细的使用方式可以去官网中学习并自己跑一下demo。
引入
VueX作为Vue的一个插件库,其使用方法就显而易见了。
- 创建一个store.js,并引入需要的库
- 作为一个插件,肯定需要Vue.ues()一下
- new一个VueX.Store实例
- 导出实例
- 在main.js中注册一下
// 1.先创建一个store.js
// 引入Vue和VueX库
import Vue from 'vue'
import Vuex from 'vuex'
// 2.安装VueX插件
Vue.use(Vuex)
// 3.定义配置项,注意!!! new的是VueX.Store实例
// 4. 导出实例
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
import Vue from 'vue'
import App from './App.vue'
// 在main.js中引入创建的VueX的store
import store from './store'
Vue.config.productionTip = false
new Vue({
// 5. 在vue实例中注册
store,
render: h => h(App)
}).$mount('#app')
简单使用介绍
简单写个例子,四个api直接使用
// 在 Vuex.Store 中定义各个项
export default new Vuex.Store({
state: {
// 直接定义值
count: 0
},
getters:{
// getters 可以当作计算属性来理解,最后需要返回计算完的值
// getters,接受两个形参,注意是形参,第一个为state,第二个为getters
doubleCount(state,getters){
return state.count * getters.diploid
},
diploid(){
return 2
}
// 最后 getters 可以返回一个函数,相当于以方法的调用,这样就可以进行传参
// doubleCount: (state) => (diploid) => {
// return state.count * diploid
// },
},
mutations: {
// mutations中需要设置事件类型,回调函数,然后通过commit进行触发
// 回调函数会默认接受 state作为第一个形参,后续为commit是传入的参数
// !!注意1: mutations中的回调函数必须为同步事件
// !!注意2: VueX利用的是vue底层的响应式原理,所以在修改值时,比如对obj添加新的属性
// 同样需要通过Vue.set去添加
add(state){
++state.count
},
addAny(state, num){
state.count += num
}
},
actions: {
// actions 会传入一个上下文(同样为形参),用来commit=》mutations事件
// 上下文中可以获取到 commit、state、getters 等
// !!注意:由于VueX.Store有模块的存在,所以此处传入的时一个上下文
// actions 需要用dispatch来触发
addPuls(context){
setTimeout(() => {
context.commit('addAny',2)
}, 1000);
}
// 也可以用解构的方式获取commit
// addPuls({ commit }){
// setTimeout(() => {
// commit('addAny',2)
// }, 1000);
}
},
})
// app组件
<template>
<div id="app">
<p>当前个数:{{ count }}</p>
<button @click="add">+1</button><br>
<button @click="addPuls">蓄力+</button><br>
<input type="text" v-model.number="val"><button @click="addAny">任意+</button>
<hello-world></hello-world>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components:{
HelloWorld
},
methods:{
add(){
// 使用commit,提交需要触发的mutations事件
this.$store.commit('add')
},
addPuls(){
// 使用dispatch,提交需要触发的actions事件
this.$store.dispatch('addPuls')
},
addAny(){
// 提交commit的时,可以传参
this.$store.commit('addAny',this.val)
},
},
// Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 中返回某个状态
computed: {
count () {
return this.$store.state.count
}
},
data() {
return {
val: 0
}
},
}
</script>
// HelloWorld子组件
<template>
<div class="hello">
<h1> 子组件中的值: {{ this.$store.state.count }}</h1>
<h1> 子组件中的双倍值: {{ this.$store.getters.doubleCount }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
}
</script>
运行使用,commit和dispatch均顺利触发
稍微进阶使用
每次使用VueX,无论是引用值或者修改值,都需要this.$store.state.XXX或this.$store.commit('XXX'),是比较麻烦的,VueX中提供了相应的映射方法,用于简便使用。
<template>
<div id="app">
<p>当前个数:{{ count }}</p>
<button @click="add">+1</button><br>
<button @click="addPuls">蓄力+</button><br>
<input
type="text"
v-model.number="val"
><button @click="addAny">任意+</button>
<hello-world></hello-world>
<p>count值:{{ count }}</p>
<p>countAlias值:{{ countAlias }}</p>
<p>countPlusLocalState值:{{ countPlusLocalState }}</p>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld';
import { mapState } from 'vuex';
export default {
// 映射VueX中的state
name: 'App',
components: {
HelloWorld
},
methods: {
add() {
this.$store.commit('add');
},
addPuls() {
this.$store.dispatch('addPuls');
},
addAny() {
this.$store.commit('addAny', this.val);
}
},
// 同时,打印一下看mapState
mounted() {
console.log(
'mapState',
mapState({
count: state => state.count,
countAlias: 'count',
countPlusLocalState(state) {
return state.count + this.localCount;
}
})
);
},
computed: {
...mapState({
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState(state) {
return state.count + this.localCount;
}
})
},
data() {
return {
val: 0,
localCount: 10
};
}
};
</script>
可以看到,mapstate最后是一个对象,每个属性都是一个方法,
而在computer中,是可以接受一个函数的,所以就可以通过解构的方法将state映射到computer上。
同理可以得到其他映射方法的使用,当然,更具体的可以去官网看。
mpaGetters
computed: {
...mapGetters({
count: 'doubleCount'
})
// 可以直接为一个数组 ['doubleCount'],当然这样就需要命名一致了
},
mapMutations
methods: {
...mapMutations({
add: 'add'
})
// 可以为数组 ['add']
}
mapActions
methods: {
...mapActions({
addPuls: 'addPuls'
})
// 同样可以为数组 ['addPuls']
}
模块化与插件
最后随着应用规模的逐渐壮大,内部逻辑变得十分复杂,就需要我们对vuex进行模块划分
引用官方的例子作为样例
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
最后,Vuex 还可以通过插件进行拓展,比如对VueX的数据进行持久化等,vuex 的 store 提供 plugins 的配置项,通过这个配置项可以安装插件。Vuex 插件就是一个函数,它接收 store 作为唯一参数:
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload }
console.log('====================================');
console.log(mutation, state);
console.log('====================================');
})
}
然后像这样使用:
const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})
在界面执行
实现 MyVueX
根据以上的使用介绍,来自己造一个简易版的VueX。
install实现
// myVueX.js
let Vue
// 定义store类
class Store {
constructor(options = {}) {
this.state = options.state
}
}
// 创建install方法
function install(_vue) {
Vue = _vue
Vue.mixin({
// 因需要等vue实例创建时,获取到store,再将其挂载到prototype上,
// 所以需要将install函数执行的时间延后,常规套路就是使用mixin在beforeCreate的时候执行
beforeCreate() {
// 只有在根实例的时候才添加store
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
// 导出install和Store
export default {
install,
Store
}
// store.js
import Vue from 'vue'
import Vuex from './myVueX'
Vue.use(Vuex)
// 先简单定义个state
export default new Vuex.Store({
state: {
name: 'myVueX'
}
})
// app组件
<template>
<div id="app">
// 在页面中展示
<p>App组件{{ this.$store.state.name }}</p>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
可以看到 页面中的state已经成功获取到值
mutations
现在实现mutations配置和commit方法
let Vue
class Store {
constructor(options = {}) {
this.state = options.state
this._mutations = options.mutations
}
commit(type, ...args) {
console.log(type, args);
const entry = this._mutations[type]
if (!entry ) {
console.error(`unknown mutation type: ${type}`);
return
}
entry(this.state, ...args)
}
}
function install(_vue) {
Vue = _vue
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
export default {
install,
Store
}
export default new Vuex.Store({
state: {
name: 'myVueX',
count: 0
},
mutations: {
add(state) {
++state.count
console.log(state);
},
},
})
<template>
<div id="app">
<p>App组件{{ this.$store.state.name }}</p>
<p>数值:{{ this.$store.state.count }}</p>
<button @click="add">+1</button><br>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
add() {
this.$store.commit('add');
}
}
};
</script>
在页面中运行,可以看到commit触发的事件执行了,但是数据并没有刷新
state响应式
现在需要解决数据响应式的问题,对于数据响应式,完全可以利用vue本身提供的特性来实现。
class Store {
...
constructor(options = {}) {
// 利用vue的数据响应式特性,来创建一个响应式的数据
this._vm = (new Vue({
data: {
?state: options.state
}
}));
this.state = this._vm._data.?state
this._mutations = options.mutations
}
...
}
现在再来看一下,数据已经可以响应式刷新了
稍微优化一下,让state不可以被覆盖
constructor(options = {}) {
// 利用vue的数据响应式特性,来创建一个响应式的数据
this._vm = (new Vue({
data: {
?state: options.state
}
}));
this._mutations = options.mutations
}
// get state时将做好响应式处理的数据返回
get state() {
return this._vm._data.?state_data
}
// 当想直接覆盖state时,提示不允许被覆盖
set state(val) {
console.error('state 不允许被覆盖');
}
action
class Store {
constructor(options = {}) {
// 利用vue的数据响应式特性,来创建一个响应式的数据
this.vm = (new Vue({
data: {
?state: options.state
}
}).);
this._mutations = options.mutations
this._actions = options.actions
}
...
dispatch(type, ...args) {
const entry = this._actions[type]
if (!entry) {
console.error(`unknown actions type: ${type}`);
return
}
entry(this, ...args)
}
...
actions: {
addPuls(context) {
setTimeout(() => {
context.commit('add')
}, 1000);
}
},
在页面中运行,可以顺利执行
getter
class Store {
constructor(options = {}) {
...
const getters = {}
for (let x in options.getters) {
Object.defineProperty(getters, x, {
get: () => {
return options.getters[x](this.state)
}
})
}
this.getters = getters
...
}
getters: {
doubleCount(state) {
return state.count * 2
},
},
在页面中运行
最后挖坑
以上基本是可以实现一个最简单版的 VueX 了
但是模块化,映射的辅助函数,直接修改state的问题,均没有实现,先挖好坑,后续填上,以及自己实现一个 VueX 插件,加深对 VueX 的使用以及理解。