在前端工程化開發的今天,
vuex
、redux
成爲了咱們項目中狀態管理的上上之選。關於如何使用它,相信這已經成爲前端開發者的必備技能之一了。今天,咱們來一塊兒嘗試進階一下,本身實現一個狀態管理器來管理咱們的項目,讓咱們能夠在之後的開發過程當中能夠更加迅捷的定位問題,能夠在遇到面試官提出(您好,能夠描述下vuex
的實現原理嗎?)相似問題的時候能夠更加從容的回答。前端
相信大多數同窗在平常開發中會這樣使用vuex
vue
// store.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
text: "Hello Vuex"
},
getters: {},
mutations: {},
actions: {},
modules: {}
)}
複製代碼
咱們在引入vuex
以後主要作了如下兩步操做面試
Vue.use(Vuex)
vuex
vuex
必須得向外面暴露一個install
方法,這個install
方法能夠幫助咱們在vue
原型上註冊咱們的功能。 new Vuex.Store()
redux
看到new
了,顧名思義咱們的vuex
不只須要暴露出install
方法,一樣還須要暴露出一個store
的類,上面掛載了咱們使用到的state、muations、actions、getters
等參數以及commit、dispatch
等方法前端工程化
經過上面的簡要分析咱們能夠了解到咱們須要建立一個install
函數和一個store
的類,而後暴露出來bash
新建my-vuex.js
app
// my-vuex.js
let Vue
const install = _Vue => {
// vue.use()執行的時候,會將vue做爲參數傳入進來,這裏咱們用一個變量接收 vue
Vue = _Vue
}
class Store {
}
export default {
install,
Store
}
複製代碼
vuex
基本的結構咱們已經搭建好,接下來咱們來繼續完善install
函數。install
函數應該是一個實現掛載全局$store
的過程。異步
// my-vuex.js
let Vue
const install = _Vue => {
// vue.use()執行的時候,會將vue實例做爲參數傳入進來,這裏咱們用一個變量接收
Vue = _Vue
// Vue.mixin幫助咱們全局混入$store
Vue.mixin({
beforeCreate(){
// 這裏的this指的是vue實例
const options = this.$options
if(options.store){
// 判斷當前組件內部是否認義了store,若是有則優先使用內部的store
this.$store = typeof options.store === 'function' ? options.store() : options.store
} else if(options.parent && options.parent.$store){
// 組件內部沒有定義store,則從父組件下繼承$store方法
this.$store = options.parent.$store
}
}
})
}
class Store {
}
export default {
install,
Store
}
複製代碼
上面咱們已經經過vue.use
將$store
實例注入到了vue
上,下面咱們繼續完善store
裏面的功能async
咱們一般會在組件中使用this.$store.state
來獲取數據,因此這裏咱們須要在Store
類上定義獲取state
時的方法
my-vuex.js
代碼以下
// 省略其他代碼
class Store {
constructor(options={}){
this.options = options
}
get state(){
return this.options.state
}
}
export default {
install,
Store
}
複製代碼
測試一下
store.js
// store.js
import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
text: "Hello Vuex"
},
getters: {},
mutations: {},
actions: {},
modules: {}
})
複製代碼
App.vue
<template>
<div id="app">
<h1>{{getState}}</h1>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.state.text
}
}
}
</script>
複製代碼
運行代碼後會發現展現出了預期的 Hello Vuex
可是在這裏有一個小問題,咱們都知道vue的數據是響應式的。若是咱們以下去操做:
// App.vue
<template>
<div id="app">
<h1>{{getState}}</h1>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.state.text
}
},
mounted(){
setTimeout(() => {
console.log('執行了')
this.$store.state.text = 'haha'
}, 1000)
}
}
</script>
複製代碼
代碼運行後會咱們發現頁面的數據並無變化,因此這裏咱們要將state
改形成響應式的數據。這裏提供兩種方法
vue
自身提供的data
響應式機制// my-vuex.js
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = new Vue({
data: {
state: options.state
}
});
}
get state(){
return this.vmData._data.state
}
}
複製代碼
vue
2.6.0新增的Vue.observable()
實現// my-vuex.js
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = {
state:Vue.observable(options.state || {})
}
}
get state(){
return this.vmData.state
}
}
複製代碼
my-vuex.js
代碼以下
// my-vuex.js
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = {
state:Vue.observable(options.state || {})
}
// 初始化getters
this.getters = {}
// 遍歷store上的getters
Object.keys(options.getters).forEach(key=>{
//爲getters裏全部的函數定義get時執行的操做
Object.defineProperty(this.getters,key,{
get:()=>{
return options.getters[key](this.vmData.state)
}
})
})
}
get state(){
return this.vmData.state
}
}
複製代碼
測試一下
store.js
import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
text: "Hello Vuex"
},
getters: {
getText(state){
return state.text
}
},
mutations: {},
actions: {},
modules: {}
})
複製代碼
App.vue
<template>
<div id="app">
<h1>{{getState}}</h1>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.getters.getText
}
}
}
</script>
複製代碼
my-vuex.js
代碼以下
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = {
state:Vue.observable(options.state || {})
}
// 初始化getters
this.getters = {}
// 遍歷store上的getters
Object.keys(options.getters).forEach(key=>{
//爲getters裏全部的函數定義get時執行的操做
Object.defineProperty(this.getters,key,{
get:()=>{
return options.getters[key](this.vmData.state)
}
})
})
// 初始化mutations
this.mutations = {}
// 遍歷mutations裏全部的函數
Object.keys(options.mutations).forEach(key=>{
// 拷貝賦值
this.mutations[key] = payload=>{
options.mutations[key](this.vmData.state,payload)
}
})
// commit實際上就是執行mutations裏指定的函數
this.commit = (type,param)=>{
this.mutations[type](param)
}
}
get state(){
return this.vmData.state
}
}
複製代碼
測試一下
store.js
import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
text: "Hello Vuex"
},
getters: {
getText(state){
return state.text
}
},
mutations: {
syncSetText(state,param){
state.text = param
}
},
actions: {},
modules: {}
})
複製代碼
App.vue
<template>
<div id="app">
<h1>{{getState}}</h1>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.getters.getText
}
},
mounted(){
setTimeout(() => {
console.log('執行了')
this.$store.commit('syncSetText','同步更改數據')
}, 1000)
}
}
</script>
複製代碼
action與mutations原理相似,一樣dispatch實現方法與commit相似
my-vuex.js
代碼以下
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = {
state:Vue.observable(options.state || {})
}
// 初始化getters
this.getters = {}
// 遍歷store上的getters
Object.keys(options.getters).forEach(key=>{
//爲getters裏全部的函數定義get時執行的操做
Object.defineProperty(this.getters,key,{
get:()=>{
return options.getters[key](this.vmData.state)
}
})
})
// 初始化mutations
this.mutations = {}
// 遍歷mutations裏全部的函數
Object.keys(options.mutations).forEach(key=>{
// 拷貝賦值
this.mutations[key] = payload=>{
options.mutations[key](this.vmData.state,payload)
}
})
// commit實際上就是執行mutations裏指定的函數
this.commit = (type,param)=>{
this.mutations[type](param)
}
// 初始化actions
this.actions = {}
Object.keys(options.actions).forEach(key => {
this.actions[key] = payload => {
options.actions[key](this, payload)
}
})
this.dispatch = (type,param)=>{
this.actions[type](param)
}
}
get state(){
return this.vmData.state
}
}
複製代碼
測試一下
store.js
import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
text: "Hello Vuex"
},
getters: {
getText(state){
return state.text
}
},
mutations: {
syncSetText(state,param){
state.text = param
}
},
actions: {
asyncSetText({commit},param){
commit('syncSetText',param)
}
},
modules: {}
})
複製代碼
App.vue
<template>
<div id="app">
<h1>{{getState}}</h1>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.getters.getText
}
},
mounted(){
setTimeout(() => {
console.log('執行了')
this.$store.dispatch('asyncSetText','異步更改數據')
}, 1000)
}
}
</script>
複製代碼
目前已經實現了vuex中基本的幾個功能,可是上面的代碼稍微現得有些冗餘,咱們來優化一下,主要從如下兩點入手
1.將出現屢次的Object.keys().forEach()
封裝成公共的forEachValue
函數
function forEachValue (obj, fn) {
Object.keys(obj).forEach(key=>fn(obj[key], key));
}
複製代碼
2.把多個初始化從新賦值的部分封裝爲易讀的register
函數
優化後的代碼以下
// my-vuex.js
// 省略多餘代碼
class Store {
constructor(options={}){
this.options = options
this.vmData = {
state:Vue.observable(options.state || {})
}
// 初始化getters
this.getters = {}
forEachValue(options.getters,(getterFn,getterName)=>{
registerGetter(this,getterName,getterFn)
}
)
// 初始化mutations
this.mutations = {}
forEachValue(options.mutations,(mutationFn,mutationName)=>{
registerMutation(this,mutationName,mutationFn)
}
)
// 初始化actions
this.actions = {}
forEachValue(options.actions,(actionFn,actionName)=>{
registerAction(this,actionName,actionFn)
}
)
// commit實際上就是執行mutations裏指定的函數
this.commit = (type,param)=>{
this.mutations[type](param)
}
this.dispatch = (type,param)=>{
this.actions[type](param)
}
}
get state(){
return this.vmData.state
}
}
// 註冊getter
function registerGetter(store,getterName,getterFn){
Object.defineProperty(store.getters,getterName,{
get:()=>{
return getterFn.call(store,store.vmData.state)
}
})
}
// 註冊mutation
function registerMutation(store,mutationName,mutationFn){
store.mutations[mutationName] = payload=>{
mutationFn.call(store,store.vmData.state,payload)
}
}
// 註冊action
function registerAction(store,actionName,actionFn){
store.actions[actionName] = payload=>{
actionFn.call(store,store,payload)
}
}
// 封裝出公共的循環執行函數
function forEachValue (obj, fn) {
Object.keys(obj).forEach(key=>fn(obj[key], key));
}
export default {
install,
Store
}
複製代碼
當咱們項目日益複雜化的時候勢必會引入module
進行模塊化狀態管理,下面咱們來繼續實現module
的功能
首先咱們一塊兒來看一下咱們通常怎樣使用module
的
store.js代碼以下
import Vue from "vue"
// import Vuex from "./my-vuex.js"
import Vuex from "vuex"
Vue.use(Vuex)
let moduleA = {
state:{
nameA:'我是模塊A'
},
mutations:{
syncSetA(state,param){
state.nameA = param
}
},
actions:{
asyncSetState({commit},param){
setTimeout(()=>{
commit('syncSetA',param)
},1000)
}
},
getters:{
getA(state){
return state.nameA
}
}
}
let moduleB = {
state:{
nameB:'我是模塊B'
},
mutations:{
syncSetB(state,param){
state.nameB = param
}
},
actions:{
asyncSetState({commit},param){
setTimeout(()=>{
commit('syncSetB',param)
},1000)
}
},
getters:{
getB(state){
return state.nameB
}
}
}
export default new Vuex.Store({
modules:{
moduleA,moduleB
},
state: {
text: "Hello Vuex"
},
getters: {
getText(state){
return state.text
}
},
mutations: {
syncSetText(state,param){
state.text = param
}
},
actions: {
asyncSetText({commit},param){
commit('syncSetText',param)
}
}
})
複製代碼
App.vue代碼以下
<template>
<div id="app">
<h1>{{getState}}</h1>
A<h2>{{stateA}}</h2>
B<h2>{{stateB}}</h2>
</div>
</template>
<script>
export default{
computed:{
getState(){
return this.$store.getters.getText
},
stateA(){
return this.$store.state.moduleA.nameA
},
stateB(){
return this.$store.state.moduleB.nameB
}
},
mounted(){
setTimeout(() => {
this.$store.dispatch('asyncSetState','異步更改數據')
}, 1000)
}
}
</script>
複製代碼
在不啓用nameSpace的狀況下,咱們發現咱們獲取模塊內的state
使用this.$store.state.moduleB.nameA
的方式獲取。而觸發模塊內的mutations
或者action
則是與之前同樣,只不過如果兩個不一樣的模塊有重名的mutation
或者action
,則須要所有都執行。下面運用兩個步驟進行模塊化實現
modules
傳來的數據若是咱們的store.js
是這樣的
export default new Vuex.Store({
modules:{
moduleA,moduleB
},
state: {},
getters: {},
mutations: {},
actions: {}
})
複製代碼
咱們能夠格式化成下面這種格式,造成一個模塊狀態樹
const newModule = {
// 根模塊store
_rootModule:store,
// 子模塊
_children:{
moduleA:{
_rootModule:moduleA,
_children:{},
state:moduleA.state
},
moduleB:{
_rootModule:moduleB,
_children:{},
state:moduleB.state
}
},
// 根模塊狀態
state:store.state
}
複製代碼
爲此咱們須要新增一個moduleCollection
類來收集store.js
中的數據,而後格式化成狀態樹
my-vuex.js
代碼以下
// my-vuex.js
let Vue
const install = _Vue => {
// 省略部分代碼
}
class Store {
constructor(options={}){
// 省略部分代碼
// 格式化數據,生成狀態樹
this._modules = new ModuleCollection(options)
}
}
class moduleCollection{
constructor(rootModule){
this.register([],rootModule)
}
register(path,rootModule){
const newModule = {
_rootModule:rootModule, // 根模塊
_children:{}, // 子模塊
state:rootModule.state // 根模塊狀態
}
// path長度爲0,說明是根元素進行初始化數據
if(path.length === 0){
this.root = newModule
}else{
//利用reduce能夠快速的將扁平化數據轉換成樹狀數據
const parent = path.slice(0,-1).reduce((module,key)=>{
return module._children(key)
},this.root)
parent._children[path[path.length - 1]] = newModule
}
// 若是含有modules,則須要循環註冊內部模塊
if(rootModule.modules){
forEachValue(rootModule.modules,(rootChildModule,key)=>{
this.register(path.concat(key),rootChildModule)
})
}
}}
複製代碼
store.js
中的數據已經被咱們遞歸組裝成了狀態樹,接下來須要將狀態樹安裝進Store
類中 這裏主要作了兩個改動
新增installModule
函數,installModule
主要幫助咱們將格式化好的狀態樹註冊到Store
類中
從新改造了註冊函數(registerMutation、registerGetter
等)以及觸發函數(commit、dispatch
)。
my-vuex.js
代碼以下
// my-vuex.js
// 省略部分代碼
class Store {
constructor(options={}){
this.options = options
// 初始化getters
this.getters = {}
// 初始化mutations
this.mutations = {}
// 初始化actions
this.actions = {}
// 初始化數據,生成狀態樹
this._modules = new moduleCollection(options)
this.commit = (type,param)=>{
this.mutations[type].forEach(fn=>fn(param))
}
this.dispatch = (type,param)=>{
this.actions[type].forEach(fn=>fn(param))
}
const state = options.state;
const path = []; // 初始路徑給根路徑爲空
installModule(this, state, path, this._modules.root);
this.vmData = {
state:Vue.observable(options.state || {})
}
}
get state(){
return this.vmData.state
}
}
class moduleCollection{
// 省略部分代碼
}
// 遞歸狀態樹,掛載getters,actions,mutations
function installModule(store, rootState, path, rootModule) {
// 這兒將模塊中的state循環出來設置到根state中去,以便咱們經過this.$store.state.moduleA來訪問數據
if (path.length > 0) {
const parent = path.slice(0,-1).reduce((state,key)=>{
return state[key]
},rootState)
Vue.set(parent, path[path.length - 1], rootModule.state)
}
// 循環註冊包含模塊內的全部getters
let getters = rootModule._rootModule.getters
if (getters) {
forEachValue(getters, (getterFn, getterName) => {
registerGetter(store, getterName, getterFn, rootModule);
});
}
// 循環註冊包含模塊內的全部mutations
let mutations = rootModule._rootModule.mutations
if (mutations) {
forEachValue(mutations, (mutationFn, mutationName) => {
registerMutation(store, mutationName, mutationFn, rootModule)
});
}
// 循環註冊包含模塊內的全部actions
let actions = rootModule._rootModule.actions
if (actions) {
forEachValue(actions, (actionFn, actionName) => {
registerAction(store, actionName, actionFn, rootModule);
});
}
// 若是模塊嵌套模塊,則須要遞歸安裝
forEachValue(rootModule._children, (child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
// 這兒的getters中的state是各自模塊中的state
function registerGetter(store,getterName,getterFn,currentModule){
Object.defineProperty(store.getters,getterName,{
get:()=>{
return getterFn.call(store,currentModule.state)
}
})
}
// 因爲各個模塊mutation存在重複狀況,所以這裏使用發佈-訂閱模式進行註冊
function registerMutation(store,mutationName,mutationFn,currentModule){
let mutationArr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
mutationArr.push((payload)=>{
mutationFn.call(store,currentModule.state,payload)
})
}
function registerAction(store,actionName,actionFn){
let actionArr = store.actions[actionName] || (store.actions[actionName] = []);
actionArr.push((payload)=>{
actionFn.call(store,store,payload)
})
}
// 省略其他代碼
複製代碼
至此,咱們已經實現了vuex
的基本功能,固然其餘相似於nameSpace、plugins,store.subscribe
的功能這裏並無展開,小夥伴們能夠自行擴展。這裏建議小夥伴們先要理清楚思路。從vuex
是什麼,要實現那些功能?怎樣能夠更好的實現?若是思路通了,相信你們能夠寫出更好的vuex
mapState,mapGetters,mapMutations,mapActions
的實現輔助函數的實現原理較爲簡單,你們自行嘗試
const mapState = stateList => {
return stateList.reduce((prev,stateName)=>{
prev[stateName] =function(){
return this.$store.state[stateName]
}
return prev
},{})
}
const mapGetters = gettersList => {
return gettersList.reduce((prev,gettersName)=>{
prev[gettersName] =function(){
return this.$store.getters[gettersName]
}
return prev
},{})
}
const mapMutations = mutationsList => {
return mutationsList.reduce((prev,mutationsName)=>{
prev[mutationsName] =function(payload){
return this.$store.commit(mutationsName,payload)
}
return prev
},{})
}
const mapActions = actionsList => {
return actionsList.reduce((prev,actionsName)=>{
prev[actionsName] =function(payload){
return this.$store.dispatch(actionsName,payload)
}
return prev
},{})
}
複製代碼
// my-vuex.js
let Vue
const install = _Vue => {
// vue.use()執行的時候,會將vue實例做爲參數傳入進來,這裏咱們用一個變量接收
Vue = _Vue
// Vue.mixin幫助咱們全局混入$store
Vue.mixin({
beforeCreate(){
// 這裏的this指的是vue實例
const options = this.$options
if(options.store){
// 判斷當前組件內部是否認義了store,若是有則優先使用內部的store
this.$store = typeof options.store === 'function' ? options.store() : options.store
} else if(options.parent && options.parent.$store){
// 組件內部沒有定義store,則從父組件下繼承$store方法
this.$store = options.parent.$store
}
}
})
}
class Store {
constructor(options={}){
this.options = options
// 初始化getters
this.getters = {}
// 初始化mutations
this.mutations = {}
// 初始化actions
this.actions = {}
// 初始化數據,生成狀態樹
this._modules = new moduleCollection(options)
// commit實際上就是執行mutations裏指定的函數
this.commit = (type,param)=>{
this.mutations[type].forEach(fn=>fn(param))
}
this.dispatch = (type,param)=>{
this.actions[type].forEach(fn=>fn(param))
}
const state = options.state;
const path = []; // 初始路徑給根路徑爲空
installModule(this, state, path, this._modules.root);
this.vmData = {
state:Vue.observable(options.state || {})
}
}
get state(){
return this.vmData.state
}
}
// 格式化狀態樹
class moduleCollection{
constructor(rootModule){
this.register([],rootModule)
}
register(path,rootModule){
const newModule = {
_rootModule:rootModule, // 根模塊
_children:{}, // 子模塊
state:rootModule.state // 根模塊狀態
}
// path長度爲0,說明是根元素進行初始化數據
if(path.length === 0){
this.root = newModule
}else{
//利用reduce能夠快速的將扁平化數據轉換成樹狀數據
const parent = path.slice(0,-1).reduce((module,key)=>{
return module._children[key]
},this.root)
parent._children[path[path.length - 1]] = newModule
}
// 若是含有modules,則須要循環註冊內部模塊
if(rootModule.modules){
forEachValue(rootModule.modules,(rootChildModule,key)=>{
this.register(path.concat(key),rootChildModule)
})
}
}}
// 遞歸狀態樹,掛載getters,actions,mutations
function installModule(store, rootState, path, rootModule) {
// 這兒將模塊中的state循環出來設置到根state中去,以便咱們經過this.$store.state.moduleA來訪問數據
if (path.length > 0) {
const parent = path.slice(0,-1).reduce((state,key)=>{
return state[key]
},rootState)
Vue.set(parent, path[path.length - 1], rootModule.state)
}
// 循環註冊包含模塊內的全部getters
let getters = rootModule._rootModule.getters
if (getters) {
forEachValue(getters, (getterFn, getterName) => {
registerGetter(store, getterName, getterFn, rootModule);
});
}
// 循環註冊包含模塊內的全部mutations
let mutations = rootModule._rootModule.mutations
if (mutations) {
forEachValue(mutations, (mutationFn, mutationName) => {
registerMutation(store, mutationName, mutationFn, rootModule)
});
}
// 循環註冊包含模塊內的全部actions
let actions = rootModule._rootModule.actions
if (actions) {
forEachValue(actions, (actionFn, actionName) => {
registerAction(store, actionName, actionFn, rootModule);
});
}
// 若是模塊嵌套模塊,則須要遞歸安裝
forEachValue(rootModule._children, (child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
// 這兒的getters中的state是各自模塊中的state
function registerGetter(store,getterName,getterFn,currentModule){
Object.defineProperty(store.getters,getterName,{
get:()=>{
return getterFn.call(store,currentModule.state)
}
})
}
// 因爲各個模塊mutation存在重複狀況,所以這裏使用發佈-訂閱模式進行註冊
function registerMutation(store,mutationName,mutationFn,currentModule){
let mutationArr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
mutationArr.push((payload)=>{
mutationFn.call(store,currentModule.state,payload)
})
}
function registerAction(store,actionName,actionFn){
let actionArr = store.actions[actionName] || (store.actions[actionName] = []);
actionArr.push((payload)=>{
actionFn.call(store,store,payload)
})
}
function forEachValue (obj, fn) {
Object.keys(obj).forEach(key=>fn(obj[key], key));
}
// 輔助函數
export const mapState = stateList => {
return stateList.reduce((prev,stateName)=>{
prev[stateName] =function(){
return this.$store.state[stateName]
}
return prev
},{})
}
export const mapGetters = gettersList => {
return gettersList.reduce((prev,gettersName)=>{
prev[gettersName] =function(){
return this.$store.getters[gettersName]
}
return prev
},{})
}
export const mapMutations = mutationsList => {
return mutationsList.reduce((prev,mutationsName)=>{
prev[mutationsName] =function(payload){
return this.$store.commit(mutationsName,payload)
}
return prev
},{})
}
export const mapActions = actionsList => {
return actionsList.reduce((prev,actionsName)=>{
prev[actionsName] =function(payload){
return this.$store.dispatch(actionsName,payload)
}
return prev
},{})
}
export default {
install,
Store,
}
複製代碼