Vue秩序白銀 —構建本身的Vuex

代碼javascript

原文css

1. Vuex實戰

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

所謂各大框架的數據管理框架,原則上來講,就是獨立團大了,全部事都團長處理太累了,因此老李只管軍事,槍彈菸酒面這些數據,交給趙政委,趙政委就是我們的Vuex,今後之後 全團共享的數據,都必須得通過趙政委統一進行調配vue

個人風格就是用過的東西,都喜歡造個輪子,實戰使用 只是基礎而已,話很少說看代碼java

// 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>衝啊,手榴彈扔了{{$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. 實現本身Vuex

要實現Vue,首先要實現的 就是Vue.use(Vuex),這是vue安裝插件的機制,須要Vuex對外暴露一個install方法,會把Vue傳遞給install這個函數,我們來小小的測試一下下git

3. 實現插件機制

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

vuex的install源碼vuex

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傳遞進來的,咱們須要使用mixin在beforeCreated來掛載,這樣才能經過this.$option獲取傳遞進來的store編程

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

function install(Vue) {
  Vue.mixin({
    beforeCreate(){
      // 這樣才能獲取到傳遞進來的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

單純的數據渲染比較easyapp

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

image-20190321163916001

6. mutation

修改數據,而且須要通知到組件,這個須要數據是響應式的,咱們須要Vue的響應式支持,因此這裏也能夠看到Vuex是和Vue強綁定的,不能脫離vue單獨使用

因爲install的時候會傳遞一個Vue,咱們維護一個全局變量,就不用再import vue了,若是zhao.js單獨發佈,減少包體積

mutation實現也比較簡單,記錄一下mutation的函數,commit的時候更新數據便可

// 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執行的時候,就有了Vue,不用import
  // 這也是爲啥 Vue.use必須在新建store以前
  Vue = _Vue
  _Vue.mixin({
    beforeCreate(){
      // 這樣才能獲取到傳遞進來的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
    }
  }
})
複製代碼

每次點擊 count都變了,頁面並無相應

01

7. 響應式state

想響應式通知到頁面,最方面的莫過於使用Vue的響應式機制,讓state編程相應式

this.state = new Vue({
      data:options.state
    })
複製代碼

8. action

異步actions,mutation 必須同步執行這個限制麼?Action 就不受約束!因爲有異步任務,commit單獨執行,因此須要用箭頭函數,確保內部的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)
  }
}

複製代碼

bingo

02

一個賊拉迷你的vuex基本完成,還有幾個概念,須要繼續完善

9. getter

相似computed,實現也不難 ,使用Object.defineProperty代理一個getter便可,獲取getter內部的值,直接執行函數計算。掛載在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
    }
  },
複製代碼
<div>炸死了{{$store.getters.killCount}}個櫃子</div>

複製代碼

10. modules

vuex支持拆包,每一個module有本身的state,getter,mutations,和actions,因此須要專門引入喝安裝modules,而且遞歸支持深層嵌套,以前的handleGetters之類的東東,每一個module都得執行一下

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

image-20190321123937642

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);
    // 默認名字都註冊在一個命名空間裏
      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);
    // 默認名字都註冊在一個命名空間裏
      Vue.set(state, moduleName,module.state)
    }
    // 設置上下文,獲取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. store.js

// zhao.js

