Реализовать 80% функциональности Vuex в 150 строках кода.

Vue.js Vuex

Автор: Инь Жунхуэй@Tencent

эта статьяадрес, добро пожаловать на просмотр

эта статьяадрес кода репозитория github, добро пожаловать звезда, спасибо.

Если вы заинтересованы в самостоятельной реализации каждого фреймворка с небольшим объемом кода, вот некоторые вещи, на которые вы можете обратить внимание:

build-your-own-react

build-your-own-flux

build-your-own-redux

содержание:

1. Простейший способ определения глобальных переменных через vuex, который можно использовать напрямую на любой странице через this.$store.state.count

2. Реализация метода геттера в vuex

3. Реализация методов мутации и фиксации

4. Реализация действий и способов отправки

5. Реализация модульного метода

6. Реализация: Vue.use (Vuex)

Давайте сначала посмотрим на эффект замены реального vuex на vuex, реализованный вами, и посмотрим, может ли он нормально работать и есть ли ошибка:

Судя по текущим результатам, работа нормальная и проблем нет. Рассмотрим пошаговый процесс внедрения:

1. Простейший способ определения глобальных переменных через vuex, который можно использовать напрямую на любой странице через this.$store.state.count

Код main.js выглядит следующим образом:

let store = new Vuex.Store({
  state: {
    count: 0
  }
}, Vue);

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

Код store.js выглядит следующим образом:

export class Store {
    constructor(options = {}, Vue) {
        this.options = options;
        Vue.mixin({ beforeCreate: vuexInit });
    }
    get state () {
        return this.options.state;
    }
}
function vuexInit () {
    const options = this.$options
    if (options.store) {
        // 组件内部设定了store,则优先使用组件内部的store
        this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = options.parent.$store
    }
}

Код интерфейса выглядит следующим образом:

<script>
export default {
  name: 'app',
  created() {
    console.log('打印出this.$store.state.count的结果',this.$store.state.count);
  },
}
</script>

Результат выполнения: значение this.$store.state.count успешно напечатано как 0.

2. Реализация метода геттера в vuex

Код main.js выглядит следующим образом:
let store = new Vuex.Store({
    state: {
        count: 0
    },
    getters: {
        getStatePlusOne(state) {
            return state.count + 1
        }
    }

}, Vue);

new Vue({
    store,
    render: h => h(App),
}).$mount('#app')

Код store.js выглядит следующим образом:

export class Store {
    constructor(options = {}, Vue) {
        this.options = options;
        this.getters = {}
        Vue.mixin({ beforeCreate: vuexInit });
        forEachValue(options.getters, (getterFn, getterName) => {
            registerGetter(this, getterName, getterFn);
        })
    }
    get state() {
        return this.options.state;
    }
}

function registerGetter(store, getterName, getterFn) {
    Object.defineProperty(store.getters, getterName, {
        get: () => {
            return getterFn(store.state)
        }
    })
}

// 将对象中的每一个值放入到传入的函数中作为参数执行
function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function vuexInit() {
    const options = this.$options
    if (options.store) {
        // 组件内部设定了store,则优先使用组件内部的store
        this.$store = typeof options.store === 'function' ?
            options.store() :
            options.store
    } else if (options.parent && options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = options.parent.$store
    }
}

Код интерфейса выглядит следующим образом:

<script>
export default {
  name: 'app',
  created() {
    console.log('打印出this.$store.getters.getStatePlusOne的结果',this.$store.getters.getStatePlusOne);
  },
}
</script>

результат операции: Значение this.$store.getters.getStatePlusOne успешно напечатано как 1.

3. Реализация методов мутации и фиксации

Код main.js выглядит следующим образом:
let store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        incrementFive(state) {
            // console.log('初始state', JSON.stringify(state));
            state.count = state.count + 5;
        }
    },
    getters: {
        getStatePlusOne(state) {
            return state.count + 1
        }
    }

}, Vue);

Код store.js выглядит следующим образом:

