從0實現一個vuex

你們知道,在開發大型vue項目時,使用vuex時不可避免的,vuex可以幫助咱們在錯綜複雜的數據流中快速拿到本身想要的數據,提升開發效率,儘管vuex沒法持久化數據,但也能夠經過插件來解決該問題,總之vuex是大型項目中百利無一害的插件。html

 

 

在上文咱們實現了一個vue-router後,咱們如今就來實現一個vuex,首先咱們從vuex的原理圖入手:前端

 

 

 

 從原理圖咱們能夠看出,$store實例經過dispatch調用actions裏的異步方法,經過commit調用mutations裏的同步方法,並只能經過mutations改變state(這裏插一句:非嚴格模式下是能夠經過commit之外的方式改變state裏的狀態的,但在嚴格模式下,Vuex中修改state的惟一渠道就是執行 commit('xx', payload) 方法,其底層經過執行 this._withCommit(fn) 設置_committing標誌變量爲true,而後才能修改state,修改完畢還須要還原_committing變量。外部修改雖然可以直接修改state,可是並無修改_committing標誌位,因此只要watch一下state,state change時判斷是否_committing值爲true,便可判斷修改的合法性,在嚴格模式下,任何 mutation 處理函數之外修改 Vuex state 都會拋出錯誤。)而後getters可以及時獲取state中的狀態並做出計算(實際上getters就是一個計算屬性)vue

  接下來咱們來簡單作一個vuex的小demo,看看vuex到底實現了哪些功能:vue-router

  咱們在store文件的index.js中這樣寫:vuex

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    counter:0
  },
  mutations: {
//同步自加的方法
    add(state){
      state.counter++
    }
  },
  actions: {
//異步自加的方法
    add({commit}){
      setTimeout(()=>{
        commit('add')
      },2000)
    }
  },
  getters:{
//獲取乘二的值
    doubleCounter(state){
      return state.counter*2
},
//獲取平方值
    squareCounter(state){
      return state.counter*state.counter
    }
  },
  modules: {
  }
})

Home組件中這樣寫:異步

<template>
  <div class="hello">
    <button @click="$store.commit('add')">counter:{{$store.state.counter}}</button>
    <button @click="$store.dispatch('add')">async counter:{{$store.state.counter}}</button>
    <p>double counter:{{$store.getters.doubleCounter}}</p>
    <p>squareCounter:{{$store.getters.squareCounter}}</p>
   <h2>這是一個Home組件</h2>
   <p>我叫rick,歡迎來看個人文章:從0實現一個vuex</p>
  </div>
</template>

那麼咱們的頁面大體長這樣:async

 

 

 

點擊counter能夠自加,點擊async counter能夠延遲兩秒自加,double counter讀出雙倍的數值,squareCounter讀出平方數值函數

 

接下來咱們把引入的vuex換成本身自制的vuex,來繼續實現現有的這些功能:學習

 

import Vuex from './kvuex'

 

那麼熟悉了原理圖,設計了一個簡易的vuex功能示例,接下就要來要實現咱們本身的vuex啦,咱們大體按照以下思路進行:this

1. 首先在$store上掛載dispatch,state,getters,commmit等方法是確定的

2. 其次要實現state的響應式,即state改變,依賴於state的全部屬性都要能實現自動渲染

3. 接着實現commit和dispatch的內部方法,即爲何每次commit或dispatch都能自動調用相關的方法

4. 最後實現getters,要注意爲何這個getters可以實現只讀的以及它的內部如何實現計算屬性

作一個魚骨圖方便你們理解:接下來咱們來逐步實現每一個細節

 

 

 

1. 掛載$store

1.1 利用install和mixin

