摘要:是如何被使用到各個(gè)組件上的為什么的數(shù)據(jù)是雙向綁定的在組件中為什么用可以觸發(fā)的在組件中為什么用可以觸發(fā)的等等等等帶著一堆問(wèn)題,我們來(lái)自己實(shí)現(xiàn)一個(gè),來(lái)理解的工作原理。為了解決以上問(wèn)題,允許我們將分割成模塊。
大家都知道vuex是vue的一個(gè)狀態(tài)管理器,它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。先看看vuex下面的工作流程圖
通過(guò)官方文檔提供的流程圖我們知道,vuex的工作流程,
1、數(shù)據(jù)從state中渲染到頁(yè)面;
2、在頁(yè)面通過(guò)dispatch來(lái)觸發(fā)action;
3、action通過(guò)調(diào)用commit,來(lái)觸發(fā)mutation;
4、mutation來(lái)更改數(shù)據(jù),數(shù)據(jù)變更之后會(huì)觸發(fā)dep對(duì)象的notify,通知所有Watcher對(duì)象去修改對(duì)應(yīng)視圖(vue的雙向數(shù)據(jù)綁定原理)。
使用vuex理解vuex的工作流程我們就看看vuex在vue中是怎么使用的。
首先用vue-cli創(chuàng)建一個(gè)項(xiàng)目工程,如下圖,選擇vuex,然后就是一路的回車(chē)鍵
安裝好之后,就有一個(gè)帶有vuex的vue項(xiàng)目了。
進(jìn)入目錄然后看到,src/store.js,在里面加了一個(gè)狀態(tài){count: 100},如下
import Vue from "vue" import Vuex from "vuex" // 引入vuex Vue.use(Vuex) // 使用插件 export default new Vuex.Store({ state: { count: 100 // 加一個(gè)狀態(tài) }, getter: { }, mutations: { }, actions: { } })
最后在A(yíng)pp.vue文件里面使用上這個(gè)狀態(tài),如下
這里是stort------->{{this.$store.state.count}}
項(xiàng)目跑起來(lái)就會(huì)看到頁(yè)面上看到,頁(yè)面上會(huì)有100了,如下圖
到這里我們使用vuex創(chuàng)建了一個(gè)store,并且在我們的App組件視圖中使用,但是我們會(huì)有一些列的疑問(wèn)。
store是如何被使用到各個(gè)組件上的??
為什么state的數(shù)據(jù)是雙向綁定的??
在組件中為什么用this.$store.dispch可以觸發(fā)store的actions??
在組件中為什么用this.$store.commit可以觸發(fā)store的mutations??
....等等等等
帶著一堆問(wèn)題,我們來(lái)自己實(shí)現(xiàn)一個(gè)vuex,來(lái)理解vuex的工作原理。
安裝并使用store在src下新建一個(gè)vuex.js文件,然后代碼如下
"use strict" let Vue = null class Store { constructor (options) { let { state, getters, actions, mutations } = options } } // Vue.use(Vuex) const install = _Vue => { // 避免vuex重復(fù)安裝 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 通過(guò)mixins讓每個(gè)組件實(shí)例化的時(shí)候都會(huì)執(zhí)行下面的beforeCreate beforeCreate () { // 只有跟節(jié)點(diǎn)才有store配置,所以這里只走一次 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子組件深度優(yōu)先 父 --> 子---> 孫子 this.$store = this.$parent.$store } } }) } export default { install, Store }
然后修改store.js中的引入vuex模塊改成自己的vuex.js
import Vuex from "./vuex" // 自己創(chuàng)建的vuex文件
在我們的代碼中export default { install, Store }導(dǎo)出了一個(gè)對(duì)象,分別是install和Store
install的作用是,當(dāng)Vue.use(Vuex)就會(huì)自動(dòng)調(diào)用install方法,在install方法里面,我們用mixin混入了一個(gè)beforeCreate的生命周期的鉤子函數(shù),使得當(dāng)每個(gè)組件實(shí)例化的時(shí)候都會(huì)調(diào)用這個(gè)函數(shù)。
在beforeCreate中,第一次根組件通過(guò)store屬性?huà)燧d$store,后面子組件調(diào)用beforeCreate掛載的$store都會(huì)向上找到父級(jí)的$store,這樣子通過(guò)層層向上尋找,讓每個(gè)組件都掛上了一個(gè)$store屬性,而這個(gè)屬性的值就是我們的new Store({...})的實(shí)例。如下圖
通過(guò)層層向上尋找,讓每個(gè)組件都掛上了一個(gè)$store屬性設(shè)置state響應(yīng)數(shù)據(jù)
通過(guò)上面,我們已經(jīng)從每個(gè)組件都通過(guò)this.$store來(lái)訪(fǎng)問(wèn)到我們的store的實(shí)例,下面我們就編寫(xiě)state數(shù)據(jù),讓其變成雙向綁定的數(shù)據(jù)。下面我們改寫(xiě)store類(lèi)
class Store { constructor (options) { let { state, getters, actions, mutations } = options // 拿到傳進(jìn)來(lái)的參數(shù) this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實(shí)例,因?yàn)関uex的數(shù)據(jù)更改回更新視圖 this._vm = new Vue({ data: { state } }) } // 訪(fǎng)問(wèn)state對(duì)象時(shí)候,就直接返回響應(yīng)式的數(shù)據(jù) get state() { // Object.defineProperty get 同理 return this._vm.state } }
傳進(jìn)來(lái)的state對(duì)象,通過(guò)new Vue({data: {state}})的方式,讓數(shù)據(jù)變成響應(yīng)式的。當(dāng)訪(fǎng)問(wèn)state對(duì)象時(shí)候,就直接返回響應(yīng)式的數(shù)據(jù),這樣子在App.vue中就可以通過(guò)this.$store.state.count拿到state的數(shù)據(jù)啦,并且是響應(yīng)式的呢。
編寫(xiě)mutations、actions、getters上面我們已經(jīng)設(shè)置好state為響應(yīng)式的數(shù)據(jù),這里我們?cè)?b>store.js里面寫(xiě)上mutations、actions、getters,如下
import Vue from "vue" import Vuex from "./vuex" // 引入我們的自己編寫(xiě)的文件 Vue.use(Vuex) // 安裝store // 實(shí)例化store,參數(shù)數(shù)對(duì)象 export default new Vuex.Store({ state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模擬異步 setTimeout(() => { commit("change") }, 1000) } } })
配置選項(xiàng)都寫(xiě)好之后,就看到getters對(duì)象里面有個(gè)newCount函數(shù),mutations和actions對(duì)象里面都有個(gè)change函數(shù),配置好store之后我們?cè)?b>App.vue就可以寫(xiě)上,dispatch和commit,分別可以觸發(fā)actions和mutations,代碼如下
這里是store的state------->{{this.$store.state.count}}
這里是store的getter------->{{this.$store.getters.newCount}}
數(shù)據(jù)都配置好之后,我們開(kāi)始編寫(xiě)store類(lèi),在此之前我們先編寫(xiě)一個(gè)循環(huán)對(duì)象工具函數(shù)。
const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key])) // 作用: // 例如{a: "123"}, 把對(duì)象的key和value作為參數(shù) // 然后就是函數(shù)運(yùn)行callback(a, "123")
工具函數(shù)都準(zhǔn)備好了,之后,下面直接縣編寫(xiě)getters、mutations和actions的實(shí)現(xiàn)
class Store { constructor (options) { let { state = {}, getters = {}, actions = {}, mutations = {} } = options this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實(shí)例,因?yàn)関uex的數(shù)據(jù)更改回更新視圖 this._vm = new Vue({ data: { state } }) // 循環(huán)getters的對(duì)象 myforEach(getters, (getterName, getterFn) => { // 對(duì)this.getters對(duì)象進(jìn)行包裝,和vue的computed是差不多的 // 例如 this.getters["newCount"] = fn(state) // 執(zhí)行 this.getters["newCount"]()就會(huì)返回計(jì)算的數(shù)據(jù)啦 Object.defineProperty(this.getters, getterName, { get: () => getterFn(state) }) }) // 這里是mutations各個(gè)key和值都寫(xiě)到,this.mutations對(duì)象上面 // 執(zhí)行的時(shí)候就是例如:this.mutations["change"]() myforEach(mutations, (mutationName, mutationsFn) => { // this.mutations.change = () => { change(state) } this.mutations[mutationName] = () => { mutationsFn.call(this, state) } }) // 原理同上 myforEach(actions, (actionName, actionFn) => { // this.mutations.change = () => { change(state) } this.actions[actionName] = () => { actionFn.call(this, this) } }) const {commit , dispatch} = this // 先存一份,避免this.commit會(huì)覆蓋原型上的this.commit // 解構(gòu) 把this綁定好 // 通過(guò)結(jié)構(gòu)的方式也要先調(diào)用這類(lèi),然后在下面在調(diào)用原型的對(duì)應(yīng)函數(shù) this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } // commi調(diào)用 commit (type) { this.mutations[type]() } // dispatch調(diào)用 dispatch (type) { this.actions[type]() } }
通過(guò)上面的,我們可以看出,其實(shí)mutations和actions都是把傳入的參數(shù),賦值到store實(shí)例上的this.mutations和this.actions對(duì)象里面。
當(dāng)組件中this.$store.commit("change")的時(shí)候 其實(shí)是調(diào)用this.mutations.change(state),就達(dá)到了改變數(shù)據(jù)的效果,actions同理。
getters是通過(guò)對(duì)Object.defineProperty(this.getters, getterName, {})
對(duì)this.getters進(jìn)行包裝當(dāng)組件中this.$store.getters.newCount其實(shí)是調(diào)用getters對(duì)象里面的newCount(state),然后返回計(jì)算結(jié)果。就可以顯示到界面上了。
大家看看完成后的效果圖。
到這里大家應(yīng)該懂了vuex的內(nèi)部代碼的工作流程了,vuex的一半核心應(yīng)該在這里了。為什么說(shuō)一半,因?yàn)檫€有一個(gè)核心概念module,也就是vuex的數(shù)據(jù)的模塊化。
vuex數(shù)據(jù)模塊化由于使用單一狀態(tài)樹(shù),應(yīng)用的所有狀態(tài)會(huì)集中到一個(gè)比較大的對(duì)象。當(dāng)應(yīng)用變得非常復(fù)雜時(shí),store 對(duì)象就有可能變得相當(dāng)臃腫。
為了解決以上問(wèn)題,Vuex 允許我們將 store 分割成模塊(module)。每個(gè)模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進(jìn)行同樣方式的分割
例如下面的store.js
// 實(shí)例化store,參數(shù)數(shù)對(duì)象 export default new Vuex.Store({ modules: { // 模塊a a: { state: { count: 4000 }, actions: { change ({state}) { state.count += 21 } }, modules: { // 模塊b b: { state: { count: 5000 } } } } }, state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模擬異步 setTimeout(() => { commit("change") }, 1000) } } })
然后就可以在界面上就可以寫(xiě)上this.$store.state.a.count(顯示a模塊count),this.$store.state.a.b.count(顯示a模塊下,b模塊的count),這里還有一個(gè)要注意的,其實(shí)在組件中調(diào)用this.$store.dispatch("change")會(huì)同時(shí)觸發(fā),根的actions和a模塊的actions里面的change函數(shù)。
下面我們就直接去實(shí)現(xiàn)models的代碼,也就是整個(gè)vuex的實(shí)現(xiàn)代碼,
"use strict" let Vue = null const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key])) class Store { constructor (options) { let state = options.state this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實(shí)例,因?yàn)関uex的數(shù)據(jù)更改回更新視圖 this._vm = new Vue({ data: { state } }) // 把模塊之間的關(guān)系進(jìn)行整理, 自己根據(jù)用戶(hù)參數(shù)維護(hù)了一個(gè)對(duì)象 // root._children => a._children => b this.modules = new ModulesCollections(options) // 無(wú)論子模塊還是 孫子模塊 ,所有的mutations 都是根上的 // 安裝模塊 installModules(this, state, [], this.modules.root) // 解構(gòu) 把this綁定好 const {commit , dispatch} = this // 通過(guò)結(jié)構(gòu)的方式也要先調(diào)用這類(lèi),然后在下面在調(diào)用原型的對(duì)應(yīng)函數(shù) this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } commit (type) { // 因?yàn)槭菙?shù)組,所以要遍歷執(zhí)行 this.mutations[type].forEach(fn => fn()) } dispatch (type) { // 因?yàn)槭菙?shù)組,所以要遍歷執(zhí)行 this.actions[type].forEach(fn => fn()) } } class ModulesCollections { constructor (options) { // vuex [] // 注冊(cè)模塊 this.register([], options) } register (path, rawModule) { // path 是空數(shù)組, rawModule 就是個(gè)對(duì)象 let newModule = { _raw: rawModule, // 對(duì)象 _children: {}, // 把子模塊掛載到這里 state: rawModule.state } if (path.length === 0) { // 第一次 this.root = newModule } else { // [a, b] ==> [a] let parent = path.slice(0, -1).reduce((root, current) => { return root._children[current] }, this.root) parent._children[path[path.length - 1]] = newModule } if (rawModule.modules) { // 遍歷注冊(cè)子模塊 myforEach(rawModule.modules, (childName, module) => { this.register(path.concat(childName), module) }) } } } // rootModule {_raw, _children, state } function installModules (store, rootState, path, rootModule) { // rootState.a = {count:200} // rootState.a.b = {count: 3000} if (path.length > 0) { // 根據(jù)path找到對(duì)應(yīng)的父級(jí)模塊 // 例如 [a] --> path.slice(0, -1) --> [] 此時(shí)a模塊的父級(jí)模塊是跟模塊 // 例如 [a,b] --> path.slice(0, -1) --> [a] 此時(shí)b模塊的父級(jí)模塊是a模塊 let parent = path.slice(0, -1).reduce((root, current) => { return root[current] }, rootState) // 通過(guò)Vue.set設(shè)置數(shù)據(jù)雙向綁定 Vue.set(parent, path[path.length - 1], rootModule.state) } // 設(shè)置getter if (rootModule._raw.getters) { myforEach(rootModule._raw.getters, (getterName, getterFn) => { Object.defineProperty(store.getters, getterName, { get: () => { return getterFn(rootModule.state) } }) }) } // 在跟模塊設(shè)置actions if (rootModule._raw.actions) { myforEach(rootModule._raw.actions, (actionName, actionsFn) => { // 因?yàn)橥窃诟K設(shè)置,子模塊也有能相同的key // 所有把所有的都放到一個(gè)數(shù)組里面 // 就變成了例如 [change, change] , 第一個(gè)是跟模塊的actions的change,第二個(gè)是a模塊的actions的change let entry = store.actions[actionName] || (store.actions[actionName] = []) entry.push(() => { const commit = store.commit const state = rootModule.state actionsFn.call(store, {state, commit}) }) }) } // 在跟模塊設(shè)置mutations, 同理上actions if (rootModule._raw.mutations) { myforEach(rootModule._raw.mutations, (mutationName, mutationFn) => { let entry = store.mutations[mutationName] || (store.mutations[mutationName] = []) entry.push(() => { mutationFn.call(store, rootModule.state) }) }) } // 遞歸遍歷子節(jié)點(diǎn)的設(shè)置 myforEach(rootModule._children, (childName, module) => { installModules(store, rootState, path.concat(childName), module) }) } const install = _Vue => { // 避免vuex重復(fù)安裝 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 通過(guò)mixins讓每個(gè)組件實(shí)例化的時(shí)候都會(huì)執(zhí)行下面的beforeCreate beforeCreate () { // 只有跟節(jié)點(diǎn)才有store配置 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子組件深度優(yōu)先 父 --> 子---> 孫子 this.$store = this.$parent.$store } } }) } export default { install, Store }
看到代碼以及注釋?zhuān)饕鞒叹褪歉鶕?jù)遞歸的方式,處理數(shù)據(jù),然后根據(jù)傳進(jìn)來(lái)的配置,進(jìn)行操作數(shù)據(jù)。
至此,我們把vuex的代碼實(shí)現(xiàn)了一遍,在我們App.vue的代碼里添加
這里是store的state------->{{this.$store.state.count}}
這里是store的getter------->{{this.$store.getters.newCount}}
這里是store的state.a------->{{this.$store.state.a.count}}
最后查看結(jié)果。
完結(jié)撒花~~~
博客文章地址:https://blog.naice.me/article...
源碼地址:https://github.com/naihe138/w...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/102252.html
摘要:但高度封裝的帶來(lái)方便的同時(shí),很多人卻很少去關(guān)注輪子的內(nèi)部結(jié)構(gòu),以至于當(dāng)使用需要手動(dòng)配置一些東西如編譯實(shí)現(xiàn)代碼壓縮,移動(dòng)端適配等配置的時(shí)候往往無(wú)從下手。廢話(huà)不多說(shuō),下面我們來(lái)看看如何基于模仿實(shí)現(xiàn)項(xiàng)目工程化。 從零搭建vue-cli 原創(chuàng)不易,如需轉(zhuǎn)載請(qǐng)聯(lián)系作者并注明出處 vue-cli的出現(xiàn)為vue工程化前端開(kāi)發(fā)工作流提供了開(kāi)箱即用的構(gòu)建配置,減輕了煩人的webpack配置流程。但高度封...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥(niǎo)雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門(mén),久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥(niǎo)雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門(mén),久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
閱讀 2022·2021-09-29 09:35
閱讀 1955·2019-08-30 14:15
閱讀 2980·2019-08-30 10:56
閱讀 964·2019-08-29 16:59
閱讀 577·2019-08-29 14:04
閱讀 1310·2019-08-29 12:30
閱讀 1031·2019-08-28 18:19
閱讀 515·2019-08-26 11:51