1. 首页

用149行代码实现Vuex 80%的核心功能

如果你对自己用少量代码实现各个框架感兴趣,那下面这些你都可以一看:

build-your-own-react

build-your-own-flux

build-your-own-redux

目录:

一.完成最简单的通过vuex定义全局变量,在任何一个页面可以通过this.$store.state.count可以直接使用

二.vuex中的getter方法的实现

三.mutation和commit方法的实现

四.actions和dispatch方法的实现

五.module方法的实现

六.实现:Vue.use(Vuex)

先来看一下用自己实现的的vuex替代真实的vuex的效果,看看能否正常运行,有没有报错:

用149行代码实现Vuex 80%的核心功能

从运行结果来看,运行正常,没有问题。接下来看看一步一步实现的过程:

一. 完成最简单的通过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

二. vuex中的getter方法的实现

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

三. mutation和commit方法的实现

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>

运行结果:成功在2秒之后输出count自增5后的结果5

四. actions和dispatch方法的实现

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秒之后dipatch自增6输出11

五. module方法的实现

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),Js中文网 - 前端进阶资源教程 https://www.javascriptc.com); state.count = state.count + 5; }, plusSix(state) { state.count = state.count + 6; } }, getters: { getStatePlusOne(state) { return state.count + 1 } } }, Vue);

Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到

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) }); }//Js中文网 - 前端进阶资源教程 https://www.javascriptc.com 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>

Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到

运行结果: 在5秒后A页面触发incrementAAction,主界面中的countA变化为101,成功

自此:基本用了150行左右的代码实现了vuex 80%左右的功能了,其中还有namespace等不能够使用,其他基本都和源代码语法相同,如果你有兴趣仔细再看看,可以移步github仓库代码,代码是建立在阅读了vuex源代码之后写的,所以看完了本文的代码,再去看vuex的代码,相信你一定会一目了然

六. 实现: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; } } }) }

参考资料:

Build a Vuex Module

How does a minimal Vuex implementation look like?

从0开始写一个自己的Vuex

vuex 源码:如何实现一个简单的 vuex

Vue 源码(三) —— Vuex

浅谈Vue.use

Vuex官方文档

作者:殷荣桧
链接:https://juejin.im/post/5c62ea95e51d457ffe60c084

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:用149行代码实现Vuex 80%的核心功能

链接:https://www.javascriptc.com/2660.html

« 用极少的代码实现redux核心部分及其演化历史前后300年
编写高效复杂且可读React组件的 5 个最佳实践»
Flutter 中文教程资源

相关推荐

QR code