你們還記得我在實現vue-router那篇文章中的方法嗎?此處我建議你們先看比較簡單的vue-router的實現原理再看這篇文章哦~(見個人文章:https://www.cnblogs.com/funkyou/p/14580129.html)

咱們故技重施,依然利用開發插件的方式,老規矩,複習一下vue文檔:

 

 

 

爲了能讓全部vue組件都能經過this.$store.xxx訪問到state,mutations等,那麼咱們就要經過全局混入的方式爲vue原型上掛載一個$store的屬性,全局混入用法見文檔:

 

 

 

實現代碼以下:

// use調用時會傳入Vue
function install(_Vue){
  // 保存Vue構造函數,插件中使用
  Vue=_Vue

  Vue.mixin({
    beforeCreate() {
      // 判斷當前options選項是否含有store
      if(this.$options.store){
        // 若是含有就在原型上掛載這個store實例
        Vue.prototype.$store=this.$options.store
      }
    },
  })
}
// 由於index.js是經過Vuex接收的,因此咱們要這樣暴露出去(實際上Vuex={Store,install,xxx})
export default{Store,install}

2. 實現state響應式

2.1:借雞生蛋

首先咱們構造一個Store實例:

 

class Store{
   constructor(options){   
  
   }
}

 

 

其實這個options就是store中的配置項:

 

 

 

咱們要想實現store中的數據響應式,可否藉助現成的能實現響應式的示例來「借雞生蛋」呢?(Vue實例此時瑟瑟發抖,並大喊你不要過來呀~)沒錯,Vue,就是你了,咱們new 一個Vue實例,把data存放進去,這樣咱們的數據就能自動被Vue的defineProperty追蹤並設置set和get屬性,便可實現響應式:

     // data響應式處理
   this._vm= new Vue({
     data:{
    //把state藏進$$state中
       $$state:options.state
     }
  })

 

 

2.2: 利用get和set實現只讀

咱們但願咱們的state是不能經過mutations之外的任何方式修改的,即實現只讀,那麼能夠利用get和set屬性對state進行設置:

  // 存取器,store.state
  get state(){
    console.log(this._vm);
    return this._vm._data.$$state
  }
  set state(v){
    console.error('你沒法設置state的值')
  }

3.實現commit

3.1判斷方法

咱們要從mutations中判斷出當前要用的是哪一個方法,並在commit內部執行這個方法,傳入state的參數,但注意要在錯綜複雜的this環境中先把寶貴的options.mutations保存起來:

 

    //  保存mutations
    this._mutations=options.mutations
    // 保存actions
    this._actions=options.actions
    // 保存getters
    this._getters=options.getters

 

 

3.2偷天換日

這裏咱們想用的是commit(type,payload)中的type對應的方法,那麼咱們可否先把這個mutations[type]方法拷貝給一個函數,再在commit方法內部執行這個函數呢?答案是可行的,這就是一種偷天換日的函數複用思想:

 commit(type,payload){
    // 借用mutations[type]方法
   const entry= this._mutations[type]
   if(!entry){
     console.error('unknown mutation type');
   }
  //  執行這個mutations[type]方法並傳入state參數
   entry(this.state,payload)
  }

 

4. 實現dispatch

4.1注意參數

此處實現dispatch和mutations大體相同,但要注意actions和mutations中傳入參數的不一樣:

mutations中:

 

 

actions中:

 

 

顯然這裏entry要傳入的是store實例,在constructer中用this代指:

 dispatch(type,payload){
    const entry=this._actions[type]
    if(!entry){
      console.error('unknow action type')
    }
    entry(this,payload)
  }

 

4.2用bind留住this

但注意,此時commit內部的this仍是不是我想要設置的那個store實例了?看demo:

 

 

此時的this已經徹底亂套了,因此咱們還須要在commit中留住this,讓他執行的永遠是store實例,直接寫:

 //這樣commit和dispatch內部的this就是當前上下文中的this,即store實例 
   this.commit= this.commit.bind(this)
   this.dispatch= this.dispatch.bind(this)

5. 實現getters

5.1計算屬性

要想實現一個只讀的getters,此處咱們依然選擇在Vue實例中設置這個computed方法:

  // 定義computed選項和getters
    const computed={}
    this.getters={}
   
this._vm= new Vue({
    data:{
      $$state:options.state
    },
    computed
  })

 

 

5.2只讀屬性

此處咱們先保存store,隨後爲這個getters設置只讀屬性,咱們能夠用Object.defineProperty方法讓咱們能經過get讀到這個方法

5.3移花接木,變無參爲有參

接下來,咱們想借用getters裏的方法並傳入state參數,可是注意:咱們的getters方法是有參數的:

那麼咱們能夠經過Object.key拿到每一個方法的索引,再用一個fn保存當前索引下的方法,再在fn裏傳入state參數,以下:

   // 保存store
    const store=this
    // 遍歷拿到索引key,並經過store._getters[key]找到這個方法
    Object.keys(this._getters).forEach(key=>{
      // 獲取用戶定義的getter
      const fn =store._getters[key]
      // 轉換爲computed可使用無參數形式
      computed[key]=function(){
        return fn(store.state)
      }
      // 爲getters定義只讀屬性
      Object.defineProperty(store.getters,key,{
        get:()=> store._vm[key]
      })
    })

 

此時咱們打印這個fn,它便是:

或者

 

 

getters中的方法,咱們調用了它並完美的把state傳了進去,這個方法是否是讓人拍案叫絕~

 

接下來是所有源碼:

//1.插件:掛載$store
// 2.實現Store
let Vue  //保存Vue構造函數,插件中使用

class Store{
  constructor(options){
   console.log(options);
    //  保存mutations
    this._mutations=options.mutations
    // 保存actions
    this._actions=options.actions
    // 保存getters
    this._getters=options.getters

    // 定義computed選項和getters
    const computed={}
    this.getters={}
    
    // 保存store
    const store=this
    // 遍歷拿到索引key,並經過store._getters[key]找到這個方法
    Object.keys(this._getters).forEach(key=>{
      // 獲取用戶定義的getter
      const fn =store._getters[key]
      // 轉換爲computed可使用無參數形式
      computed[key]=function(){
        console.log(fn);
        return fn(store.state)
      }
      // 爲getters定義只讀屬性
      Object.defineProperty(store.getters,key,{
        get:()=> store._vm[key]
      })
    })
     // data響應式處理
   this._vm= new Vue({
    data:{
      $$state:options.state
    },
    computed
  })
    //這樣commit和dispatch內部的this就是當前上下文中的this,即store實例 
   this.commit= this.commit.bind(this)
   this.dispatch= this.dispatch.bind(this)
  }
  // 存取器,store.state
  get state(){
    console.log(this._vm);
    return this._vm._data.$$state
  }
  set state(v){
    console.error('can not set')
  }
  commit(type,payload){
    // 借用mutations[type]方法
   const entry= this._mutations[type]
   if(!entry){
     console.error('unknown mutation type');
   }
  //  執行這個mutations[type]方法並傳入state參數
   entry(this.state,payload)
  }
  dispatch(type,payload){
    const entry=this._actions[type]
    if(!entry){
      console.error('unknow action type')
    }
    entry(this,payload)
  }
}
// use調用時會傳入Vue
function install(_Vue){
  // 保存Vue構造函數,插件中使用
  Vue=_Vue
  Vue.mixin({
    beforeCreate() {
      // 判斷當前options選項是否含有store
      if(this.$options.store){
        // 若是含有就在原型上掛載這個store實例
        Vue.prototype.$store=this.$options.store
      }
    },
  })
}
// 由於index.js是經過Vuex接收的,因此咱們要這樣暴露出去(實際上Vuex={Store,install,xxx})
export default{Store,install}

 

最後看下效果:

完美實現~!若是你們想和我一塊兒學習前端交流心得指點江山,歡迎加個人vxshq173392531

相關文章
相關標籤/搜索