既然 VUEX 可以 use,內部必定是有一個 install 方法,所以我們先要實現一個install方法,當我們用的時候,每一個組件上面都有一個this.$store屬性,裏面包含了狀態倉庫裏面的state,mutations, actions, getters,所以我們也需要在每個組件上都掛載一個$store屬性,具體實現如下:
let Vue = null;
export function install(_Vue) {
// 爲了防止重複註冊
if (Vue !== _Vue) {
Vue = _Vue
}
Vue.mixin({
beforeCreate() {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
})
}
爲了後面的緩緩,我們需要封裝一個自己的循環方法,用來簡化代碼操作
const forEach = (obj, cb) => {
Object.keys(obj).forEach(key => {
cb(key, obj[key]);
})
}
到此我們就可以正常的引入並use(vuex)啦,但是此時我們並沒有去初始化倉庫,因爲原聲的vuex還需要去 new Vuex.Store(),可以傳入一些初始化參數,比如state、mutations、actions、getters,既然能 new ,說明這是一個 類,所以我們現在去寫 Store 這個類,具體實現如下:
export class Store {
constructor(options = {}) {
// TODO....
}
}
好了,這個時候頁面就不會報錯啦,而且也可以通過 this.$store.state.xxx 取到state中的值了,但是原生的state重新設置會引發視圖更新,所以還需要把store中的state設置成響應式的,具體實現如下:
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
},
// 類的屬性訪問器
get state() {
return this.vm.state
}
}
到此我們在頁面中設置值就可以引發視圖更新啦,因爲我們已經把所有的數據變成了雙向綁定,只要視圖更改就會引發視圖更新這個時候,我們在初始化store的時候還傳入了mutations、actions、getters,這些數據都還沒有處理,所以現在我們就可以優先處理這些數據,而且在$store屬性上還有 commit,dispatch,各種屬性,所以我們還需要把這些屬性頁全部寫好,具體實現如下:
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
this.getters = {};
this.mutations = {};
this.actions = {};
// 處理 getters 響應式數據
let getters = options.getters;
forEach(getters,(getterName, fn)=>{
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
// 獲取所有的同步的更新操作方法
let mutations = options.mutations;
forEach(mutations,(mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
fn(this.state, payload)
}
});
// 獲取所有的異步的更新操作方法
let actions = options.actions;
forEach(actions,(actionName, fn) => {
this.actions[actionName] = (payload) => {
fn(this, payload);
}
})
}
commit = (type, payload) => {
this.mutations[type](payload)
}
dispatch = (type, payload) => {
this.actions[type](payload)
}
get state() {
return this.vm.state
}
}
到此,基本的vuex 已經可以正常運行啦!是不是很簡單!!!!!
但是,到此還遠遠不止!!!!!!
我們知道,原生的 vuex 中還有modules 屬性,裏面可以嵌套任意層state,mutations,actions,getters,所以我們還需要處理這個屬性
看原生的vuex屬性中有個_modules 屬性,是一個樹結構
所以我們也仿照原生,生成一個__modules樹結構的對象
// 格式化 _modules
class ModuleCollection {
constructor(options) {
// 註冊模塊 將模塊註冊成樹結構
this.register([], options);
}
register(path, rootModule) {
let module = { // 將模塊格式化
_rawModule: rootModule,
_chidlren: {},
state: rootModule.state
}
if (path.length == 0) {
// 如果是根模塊 將這個模塊掛在到根實例上
this.root = module;
} else {
// 遞歸都用reduce方法
// 通過 _children 屬性進行查找
let parent = path.slice(0, -1).reduce((root, current) => {
return root._chidlren[current]
}, this.root)
parent._chidlren[path[path.length - 1]] = module
}
// 看當前模塊是否有modules , 如果有modules 開始重新再次註冊
if (rootModule.modules) {
forEach(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module)
})
}
}
}
然後在 Store 類中把 _modules 屬性掛載到實例上
Store類中:
// 把數據格式化成一個 想要的樹結構
this._modules = new ModuleCollection(options);
到此,我們把modules都已經處理成了想要的結構,但是各個模塊下的屬性都沒有掛載處理,所以我們還需要遞歸掛載各個模塊的屬性,所以上面寫的處理mutations、atcions、getters屬性的方法都需要重新去寫,具體實現如下:
/**
* @explain { 安裝模塊 }
* @param { store } 整個store
* @param { rootState } 當前的根狀態
* @param { path } 爲了遞歸來創建的
* @param { rootModule } 從根模塊開始安裝
*/
const installModule = (store, rootState, path, rootModule) => {
if (path.length > 0) {
// [a]
// 是兒子,兒子要找到爸爸將自己的狀態 放到上面去
let parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// vue 不能在對象上增加不存在的屬性 否則不會導致視圖更新
Vue.set(parent, path[path.length - 1], rootModule.state);
// {age:1,a:{a:1}}
// 實現了 查找掛在數據格式
}
// 以下代碼都是在處理 模塊中 getters actions mutation
let getters = rootModule._rawModule.getters;
if (getters) {
forEach(getters, (getterName, fn) => {
Object.defineProperty(store.getters, getterName, {
get() {
return fn(rootModule.state); // 讓對應的函數執行
}
});
})
}
let mutations = rootModule._rawModule.mutations;
if (mutations) {
forEach(mutations, (mutationName, fn) => {
let mutations = store.mutations[mutationName] || [];
mutations.push((payload) => {
fn(rootModule.state, payload);
// 發佈 讓所有的訂閱依次執行
store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
})
store.mutations[mutationName] = mutations;
})
}
let actions = rootModule._rawModule.actions;
if (actions) {
forEach(actions, (actionName, fn) => {
let actions = store.actions[actionName] || [];
actions.push((payload) => {
fn(store, payload);
})
store.actions[actionName] = actions;
})
}
// 掛載兒子
forEach(rootModule._chidlren, (moduleName, module) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
然後在 Store 類中執行此處理函數,完勝掛載
/**
* @explain { 安裝模塊 }
* @param { this } 整個store
* @param { this.state } 當前的根狀態
* @param { [] } 爲了遞歸來創建的
* @param { this._modules.root } 從根模塊開始安裝
*/
installModule(this, this.state, [], this._modules.root);
此時,我們自己寫的 vuex 就可以完全正常運行啦,可以隨意書寫modules,state,mutations,actions,getters,是不是很酷!!!!!!哈哈哈哈但是我們還有一個 plugins 屬性還沒實現,具體這塊的實現非常簡單,因爲我們在使用 plugins 的時候,傳遞一個數組,數組裏面是一個個的 中間件函數,每一個函數的第一個參數都是 倉庫實例本身,所以,我們在代碼裏就可以這樣寫
// 處理 插件
(options.plugins || []).forEach(plugin => plugin(this));
到此,我們的 vuex 就實現啦,是不是很簡單,哈哈哈哈哈哈哈
下面是所有的代碼:
const forEach = (obj, cb) => {
Object.keys(obj).forEach(key => {
cb(key, obj[key]);
})
}
let Vue = null;
export function install(_Vue) {
if (Vue !== _Vue) {
Vue = _Vue
}
Vue.mixin({
beforeCreate() {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
})
}
/**
* @explain { 安裝模塊 }
* @param { store } 整個store
* @param { rootState } 當前的根狀態
* @param { path } 爲了遞歸來創建的
* @param { rootModule } 從根模塊開始安裝
*/
const installModule = (store, rootState, path, rootModule) => {
if (path.length > 0) {
// [a]
// 是兒子,兒子要找到爸爸將自己的狀態 放到上面去
let parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// vue 不能在對象上增加不存在的屬性 否則不會導致視圖更新
Vue.set(parent, path[path.length - 1], rootModule.state);
// {age:1,a:{a:1}}
// 實現了 查找掛在數據格式
}
// 以下代碼都是在處理 模塊中 getters actions mutation
let getters = rootModule._rawModule.getters;
if (getters) {
forEach(getters, (getterName, fn) => {
Object.defineProperty(store.getters, getterName, {
get() {
return fn(rootModule.state); // 讓對應的函數執行
}
});
})
}
let mutations = rootModule._rawModule.mutations;
if (mutations) {
forEach(mutations, (mutationName, fn) => {
let mutations = store.mutations[mutationName] || [];
mutations.push((payload) => {
fn(rootModule.state, payload);
// 發佈 讓所有的訂閱依次執行
store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
})
store.mutations[mutationName] = mutations;
})
}
let actions = rootModule._rawModule.actions;
if (actions) {
forEach(actions, (actionName, fn) => {
let actions = store.actions[actionName] || [];
actions.push((payload) => {
fn(store, payload);
})
store.actions[actionName] = actions;
})
}
// 掛載兒子
forEach(rootModule._chidlren, (moduleName, module) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
class ModuleCollection { // 格式化
constructor(options) {
// 註冊模塊 將模塊註冊成樹結構
this.register([], options);
}
register(path, rootModule) {
let module = { // 將模塊格式化
_rawModule: rootModule,
_chidlren: {},
state: rootModule.state
}
if (path.length == 0) {
// 如果是根模塊 將這個模塊掛在到根實例上
this.root = module;
} else {
// 遞歸都用reduce方法
// 通過 _children 屬性進行查找
let parent = path.slice(0, -1).reduce((root, current) => {
return root._chidlren[current]
}, this.root)
parent._chidlren[path[path.length - 1]] = module
}
// 看當前模塊是否有modules , 如果有modules 開始重新再次註冊
if (rootModule.modules) {
forEach(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module)
})
}
}
}
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
this.getters = {};
this.mutations = {};
this.actions = {};
this._subscribes = [];
// 把數據格式化成一個 想要的樹結構
this._modules = new ModuleCollection(options);
/**
* @explain { 安裝模塊 }
* @param { this } 整個store
* @param { this.state } 當前的根狀態
* @param { [] } 爲了遞歸來創建的
* @param { this._modules.root } 從根模塊開始安裝
*/
installModule(this, this.state, [], this._modules.root);
// 處理 插件
(options.plugins || []).forEach(plugin => plugin(this));
}
subscribe(fn){
this._subscribes.push(fn);
}
commit = (type, payload) => {
this.mutations[type].forEach(cb => cb(payload))
}
dispatch = (type, payload) => {
this.actions[type].forEach(cb => cb(payload))
}
get state() {
return this.vm.state
}
}
export default {
install,
Store,
}
完啦,謝謝大家觀看學習!哈哈哈哈