一 功能简介
vuex的定义是全局状态管理工具,通俗的说是一个管理全局变量的工具。
vue组件化的开发基础上,暴露了很多变量的通讯方式,如父向子传递使用props,子向父传递使用emit事件传参等。
尽管有这些方法,当项目达到规模后,有一些变量是希望在大量组件可用的,如登录状态,用户名,主题颜色等。
这种变量如果采用组件间传递的方式,会变得混乱繁琐。
这是我们希望有一个统一的机制,对数据进行存储和发放。这就是vuex的由来。
二 快速上手
我们先体验一下vuex,列出要实现的功能点。
npm install vuex@next
2.1 数据存储
首先我们使用vuex,将我们准备好的数据存储起来,这里使用vuex暴露的方法createStore。
import { createStore } from 'vuex'
const store = createStore({
state () {
return { count: 666 }
},
mutations: {
add (state) { state.count++ }
}
})
export default store
我们的数据格式是安装store的要求准备的,是一个含有state和mutations属性的对象。
将该对象作为参数调用vuex暴露的方法createStore,可以返回一个store变量,导出store。
2.2 Use注册
在main.js中引入并注册store。
// vue3版示例
import { createApp } from 'vue'
import App from './App.vue'
import store from './store/index'
createApp(App)
.use(store)
.mount('#app')
2.3 数据使用
使用数据用到vuex暴露的方法useStore,这个方法可以返回一个对象,通过这个对象我们可以调用开始时传入的数据中的值和方法。
<script setup>
import {useStore} from 'vuex'
let store = useStore()
</script>
<template>
{{store.state.count}}
<button @click="store.commit('add')"></button>
</template>
三 实现解析
3.1 思路梳理
我们整理出需要暴露的两个方法。
createStore:用来存储数据,返回值可use;
useStore:用来暴露数据,返回值含有state,commit。
function createStore(options){}
function useStore(){}
export { createStore, useStore }
数据存储后放在哪儿?store在use(注册)时做了什么?useStore又是从哪儿取得的数据?
搞明白这些,需要做一点知识补充。
如何实现在vue中自由的使用数据而不必在意其组件位置,官网上有一段话:
我们可以使用一对
provide和inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这些数据。
3.2 实现:state
整理脉络可得,实现vuex分为三步:
数据存储:在createStore中对输入数据整理后返回,返回的对象包含注册函数install和所有要用的数据。
use注册:在install中,我们使用provide()对数据全局注册到app实例上。
数据使用:在useStore中,使用inject对数据进行读取,返回读取后的对象。
import { inject } from "vue"
// 数据存储
function createStore(options){
return new Store(options)
}
// 数据使用
function useStore(){
return inject('__store__')
}
class Store {
constructor(options) {
this._state = options.state
this._mutation = options.mutations
}
// use注册
install(app) {
app.provide('__store__', this)
}
}
export { createStore, useStore }
这时基本功能已经可以使用了,我们可以试试。
存储数据:
import { createStore } from './gvuex'
const store = createStore({
state () {
return { count: 123 }
},
mutations: {
add (state: any){
state.count++
}
}
})
export default store
将这个导出的store注册。
数据使用:
<script setup>
import {useStore} from './store/gvuex'
let store = useStore()
</script>
<template>
{{store._state}}
</template>
可以看到store.state是一个对象,我们可以通过store. state.count访问数据。
3.3 实现:mutation
我们在修改数据的时,希望通过commit调用mutation,进行修改。
通过调用方式我们可知:commit是挂在Store上的一个函数,根据传入的参数调用对应的mutation。
store.commit('add')
向Store类中加入commit方法。
class Store {
...
commit = (type: any) => {
this._mutation[type](this.state)
}
}
我们知道在commit中。
如果我们传入不正确的type值怎么处理?如果我们需要携带参数怎么处理?
在原来代码基础上更改如下,我们添加了type校验和参数传递。
class Store {
...
commit = (type: any, payload: any) => {
const entry = this._mutation[type]
entry && entry(this.state, payload)
}
}
在页面中调用mutation事件。
<template>
{{store._state}}
<button @click="store.commit('add')"></button>
</template>
<script setup>
import {useStore} from '../store/gvuex'
let store = useStore()
</script>
但是点击按钮后,页面上的数据没有发生变化。
3.4 实现:数据响应
我们在commit中打印log。
class Store {
...
commit = (type: any, payload: any) => {
const entry = this._mutation[type]
entry && entry(this.state, payload)
console.log(this.state)
}
}
点击按钮后发现,数据已经成功改变。
但是页面没有更新。
这是由于provide和inject默认为非响应式操作,官网在provide和inject一节出给出了响应式的解决方案。
Vue3:这是因为默认情况下,
provide/inject绑定并不是响应式的。我们可以通过传递一个refproperty 或reactive对象给provide来改变这种行为。Vue2:
provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
这里给出vue3的响应实现方式:
import { inject, reactive } from "vue"
...
class Store {
constructor(options: any) {
this._state = reactive({data: options.state()})
this._mutation = options.mutations
}
get state() {
return this._state.data
}
...
}
vue2是通过new一个vue实例,数据挂在vue实例的data下,直接访问返回的vue实例就可以访问到变化的值。
class Store {
constructor (options) {
this.vm = new _Vue({
data: {
state: options.state
}
})
}
get state () {
return this.vm.state
}
}