從0開始寫一個本身的Vuex

Vuex從0開始學習源碼

前言

嘗試從0開始,寫一個Vuex(主要是copy vuex的源代碼),從中學習下vuex的源代碼.先來看下列子中是怎麼使用store的。vue

import Vue from 'vue'
import Vuex from '../../src'

Vue.use(Vuex)

// mutation types
// optional if you don't like constants.
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'

// root state object.
// each Vuex instance is just a single state tree.
const state = {
  count: 0
}

// actions are what components will be able to
// call as store.actions.xxx
// note these are not the final functions the
// components will be calling.
const actions = {

  // for simple actions that just dispatches a single mutation,
  // we can just provide the mutation type.
  increment: INCREMENT,
  decrement: DECREMENT,

  // for a normal action function, it always recieves the store
  // instance as the first argument, from which we can get the
  // dispatch function and the state object. Any additional
  // arguments will follow the store argument.
  incrementIfOdd: ({ dispatch, state }) => {
    if ((state.count + 1) % 2 === 0) {
      dispatch(INCREMENT)
    }
  },

  // Same thing for async actions.
  incrementAsync: ({ dispatch }) => {
    setTimeout(() => {
      dispatch(INCREMENT)
    }, 1000)
  }
}

// mutations are operations that actually mutates the state.
// each mutation handler gets the entire state tree as the
// first argument, followed by additional payload arguments.
// mutations must be synchronous and can be recorded by middlewares
// for debugging purposes.
const mutations = {
  [INCREMENT] (state) {
    state.count++
  },
  [DECREMENT] (state) {
    state.count--
  }
}

// A Vuex instance is created by combining the state, the actions,
// and the mutations. Because the actions and mutations are just
// functions that do not depend on the instance itself, they can
// be easily tested or even hot-reloaded (see counter-hot example).
// 
// You can also provide middlewares, which is just an array of
// objects containing some hooks to be called at initialization
// and after each mutation.
export default new Vuex.Store({
  state,
  actions,
  mutations
})

開始 第一步

Vuex做爲一個插件 先得實現install方法。同時咱們在install方法裏面在Vue組件注入$store,也就是爲何vue中各個子組件爲何可以經過this.$store訪問到store這個對象git

let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{

}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

上述代碼中。
先定義一個Vue變量。有兩個做用
第一個做用就是給Vue各個組件注入$store變量,另一個功能後面會說到github

第二步 暴露state

咱們使用vuex的時候,會傳入state給頁面訪問,同時支持當頁面中用到state裏面的變量的時候。及時更新狀態。這裏就會Vue的另一個功能,雙向綁定。vuex

let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{
    constructor ({
        state = {},
        actions = {},
        mutations = {}
        }){
        //依賴vue雙向綁定
        this._vm = new Vue({
            data : state
        })

    }
    get state (){
        //頁面中經過此方法獲取state
        return this._vm._data;
    }
    set state (v){
        throw new Error('[Vuex] vuex root state is read only.')
    }
}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

clipboard.png
能夠看到頁面中count的數值已經能夠顯示了app

第三步實現actions

Vuex中的action是用來幹嗎?是用來dispatch事件,從而來執行mutations的,中間能夠穿插一些邏輯,因此咱們封裝下actionsasync

import { createAction, mergeObjects } from './util'
let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{
    constructor ({
        state = {},
        actions = {},
        mutations = {}
        }){
        //依賴vue雙向綁定
        this._vm = new Vue({
            data : state
        })
        this.actions = Object.create(null)
        //構造下action。兼容字符串和function兩種模式
        this._setupActions(actions);
    }
    get state (){
        //頁面中經過此方法獲取state
        return this._vm._data;
    }
    set state (v){
        throw new Error('[Vuex] vuex root state is read only.')
    }
    _setupActions (actions){
        this._actions = Object.create(null);
        actions = Array.isArray(actions) ? mergeObjects(actions) : actions;
        Object.keys(actions).forEach(name =>{
            this._actions[name] = createAction(actions[name],this); //兼容string 和function的寫法
            if(!this.actions[name]){
                this.actions[name] = (...args) =>this._actions[name](...args)
            }
        })
    }
}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

utils.js中的代碼ide

export function createAction (action, store) {
  if (typeof action === 'string') {
    // simple action string shorthand
    return (...payload) => store.dispatch(action, ...payload)
  } else if (typeof action === 'function') {
    // normal action
    return (...payload) => action(store, ...payload)
  }
}

第四步 構造下mutations

這步比較簡單,直接看代碼學習

