vuex原理解讀

1. Vuex實戰(zhàn)


上次文章介紹了Vue組件化之間通信的各種姿勢,其中vuex基本算是終極解決方案了,這個沒啥說的,直接貼代碼把

所謂各大框架的數(shù)據(jù)管理框架,原則上來說,就是獨(dú)立團(tuán)大了,所有事都團(tuán)長處理太累了,所以老李只管軍事,槍彈煙酒面這些數(shù)據(jù),交給趙政委,趙政委就是咱們的Vuex,從此以后 全團(tuán)共享的數(shù)據(jù),都必須得經(jīng)過趙政委統(tǒng)一進(jìn)行調(diào)配

我的風(fēng)格就是用過的東西,都喜歡造個輪子,實戰(zhàn)使用 只是基礎(chǔ)而已,話不多說看代碼

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state,n=1) {
      state.count += n
    }
  },
  actions:{
    incrementAsync({commit}){
      setTimeout(()=>{
        commit('increment',2)
      },1000)
    }
  }
})
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
<template>
  <div id="app">
    <div>沖啊,手榴彈扔了{(lán){$store.state.count}}個</div>
    <button @click="add">扔一個</button>
    <button @click="addAsync">蓄力扔倆</button>
  </div>
</template>
<script>

export default {
  name: 'app',

  methods:{
    add(){
      this.$store.commit('increment')
    },
    addAsync(){
      this.$store.dispatch('incrementAsync')
    }
  }
}
</script>

2. 實現(xiàn)自己Vuex


要實現(xiàn)Vue,首先要實現(xiàn)的 就是Vue.use(Vuex),這是vue安裝插件的機(jī)制,需要Vuex對外暴露一個install方法,會把Vue傳遞給install這個函數(shù),咱們來小小的測試一下下

3. 實現(xiàn)插件機(jī)制


新建zhao(趙政委).js 對外暴露install 方法,內(nèi)部在Vue的組件上掛載一個$store變量

class Store {
  constructor() {
    this.name = '趙政委'
  }
}


function install(Vue) {
  Vue.prototype.$store = new Store()
}
export default { Store, install }
// app.vue
<template>
  <div id="app">
  </div>
</template>
<script>

export default {
  name: 'app',
  created(){
    console.log(this.$store)
  }
}
</script>
// output Store {name: "趙政委"}

4. 傳遞store


真正使用的時候,store是通過new Vue傳遞進(jìn)來的,我們需要使用mixin在beforeCreated來掛載,這樣才能通過this.$option獲取傳遞進(jìn)來的store


// zhao.js
class Store {
  constructor() {
    this.name = '趙政委'
  }
}

function install(Vue) {
  Vue.mixin({
    beforeCreate(){
      // 這樣才能獲取到傳遞進(jìn)來的store
      // 只有root元素才有store,所以判斷一下
      if(this.$options.store){
        Vue.prototype.$store = store

      }
    }
  })
  // console.log(this)
}
export default { Store, install }
// store.js
import Vue from 'vue'
import Vuex from './zhao'

Vue.use(Vuex)
export default new Vuex.Store()

5. state


單純的數(shù)據(jù)渲染比較easy

// zhao.js
class Store {
  constructor(options={}) {
    // this.name = '趙政委'
    this.state = options.state
  }
}

6.mutation

修改數(shù)據(jù),并且需要通知到組件,這個需要數(shù)據(jù)是響應(yīng)式的,我們需要Vue的響應(yīng)式支持,所以這里也可以看到Vuex是和Vue強(qiáng)綁定的,不能脫離vue單獨(dú)使用

由于install的時候會傳遞一個Vue,我們維護(hù)一個全局變量,就不用再import vue了,如果zhao.js單獨(dú)發(fā)布,減小包體積

mutation實現(xiàn)也比較簡單,記錄一下mutation的函數(shù),commit的時候更新數(shù)據(jù)即可

// zhao.js

let Vue
class Store {
  constructor(options={}) {
    // this.name = '趙政委'
    this.state = options.state || {}
    this.mutations = options.mutations || {}
  }
  commit(type,arg){
    if(!this.mutations[type]){
      console.log('不合法的mutation')
      return 
    }
    this.mutations[type](this.state,arg)
  }
}

function install(_Vue) {
  // 這樣store執(zhí)行的時候,就有了Vue,不用import
  // 這也是為啥 Vue.use必須在新建store之前
  Vue = _Vue
  _Vue.mixin({
    beforeCreate(){
      // 這樣才能獲取到傳遞進(jìn)來的store
      // 只有root元素才有store,所以判斷一下
      if(this.$options.store){
        _Vue.prototype.$store = this.$options.store

      }
    }
  })
}
export default { Store, install }
// store.js
import Vue from 'vue'
import Vuex from './zhao'

Vue.use(Vuex)
export default new Vuex.Store({
  state:{
    count:0
  },
  mutations:{
    increment (state,n=1) {
      state.count += n
    }
  }
})

7. 響應(yīng)式state

每次點擊 count都變了,頁面并沒有相應(yīng)
想響應(yīng)式通知到頁面,最方面的莫過于使用Vue的響應(yīng)式機(jī)制,讓state編程相應(yīng)式

 this.state = new Vue({
      data:options.state
    })

8. action


異步actions,mutation 必須同步執(zhí)行這個限制么?Action 就不受約束!由于有異步任務(wù),commit單獨(dú)執(zhí)行,所以需要用箭頭函數(shù),確保內(nèi)部的this指向