export class Store {
    constructor(options = {}, Vue) {
        Vue.mixin({ beforeCreate: vuexInit })
        this.options = options;
        this.getters = {};
        this.mutations = {};
        const { commit } = this;
        this.commit = (type) => {
            return commit.call(this, type);
        }
        forEachValue(options.getters, (getterFn, getterName) => {
            registerGetter(this, getterName, getterFn);
        });

        forEachValue(options.mutations, (mutationFn, mutationName) => {
            registerMutation(this, mutationName, mutationFn)
        });

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

    get state() {
        // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式
        return this._vm._data.state;
    }
    commit(type) {
        this.mutations[type]();
    }
}

function registerMutation(store, mutationName, mutationFn) {
    store.mutations[mutationName] = () => {
        mutationFn.call(store, store.state);
    }
}


function registerGetter(store, getterName, getterFn) {
    Object.defineProperty(store.getters, getterName, {
        get: () => {
            return getterFn(store.state)
        }
    })
}

// 将对象中的每一个值放入到传入的函数中作为参数执行
function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function vuexInit() {
    const options = this.$options
    if (options.store) {
        // 组件内部设定了store,则优先使用组件内部的store
        this.$store = typeof options.store === 'function' ?
            options.store() :
            options.store
    } else if (options.parent && options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = options.parent.$store
    }
}

Код интерфейса выглядит следующим образом:

<script>
export default {
  name: 'app',
  created() {
    console.log('打印出this.$store.getters.getStatePlusOne的结果',this.$store.getters.getStatePlusOne);
  },
  mounted() {
    setTimeout(() => {
      this.$store.commit('incrementFive');
      console.log('store state自增5后的结果', this.$store.state.count);
    }, 2000);
  },
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
}
</script>

Результат выполнения: успешно вывести результат 5 после того, как счетчик увеличится на 5 через 2 секунды.

4. Реализация действий и способов отправки

Код main.js выглядит следующим образом:
let store = new Vuex.Store({
    state: {
        count: 0
    },
    actions: {
        countPlusSix(context) {
            context.commit('plusSix');
        }
    },
    mutations: {
        incrementFive(state) {
            // console.log('初始state', JSON.stringify(state));
            state.count = state.count + 5;
        },
        plusSix(state) {
            state.count = state.count + 6;
        }
    },
    getters: {
        getStatePlusOne(state) {
            return state.count + 1
        }
    }

}, Vue);

Код store.js выглядит следующим образом:

export class Store {
    constructor(options = {}, Vue) {
        Vue.mixin({ beforeCreate: vuexInit })
        this.options = options;
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        const { dispatch, commit } = this;
        this.commit = (type) => {
            return commit.call(this, type);
        }
        this.dispatch = (type) => {
            return dispatch.call(this, type);
        }
        forEachValue(options.actions, (actionFn, actionName) => {
            registerAction(this, actionName, actionFn);
        });

        forEachValue(options.getters, (getterFn, getterName) => {
            registerGetter(this, getterName, getterFn);
        });

        forEachValue(options.mutations, (mutationFn, mutationName) => {
            registerMutation(this, mutationName, mutationFn)
        });

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

    get state() {
        // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式
        return this._vm._data.state;
    }
    commit(type) {
        this.mutations[type]();
    }
    dispatch(type) {
        return this.actions[type]();
    }
}

function registerMutation(store, mutationName, mutationFn) {
    store.mutations[mutationName] = () => {
        mutationFn.call(store, store.state);
    }
}

function registerAction(store, actionName, actionFn) {
    store.actions[actionName] = () => {
        actionFn.call(store, store)
    }
}

function registerGetter(store, getterName, getterFn) {
    Object.defineProperty(store.getters, getterName, {
        get: () => {
            return getterFn(store.state)
        }
    })
}

// 将对象中的每一个值放入到传入的函数中作为参数执行
function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function vuexInit() {
    const options = this.$options
    if (options.store) {
        // 组件内部设定了store,则优先使用组件内部的store
        this.$store = typeof options.store === 'function' ?
            options.store() :
            options.store
    } else if (options.parent && options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = options.parent.$store
    }
}

Код интерфейса выглядит следующим образом:

export default {
  name: 'app',
  created() {
    console.log('打印出this.$store.getters.getStatePlusOne的结果',this.$store.getters.getStatePlusOne);
  },
  mounted() {
    setTimeout(() => {
      this.$store.commit('incrementFive');
      console.log('store state自增5后的结果', this.$store.state.count);
    }, 2000);
    setTimeout(() => {
      this.$store.dispatch('countPlusSix');
      console.log('store dispatch自增6后的结果', this.$store.state.count);
    }, 3000);
  },
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
}

Результат выполнения: через 3 секунды дипатч увеличивается на 6 и выводит 11.

5. Реализация модульного метода

Код main.js выглядит следующим образом:
const pageA = {
    state: {
        count: 100
    },
    mutations: {
        incrementA(state) {
            state.count++;
        }
    },
    actions: {
        incrementAAction(context) {
            context.commit('incrementA');
        }
    }
}

let store = new Vuex.Store({
    modules: {
        a: pageA
    },
    state: {
        count: 0
    },
    actions: {
        countPlusSix(context) {
            context.commit('plusSix');
        }
    },
    mutations: {
        incrementFive(state) {
            // console.log('初始state', JSON.stringify(state));
            state.count = state.count + 5;
        },
        plusSix(state) {
            state.count = state.count + 6;
        }
    },
    getters: {
        getStatePlusOne(state) {
            return state.count + 1
        }
    }

}, Vue);

Код store.js выглядит следующим образом:

let _Vue;
export class Store {
    constructor(options = {}, Vue) {
        _Vue = Vue
        Vue.mixin({ beforeCreate: vuexInit })
        this.getters = {};
        this._mutations = {}; // 在私有属性前加_
        this._wrappedGetters = {};
        this._actions = {};
        this._modules = new ModuleCollection(options)
        const { dispatch, commit } = this;
        this.commit = (type) => {
            return commit.call(this, type);
        }
        this.dispatch = (type) => {
            return dispatch.call(this, type);
        }
        const state = options.state;
        const path = []; // 初始路径给根路径为空
        installModule(this, state, path, this._modules.root);
        this._vm = new Vue({
            data: {
                state: state
            }
        });
    }

