如何手寫一個簡易的 Vuex
前言
本文適合使用過 Vuex 的人閱讀,來了解下怎麼自己實現一個 Vuex。
基本骨架
這是本專案的src/store/index.js檔案,看看一般 vuex 的使用
import Vue from 'vue' import Vuex from './myvuex' // 引入自己寫的 vuex import * as getters from './getters' import * as actions from './actions' import state from './state' import mutations from './mutations' Vue.use(Vuex) // Vue.use(plugin)方法使用vuex外掛 // vuex 匯出一個類叫Store,並傳入物件作為引數 export default new Vuex.Store({ state,mutations,actions,getters,})
Vue.use
的用法:
安裝 Vue.js 外掛。如果外掛是一個物件,必須提供 install 方法。如果外掛是一個函式,它會被作為 install 方法。install 方法呼叫時,會將 Vue 作為引數傳入。這個方法的第一個引數是 Vue 構造器,第二個引數是一個可選的選項物件。
- 該方法需要在呼叫 new Vue() 之前被呼叫。
- 當 install 方法被同一個外掛多次呼叫,外掛將只會被安裝一次。
即是我們需要在./myvuex.js
中匯出 install
方法,同時匯出一個類Store
,於是第一步可以寫出程式碼:
let Vue = null class Store { constructor(options) {} } function install(_Vue) { Vue = _Vue // 上面Store類需要能獲取到Vue } export default { Store,install,}
install 方法
當我們使用 vuex 的時候,每一個元件上面都有一個this.$store屬性,裡面包含了 state,mutations, actions, getters 等,所以我們也需要在每個元件上都掛載一個$store 屬性,要讓每一個元件都能獲取到,這裡我們使用Vue.mixin(mixin),用法介紹如下:
全域性註冊一個混入,影響註冊之後所有建立的每個 Vue 例項。可以使用混入向元件注入自定義的行為,它將影響每一個之後建立的 Vue 例項。
function install(_Vue) { Vue = _Vue // install方法呼叫時,會將Vue作為引數傳入(上面Store類需要用到Vue) // 實現每一個元件,都能通過this呼叫$store Vue.mixin({ beforeCreate() { // 通過this.$options可以獲取new Vue({引數}) 傳遞的引數 if (this.$options && this.$options.store) { // 證明這個this是根例項,也就是new Vue產生的那個例項 this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子元件獲取父元件的$store屬性 this.$store = this.$parent.$store } },}) }
state
由於 Vuex 是基於 Vue 的響應式原理基礎,所以我們要讓資料改變可重新整理檢視,則需要建立一個 vue 例項
class Store { // options 即是 Vuex.Store({})傳入的引數 constructor(options) { // vuex 的核心就是借用了vue的例項,因為vue的例項資料變化,會重新整理檢視 let vm = new Vue({ data: { state: options.state,},}) // state this.state = vm.state } }
commit
我們使用 vuex 改變資料時,是觸發 commit 方法,即是這樣使用的:
this.$store.commit('eventName','引數' );
所以我們要實現一個commit方法,把 Store 建構函式傳入的 mutations 做下處理
class Store { constructor(options) { // 實現 state ... // mutations this.mutations = {} // 儲存傳進來的mutations let mutations = options.mutations || {} // 迴圈取出事件名進行處理(mutations[事件名]: 執行方法) Object.keys(mutations).forEach(key => { this.mutations[key] = params => { mutations[key].call(this,this.state,params) // 修正this指向 } }) } commit = (key,params) => { // key為要觸發的事件名 this.mutations[key](params) } }
dispatch
跟上面的 commit 流程同理
class Store { constructor(options = {}) { // ... // actions this.actions = {} let actions = options.actions || {} Object.keys(actions).forEach(key => { this.actions[key] = params => { actions[key].call(this,this,params) } }) } dispatch = (type,payload) => { this.actions[type](payload) } }
getters
getters 實際就是返回 state 的值,在使用的時候是放在 computed 屬性,每一個 getter 都是函式形式;
getters 是需要雙向繫結的。但不需要雙向繫結所有的 getters,只需要繫結專案中事件使用的 getters。
這裡使用Object.defineProperty()方法,它會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,並返回此物件。
class Store { constructor(options = {}) { // ... // getters this.getters = {} let getters = options.getters || {} Object.keys(getters).forEach(key => { Object.defineProperty(this.getters,key,{ get: () => { return getters[key].call(this,this.state) },}) }) } }
到此為止,已經可以使用我們自己寫的 vuex 做一些基本操作了,但只能通過this.$store.xx的形式呼叫,故需要再實現方法。
map 輔助函式
先來說說 mapState
沒有 map 輔助函式之前這樣使用:
computed: { count () { return this.$store.state.count } }
當對映的計算屬性的名稱與 state 的子節點名稱相同時,給 mapState 傳一個字串陣列。
computed: { // 使用物件展開運算子將此物件混入到外部物件中 ...mapState(['count']) }
我們這裡簡單就只實現陣列的情況
export const mapState = args => { let obj = {} args.forEach(item => { obj[item] = function() { return this.$store.state[item] } }) return obj }
之後幾個 map 輔助函式都是類似
- mapGetters
export const mapGetters = args => { let obj = {} args.forEach(item => { obj[item] = function() { return this.$store.getters[item] } }) return obj }
- mapMutations
export const mapMutations = args => { let obj = {} args.forEach(item => { obj[item] = function(params) { return this.$store.commit(item,params) } }) return obj }
- mapActions
export const mapActions = args => { let obj = {} args.forEach(item => { obj[item] = function(payload) { return this.$store.dispatch(item,payload) } }) return obj }
完整程式碼
let Vue = null class Store { constructor(options) { // vuex 的核心就是借用了vue的例項,因為vue的例項資料變化,會重新整理檢視 let vm = new Vue({ data: { state: options.state,}) // state this.state = vm.state // mutations this.mutations = {} // 儲存傳進來的mutations let mutations = options.mutations || {} Object.keys(mutations).forEach(key => { this.mutations[key] = params => { mutations[key].call(this,params) } }) // actions this.actions = {} let actions = options.actions || {} Object.keys(actions).forEach(key => { this.actions[key] = params => { actions[key].call(this,params) } }) // getters this.getters = {} let getters = options.getters || {} Object.keys(getters).forEach(key => { Object.defineProperty(this.getters,}) }) } commit = (key,params) => { this.mutations[key](params) } dispatch = (type,payload) => { this.actions[type](payload) } } export const mapState = args => { let obj = {} args.forEach(item => { obj[item] = function() { return this.$store.state[item] } }) return obj } export const mapGetters = args => { let obj = {} args.forEach(item => { obj[item] = function() { return this.$store.getters[item] } }) return obj } export const mapMutations = args => { let obj = {} args.forEach(item => { obj[item] = function(params) { return this.$store.commit(item,params) } }) return obj } export const mapActions = args => { let obj = {} args.forEach(item => { obj[item] = function(payload) { return this.$store.dispatch(item,payload) } }) return obj } function install(_Vue) { Vue = _Vue // install方法呼叫時,會將Vue作為引數傳入(上面Store類需要用到Vue) // 實現每一個元件,都能通過this呼叫$store Vue.mixin({ beforeCreate() { // 通過this.$options可以獲取new Vue({引數}) 傳遞的引數 if (this.$options && this.$options.store) { // 證明這個this是根例項,也就是new Vue產生的那個例項 this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子元件獲取父元件的$store屬性 this.$store = this.$parent.$store } },}) } export default { Store,}
以上就是如何手寫一個簡易的 Vuex的詳細內容,更多關於手寫 vuex的資料請關注我們其它相關文章!