import { createAction, mergeObjects } from './util'
let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{
    constructor ({
        state = {},
        actions = {},
        mutations = {}
        }){
        //依賴vue雙向綁定
        this._vm = new Vue({
            data : state
        })
        this.actions = Object.create(null)
        //構造下action。兼容字符串和function兩種模式
        this._setupActions(actions);
        //構造mutations
        this._setupMutations(mutations);
    }
    get state (){
        //頁面中經過此方法獲取state
        return this._vm._data;
    }
    set state (v){
        throw new Error('[Vuex] vuex root state is read only.')
    }
    _setupActions (actions){
        this._actions = Object.create(null);
        actions = Array.isArray(actions) ? mergeObjects(actions) : actions;
        Object.keys(actions).forEach(name =>{
            this._actions[name] = createAction(actions[name],this); //兼容string 和function的寫法
            if(!this.actions[name]){
                this.actions[name] = (...args) =>this._actions[name](...args)
            }
        })
    }
    _setupMutations(mutations){
        this._mutations = Array.isArray(mutations) ? mergeObjects(mutations,true) : mutations
    }
}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

第五步,實現dispatch方法

咱們知道咱們在action裏面dispatch事件了。這個就相似如今的commit。dispatch事件,是要執行mutations的測試

import { createAction, mergeObjects } from './util'
let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{
    constructor ({
        state = {},
        actions = {},
        mutations = {}
        }){
        //依賴vue雙向綁定
        this._vm = new Vue({
            data : state
        })
        this.actions = Object.create(null)
        //構造下action。兼容字符串和function兩種模式
        this._setupActions(actions);
        //構造mutations
        this._setupMutations(mutations);
    }
    get state (){
        //頁面中經過此方法獲取state
        return this._vm._data;
    }
    set state (v){
        throw new Error('[Vuex] vuex root state is read only.')
    }
    _setupActions (actions){
        this._actions = Object.create(null);
        actions = Array.isArray(actions) ? mergeObjects(actions) : actions;
        Object.keys(actions).forEach(name =>{
            this._actions[name] = createAction(actions[name],this); //兼容string 和function的寫法
            if(!this.actions[name]){
                this.actions[name] = (...args) =>this._actions[name](...args)
            }
        })
    }
    _setupMutations(mutations){
        this._mutations = Array.isArray(mutations) ? mergeObjects(mutations,true) : mutations
    }
    /**
     * 執行mutation
     */
    dispatch (type,...payload) {
        const mutation = this._mutations[type];
        const state = this.state;
        if(mutation){
            this._dispatching = true
            if(Array.isArray(mutation)){
                //遍歷執行
                mutation.forEach(m =>m(state,...payload))
            }else{
                mutation(state,...payload)
            }
            this._dispatching = false
        }else{
            console.warn("[vuex] unknown mutation:${type}")
        }
    }
}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

到此爲止 測試頁面的+ -count功能應該是沒有問題了this

clipboard.png

當點擊後面兩個方法,發現會有報錯

clipboard.png

這個什麼緣由呢? 調試也能夠發現,做用域的問題,調用不了vuex裏面的對象

const dispatch = this.dispatch
        this.dispatch = (...args) =>{
            dispatch.apply(this,args)
        }

完整代碼

import { createAction, mergeObjects } from './util'
let Vue //存儲Vue變量。一是爲了注入$store到各個Vue組件,二是後續要用到Vue的雙向綁定的功能
export class Store{
    constructor ({
        state = {},
        actions = {},
        mutations = {}
        }){
        //加上這個,解決在外面調用dispatch的問題
        const dispatch = this.dispatch
        this.dispatch = (...args) =>{
            dispatch.apply(this,args)
        }
        //依賴vue雙向綁定
        this._vm = new Vue({
            data : state
        })
        this.actions = Object.create(null)
        //構造下action。兼容字符串和function兩種模式
        this._setupActions(actions);
        //構造mutations
        this._setupMutations(mutations);
    }
    get state (){
        //頁面中經過此方法獲取state
        return this._vm._data;
    }
    set state (v){
        throw new Error('[Vuex] vuex root state is read only.')
    }
    _setupActions (actions){
        this._actions = Object.create(null);
        actions = Array.isArray(actions) ? mergeObjects(actions) : actions;
        Object.keys(actions).forEach(name =>{
            this._actions[name] = createAction(actions[name],this); //兼容string 和function的寫法
            if(!this.actions[name]){
                this.actions[name] = (...args) =>this._actions[name](...args)
            }
        })
    }
    _setupMutations(mutations){
        this._mutations = Array.isArray(mutations) ? mergeObjects(mutations,true) : mutations
    }
    /**
     * 執行mutation
     */
    dispatch (type,...payload) {
        const mutation = this._mutations[type];
        const state = this.state;
        if(mutation){
            this._dispatching = true
            if(Array.isArray(mutation)){
                //遍歷執行
                mutation.forEach(m =>m(state,...payload))
            }else{
                mutation(state,...payload)
            }
            this._dispatching = false
        }else{
            console.warn("[vuex] unknown mutation:${type}")
        }
    }
}
export function install (_Vue){
    Vue = _Vue
    const _init = Vue.prototype._init;
    Vue.prototype._init = function(options){
        options = options || {}
        if(options.store){
            this.$store = options.store
        }else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
        _init.call(this,options)
    }
}
export default {
    Store,install
}

只此。VUEX的基本功能已完成了

以上代碼都來至vuex 0.3
我不生成代碼,只作代碼的搬運工
測試代碼在這裏
https://github.com/denditang/...

相關文章
相關標籤/搜索