    get state() {
        // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式
        return this._vm._data.state;
    }
    commit(type) {
        this._mutations[type].forEach(handler => handler());
    }
    dispatch(type) {
        return this._actions[type][0]();
    }
}

class ModuleCollection {
    constructor(rawRootModule) {
        this.register([], rawRootModule)
    }
    register(path, rawModule) {
        const newModule = {
            _children: {},
            _rawModule: rawModule,
            state: rawModule.state
        }
        if (path.length === 0) {
            this.root = newModule;
        } else {
            const parent = path.slice(0, -1).reduce((module, key) => {
                return module._children(key);
            }, this.root);
            parent._children[path[path.length - 1]] = newModule;
        }
        if (rawModule.modules) {
            forEachValue(rawModule.modules, (rawChildModule, key) => {
                this.register(path.concat(key), rawChildModule);
            })
        }
    }
}

function installModule(store, rootState, path, module) {
    if (path.length > 0) {
        const parentState = rootState;
        const moduleName = path[path.length - 1];
        _Vue.set(parentState, moduleName, module.state)
    }
    const context = {
        dispatch: store.dispatch,
        commit: store.commit,
    }
    const local = Object.defineProperties(context, {
        getters: {
            get: () => store.getters
        },
        state: {
            get: () => {
                let state = store.state;
                return path.length ? path.reduce((state, key) => state[key], state) : state
            }
        }
    })
    if (module._rawModule.actions) {
        forEachValue(module._rawModule.actions, (actionFn, actionName) => {
            registerAction(store, actionName, actionFn, local);
        });
    }
    if (module._rawModule.getters) {
        forEachValue(module._rawModule.getters, (getterFn, getterName) => {
            registerGetter(store, getterName, getterFn, local);
        });
    }
    if (module._rawModule.mutations) {
        forEachValue(module._rawModule.mutations, (mutationFn, mutationName) => {
            registerMutation(store, mutationName, mutationFn, local)
        });
    }
    forEachValue(module._children, (child, key) => {
        installModule(store, rootState, path.concat(key), child)
    })

}

function registerMutation(store, mutationName, mutationFn, local) {
    const entry = store._mutations[mutationName] || (store._mutations[mutationName] = []);
    entry.push(() => {
        mutationFn.call(store, local.state);
    });
}

function registerAction(store, actionName, actionFn, local) {
    const entry = store._actions[actionName] || (store._actions[actionName] = [])
    entry.push(() => {
        return actionFn.call(store, {
            commit: local.commit,
            state: local.state,
        })
    });
}

function registerGetter(store, getterName, getterFn, local) {
    Object.defineProperty(store.getters, getterName, {
        get: () => {
            return getterFn(
                local.state,
                local.getters,
                store.state
            )
        }
    })
}

// 将对象中的每一个值放入到传入的函数中作为参数执行
function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function vuexInit() {
    const options = this.$options
    if (options.store) {
        // 组件内部设定了store,则优先使用组件内部的store
        this.$store = typeof options.store === 'function' ?
            options.store() :
            options.store
    } else if (options.parent && options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = options.parent.$store
    }
}

Основной код интерфейса выглядит следующим образом:

<template>
  <div id="app">
    ==============主页================<br>
    主页数量count为: {{count}}<br>
    pageA数量count为: {{countA}}<br>
    ==========以下为PageA内容==========<br>
    <page-a></page-a>
  </div>
</template>

<script>
import pageA from './pageA';

export default {
  name: 'app',
  components: {
    pageA
  },
  created() {
    console.log('打印出this.$store.getters.getStatePlusOne的结果',this.$store.getters.getStatePlusOne);
  },
  mounted() {
    setTimeout(() => {
      this.$store.commit('incrementFive');
      console.log('store state自增5后的结果', this.$store.state.count);
    }, 2000);
    setTimeout(() => {
      this.$store.dispatch('countPlusSix');
      console.log('store dispatch自增6后的结果', this.$store.state.count);
    }, 3000);
  },
  computed: {
    count() {
      return this.$store.state.count;
    },
    countA() {
      return this.$store.state.a.count;
    }
  }
}
</script>

Страница pageA выглядит следующим образом:

<template>
    <div>
        页面A被加载
    </div>
</template>

<script>
export default {
  name: 'pageA',
  mounted() {
      setTimeout(() => {
          this.$store.dispatch('incrementAAction');
      }, 5000)
  },
}
</script>

Результат выполнения: действие incrementAAction запускается на странице A через 5 секунд, а значение countA в основном интерфейсе изменяется на 101, что является успешным.

С тех пор: было использовано около 150 строк кода для достижения примерно 80% функций vuex. Среди них нельзя использовать пространства имен. Другие в основном совпадают с синтаксисом исходного кода. Если вам интересно посмотреть поближе , вы можете двигаться шагкод репозитория на гитхабе, код написан после прочтения исходного кода vuex, поэтому, прочитав код этой статьи, а затем взглянув на код vuex, я думаю, вы поймете его с первого взгляда.

6. Реализация: Vue.use (Vuex)

Наконец, чтобы быть максимально похожим на исходный код vuex, также используется Vue.use (Vuex), а для реализации используется следующий код:
export function install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
        beforeCreate: function vuexInit() {
            const options = this.$options;
            if (options.store) {
                this.$store = options.store;
            } else if (options.parent && options.parent.$store) {
                this.$store = options.parent.$store;
            }
        }
    })
}

Отдел набирает новых сотрудников, который является отделом корпоративных продуктов Tencent и входит в бизнес-группу CSIG. Много льгот, высокая зарплата, только и ждут вас. Если вы заинтересованы, пожалуйста, нажмите на две ссылки ниже.   Lagoo.com/Jobs/521039… woohoo.products.com/job_detail/…

Использованная литература:

Build a Vuex Module

How does a minimal Vuex implementation look like?

Напишите свой собственный Vuex с нуля

Исходный код Vuex: как реализовать простой vuex

Исходный код Vue (3) — Vuex

Говоря о Vue.use

Официальная документация Vuex

репозиторий vuex на гитхабе