let Vue
class Store {
  constructor(options={}) {
    // this.name = '趙政委'
    this.state = new Vue({
      data:options.state
    })
    this.mutations = options.mutations || {}
    this.actions = options.actions
  }
  commit = (type,arg)=>{
    this.mutations[type](this.state,arg)
  }
  dispatch(type, arg){
    this.actions[type]({
      commit:this.commit,
      state:this.state
    }, arg)
  }
}

9. getter


類似computed,實現(xiàn)也不難 ,使用Object.defineProperty代理一個getter即可,獲取getter內(nèi)部的值,直接執(zhí)行函數(shù)計算。掛載在store.getters之上

 handleGetters(getters){
    this.getters = {}
    Object.keys(getters).forEach(key=>{
      Object.defineProperty(this.getters,key,{
        get:()=>{
          return getters[key](this.state)
        }
      })

    })
  }
//store.js
  state:{
    count:0
  },
  getters:{
    killCount(state){
      return state.count * 2
    }
  },
<!-- html -->
<div>炸死了{(lán){$store.getters.killCount}}個柜子</div>

10. modules

vuex支持拆包,每個module有自己的state,getter,mutations,和actions,所以需要專門引入喝安裝modules,并且遞歸支持深層嵌套,之前的handleGetters之類的東東,每個module都得執(zhí)行一下

深層次嵌套,state需要getter代理一下

11. 注冊modules


掛載到root上

register(path, module){
    const newModule = {
      children: {},
      module: module,
      state: module.state
    }
    if(path.length){
      // path有值,子模塊
      const parent = path.slice(0, -1).reduce((module, key) => {
        return module.children(key);
      }, this.root);
      parent.children[path[path.length - 1]] = newModule;
    }else{
      // 空 就是根目錄
      this.root = newModule
    }
    if(module.modules){
      this.forEachObj(module.modules,(name,mod)=>{
        // console.log(123,name,mod)
        this.register([...path,name],mod)
      })
    }
  }

12. 啟動modules


installModules(state,path,module){
    // 安裝所有的module的mutation,actions,
    if(path.length>0){
      const moduleName = this.last(path);
    // 默認(rèn)名字都注冊在一個命名空間里
      Vue.set(state, moduleName,module.state)
    }
    
    this.forEachObj(module.children, (key,child)=>{
      this.installModules(state, [...path,key],child)
    })
  }
constructor(options={}) {
    // this.name = '趙政委'
    this._vm = new Vue({
      data:{
        state:options.state
      }
    })
    // 根模塊
    this.root = null
    this.mutations = options.mutations || {}
    this.actions = options.actions
    this.handleGetters(options.getters)
    // 注冊一下module,遞歸,變成一個大的對象 掛載到root
    this.register([], options)
    this.installModules(options.state, [], this.root)
    // this.installModules(options.modules)

    
  }
  get state(){
    return this._vm._data.state
  }
installModules(state,path,module){
    // 安裝所有的module的mutation,actions,
    if(path.length>0){
      const moduleName = this.last(path);
    // 默認(rèn)名字都注冊在一個命名空間里
      Vue.set(state, moduleName,module.state)
    }
    // 設(shè)置上下文,獲取state要遍歷 path
    const context = {
      dispatch: this.dispatch,
      commit: this.commit,
    }
    Object.defineProperties(context, {
      getters: {
        get: () => this.getters
      },
      state: {
        get: () => {
          let state = this.state;
          return path.length ? path.reduce((state, key) => state[key], state) : state
        }
      }
    })
    // 注冊mutations 傳遞正確的state
    this.registerMutations(module.module.mutations,context)
    // 注冊action
    this.registerActions(module.module.actions,context)

    // 注冊getters
    this.registerGetters(module.module.getters,context)

    // 遞歸
    this.forEachObj(module.children, (key,child)=>{
      this.installModules(state, [...path,key],child)
    })
  }

13. mapState


其實很簡單,直接返回對應(yīng)的值就可以了,computed內(nèi)部可以通過this.$store拿到,代碼就呼之欲出了

function mapState(obj){
  const ret = {}
  Object.keys(obj).forEach((key)=>{
    // 支持函數(shù)
    let val = obj[key]
    ret[key] = function(){
      const state = this.$store.state
      return typeof val === 'function'
                          ? val.call(this, state)
                          : state[val]
    } 

  })
  return ret
}

14. mapMutations


function mapMutations(mutations){
  const ret = {}
  mutations.forEach((key)=>{
    ret[key] = function(){
      const commit = this.$store.commit
      commit(key)
    } 

  })
  return ret
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在前面 因為對Vue.js很感興趣,而且平時工作的技術(shù)棧也是Vue.js,這幾個月花了些時間研究學(xué)習(xí)了一下Vue...
    染陌同學(xué)閱讀 1,712評論 0 12
  • 基于Vue的一些資料 內(nèi)容 UI組件 開發(fā)框架 實用庫 服務(wù)端 輔助工具 應(yīng)用實例 Demo示例 element★...
    嘗了又嘗閱讀 1,284評論 0 1
  • 上一章總結(jié)了 Vuex 的框架原理,這一章我們將從 Vuex 的入口文件開始,分步驟閱讀和解析源碼。由于 Vuex...
    你的肖同學(xué)閱讀 1,888評論 3 16
  • ## 框架和庫的區(qū)別?> 框架(framework):一套完整的軟件設(shè)計架構(gòu)和**解決方案**。> > 庫(lib...
    Rui_bdad閱讀 3,153評論 1 4
  • Vuejs技術(shù)棧知識點小結(jié) 閱讀目錄 前言 Vue vue-router vuex 前言 上家公司的項目主要是使用...
    yichen_china閱讀 945評論 0 0

友情鏈接更多精彩內(nèi)容