let Vue
class Store {
  constructor(options={}) {
    // this.name = '趙政委'
    this._vm = new Vue({
      data:{
        state:options.state
      }
    })
    // 根模塊
    this.root = null
    this.mutations = {}
    this.actions = {}
    this.getters = {}
    // 註冊一下module,遞歸,變成一個大的對象 掛載到root
    this.register([], options)
    this.installModules(options.state, [], this.root)
  }
  get state(){
    return this._vm._data.state
  }
  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)
      })
    }
  }
  forEachObj(obj={},fn){
    Object.keys(obj).forEach(key=>{
        fn(key, obj[key])
    })
  }
  commit = (type,arg)=>{
    this.mutations[type](this.state,arg)
  }
  dispatch(type, arg){
    this.actions[type]({
      commit:this.commit,
      state:this.state
    }, arg)
  }
  last(arr){
    return arr[arr.length-1]
  }
  installModules(state,path,module){
    // 安裝全部的module的mutation,actions,
    if(path.length>0){
      const moduleName = this.last(path);
    // 默認名字都註冊在一個命名空間裏
      Vue.set(state, moduleName,module.state)
    }
    // 設置上下文,獲取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)
    })
  }
  handleGetters(getters){
    Object.keys(getters).forEach(key=>{
      Object.defineProperty(this.getters,key,{
        get:()=>{
          return getters[key](this.state)
        }
      })
    })
  }
  registerGetters(getters, context){
    this.forEachObj(getters,(key,getter)=>{
      Object.defineProperty(this.getters,key,{
        get:()=>{
          return getter(
            // module的state
            context.state,
            context.getters,
            // 最外層的store
            this.state
          )
        }
      })
    })

  }
  registerMutations(mutations, context){
    if(mutations){
      this.forEachObj(mutations, (key,mutation)=>{
        this.mutations[key] = ()=>{
          mutation.call(this, context.state)
        }
      })
    }
  }
  registerActions(actions,context){
    if(actions){
      this.forEachObj(actions, (key,action)=>{
        this.actions[key] = ()=>{
          action.call(this, context)
        }
      })
    }
  }
}

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

      }
    }
  })
}
export default { Store, install }
複製代碼

14. store.js

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

Vue.use(Vuex)

const commander = {
  state: {
      num: 17
  },
  mutations: {
      fire(state) {
          state.num -= 1
      }
  },
  getters:{
    fireCount(state){
      return (17-state.num) *100 
    },
    totalCount(state,getters,rootState){
      return getters.fireCount + rootState.count*2
    }
  },
  actions: {
      fireAsync({commit}) {
        setTimeout(()=>{
          commit('fire');
        },2000)
      }
  }
}

export default new Vuex.Store({
  modules:{
    commander
  },
  state:{
    count:0
  },
  getters:{
    killCount(state){
      return state.count * 2
    }
  },
  mutations:{
    increment (state,n=1) {
      state.count += n
    }
  },
  actions:{
    incrementAsync(context){
      console.log(context,123)
      setTimeout(()=>{
        context.commit('increment',2)
      },1000)
    }
  }
})

複製代碼

15. 組件使用

<template>
  <div id="app">
    <p>衝啊,手榴彈扔了{{$store.state.count}}個</p>
    <p>炸死了{{$store.getters.killCount}}個櫃子</p>
    <p>
      <button @click="add">扔一個</button>
    <button @click="addAsync">蓄力扔倆</button>

    </p>
    <Battalion></Battalion>
  </div>
</template>
<script> import Battalion from '@/components/Battalion' export default { name: 'app', components:{ Battalion }, created(){ }, methods:{ add(){ this.$store.commit('increment') console.log(this.$store.state) console.log(this.$store.state.getters) }, addAsync(){ this.$store.dispatch('incrementAsync') } } } </script>
<style> div{ border:1px solid red; margin:20px; padding:20px; } </style>


複製代碼
子組件
<template>
    <div>
      <p> 意大利炮還有{{$store.state.commander.num}}發炮彈 </p>
      <p> 意大利炮炸死了{{$store.getters.fireCount}}個鬼子 和手榴彈一塊兒是{{$store.getters.totalCount}} </p>
        <button @click="fire">開炮</button>
        <button @click="fireAsync">一會再開炮</button>
    </div>
</template>

<script> export default { name: 'pageA', mounted(){ }, methods:{ fire(){ this.$store.commit('fire') }, fireAsync(){ this.$store.dispatch('fireAsync') } } } </script>

複製代碼

03

16. mapState

其實很簡單,直接返回對應的值就能夠了,computed內部能夠經過this.$store拿到,代碼就呼之欲出了

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

  })
  return ret
}
複製代碼

17. mapMutations

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

  })
  return ret
}
複製代碼

mapGetters和mapActions原理相似 不贅述了 白白

相關文章
相關標籤/搜索