├── README.md └── my-own-vuex ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public └── index.html └── src ├── App.vue ├── main.js ├── myVuex ├── index.js └── store.js └── pageA.vue /README.md: -------------------------------------------------------------------------------- 1 |

项目运行方法: 进入到my-own-vuex目录下,运行npm install 再运行npm run serve就可以了

2 |

用150行代码实现Vuex 80%的功能

3 | 4 |

作者: 殷荣桧@腾讯

5 | 6 | 本文[github仓库代码地址](https://github.com/jackiewillen?tab=repositories),欢迎star,谢谢。 7 | 8 | 如果你对自己用少量代码实现各个框架感兴趣,那下面这些你都可以一看: 9 | 10 | [build-your-own-react](https://github.com/jackiewillen/build-your-own-react) 11 | 12 | [build-your-own-flux](https://github.com/jackiewillen/build-your-own-flux) 13 | 14 | [build-your-own-redux](https://github.com/jackiewillen/build-your-own-redux) 15 | 16 |

目录:

17 | 18 | > 一.完成最简单的通过vuex定义全局变量,在任何一个页面可以通过this.$store.state.count可以直接使用 19 | > 20 | > 二.vuex中的getter方法的实现 21 | > 22 | > 三.mutation和commit方法的实现 23 | > 24 | > 四.actions和dispatch方法的实现 25 | > 26 | > 五.module方法的实现 27 | > 28 | > 六.实现:Vue.use(Vuex) 29 | 30 | 先来看一下用自己实现的的vuex替代真实的vuex的效果,看看能否正常运行,有没有报错: 31 |
32 | 33 | 从运行结果来看,运行正常,没有问题。接下来看看一步一步实现的过程: 34 | 35 |

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

36 | 37 | main.js代码如下: 38 | 39 | let store = new Vuex.Store({ 40 | state: { 41 | count: 0 42 | } 43 | }, Vue); 44 | 45 | new Vue({ 46 | store, 47 | render: h => h(App), 48 | }).$mount('#app') 49 | 50 | store.js的代码如下: 51 | 52 | export class Store { 53 | constructor(options = {}, Vue) { 54 | this.options = options; 55 | Vue.mixin({ beforeCreate: vuexInit }); 56 | } 57 | get state () { 58 | return this.options.state; 59 | } 60 | } 61 | function vuexInit () { 62 | const options = this.$options 63 | if (options.store) { 64 | // 组件内部设定了store,则优先使用组件内部的store 65 | this.$store = typeof options.store === 'function' 66 | ? options.store() 67 | : options.store 68 | } else if (options.parent && options.parent.$store) { 69 | // 组件内部没有设定store,则从根App.vue下继承$store方法 70 | this.$store = options.parent.$store 71 | } 72 | } 73 | 74 | 界面代码如下: 75 | 76 | 84 | 85 | 运行结果: 成功打印出this.$store.state.count的值为0 86 | 87 |

二. vuex中的getter方法的实现

88 | main.js代码如下: 89 | 90 | let store = new Vuex.Store({ 91 | state: { 92 | count: 0 93 | }, 94 | getters: { 95 | getStatePlusOne(state) { 96 | return state.count + 1 97 | } 98 | } 99 | 100 | }, Vue); 101 | 102 | new Vue({ 103 | store, 104 | render: h => h(App), 105 | }).$mount('#app') 106 | 107 | store.js的代码如下: 108 | 109 | export class Store { 110 | constructor(options = {}, Vue) { 111 | this.options = options; 112 | this.getters = {} 113 | Vue.mixin({ beforeCreate: vuexInit }); 114 | forEachValue(options.getters, (getterFn, getterName) => { 115 | registerGetter(this, getterName, getterFn); 116 | }) 117 | } 118 | get state() { 119 | return this.options.state; 120 | } 121 | } 122 | 123 | function registerGetter(store, getterName, getterFn) { 124 | Object.defineProperty(store.getters, getterName, { 125 | get: () => { 126 | return getterFn(store.state) 127 | } 128 | }) 129 | } 130 | 131 | // 将对象中的每一个值放入到传入的函数中作为参数执行 132 | function forEachValue(obj, fn) { 133 | Object.keys(obj).forEach(key => fn(obj[key], key)); 134 | } 135 | 136 | function vuexInit() { 137 | const options = this.$options 138 | if (options.store) { 139 | // 组件内部设定了store,则优先使用组件内部的store 140 | this.$store = typeof options.store === 'function' ? 141 | options.store() : 142 | options.store 143 | } else if (options.parent && options.parent.$store) { 144 | // 组件内部没有设定store,则从根App.vue下继承$store方法 145 | this.$store = options.parent.$store 146 | } 147 | } 148 | 149 | 界面代码如下: 150 | 151 | 159 | 160 | 运行结果: 161 | 成功打印出this.$store.getters.getStatePlusOne的值为1 162 | 163 |

三. mutation和commit方法的实现

164 | main.js代码如下: 165 | 166 | let store = new Vuex.Store({ 167 | state: { 168 | count: 0 169 | }, 170 | mutations: { 171 | incrementFive(state) { 172 | // console.log('初始state', JSON.stringify(state)); 173 | state.count = state.count + 5; 174 | } 175 | }, 176 | getters: { 177 | getStatePlusOne(state) { 178 | return state.count + 1 179 | } 180 | } 181 | 182 | }, Vue); 183 | 184 | store.js的代码如下: 185 | 186 | export class Store { 187 | constructor(options = {}, Vue) { 188 | Vue.mixin({ beforeCreate: vuexInit }) 189 | this.options = options; 190 | this.getters = {}; 191 | this.mutations = {}; 192 | const { commit } = this; 193 | this.commit = (type) => { 194 | return commit.call(this, type); 195 | } 196 | forEachValue(options.getters, (getterFn, getterName) => { 197 | registerGetter(this, getterName, getterFn); 198 | }); 199 | 200 | forEachValue(options.mutations, (mutationFn, mutationName) => { 201 | registerMutation(this, mutationName, mutationFn) 202 | }); 203 | 204 | this._vm = new Vue({ 205 | data: { 206 | state: options.state 207 | } 208 | }); 209 | } 210 | 211 | get state() { 212 | // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式 213 | return this._vm._data.state; 214 | } 215 | commit(type) { 216 | this.mutations[type](); 217 | } 218 | } 219 | 220 | function registerMutation(store, mutationName, mutationFn) { 221 | store.mutations[mutationName] = () => { 222 | mutationFn.call(store, store.state); 223 | } 224 | } 225 | 226 | 227 | function registerGetter(store, getterName, getterFn) { 228 | Object.defineProperty(store.getters, getterName, { 229 | get: () => { 230 | return getterFn(store.state) 231 | } 232 | }) 233 | } 234 | 235 | // 将对象中的每一个值放入到传入的函数中作为参数执行 236 | function forEachValue(obj, fn) { 237 | Object.keys(obj).forEach(key => fn(obj[key], key)); 238 | } 239 | 240 | function vuexInit() { 241 | const options = this.$options 242 | if (options.store) { 243 | // 组件内部设定了store,则优先使用组件内部的store 244 | this.$store = typeof options.store === 'function' ? 245 | options.store() : 246 | options.store 247 | } else if (options.parent && options.parent.$store) { 248 | // 组件内部没有设定store,则从根App.vue下继承$store方法 249 | this.$store = options.parent.$store 250 | } 251 | } 252 | 253 | 界面代码如下: 254 | 255 | 274 | 275 | 运行结果:成功在2秒之后输出count自增5后的结果5 276 | 277 |

四. actions和dispatch方法的实现

278 | main.js代码如下: 279 | 280 | let store = new Vuex.Store({ 281 | state: { 282 | count: 0 283 | }, 284 | actions: { 285 | countPlusSix(context) { 286 | context.commit('plusSix'); 287 | } 288 | }, 289 | mutations: { 290 | incrementFive(state) { 291 | // console.log('初始state', JSON.stringify(state)); 292 | state.count = state.count + 5; 293 | }, 294 | plusSix(state) { 295 | state.count = state.count + 6; 296 | } 297 | }, 298 | getters: { 299 | getStatePlusOne(state) { 300 | return state.count + 1 301 | } 302 | } 303 | 304 | }, Vue); 305 | 306 | store.js的代码如下: 307 | 308 | export class Store { 309 | constructor(options = {}, Vue) { 310 | Vue.mixin({ beforeCreate: vuexInit }) 311 | this.options = options; 312 | this.getters = {}; 313 | this.mutations = {}; 314 | this.actions = {}; 315 | const { dispatch, commit } = this; 316 | this.commit = (type) => { 317 | return commit.call(this, type); 318 | } 319 | this.dispatch = (type) => { 320 | return dispatch.call(this, type); 321 | } 322 | forEachValue(options.actions, (actionFn, actionName) => { 323 | registerAction(this, actionName, actionFn); 324 | }); 325 | 326 | forEachValue(options.getters, (getterFn, getterName) => { 327 | registerGetter(this, getterName, getterFn); 328 | }); 329 | 330 | forEachValue(options.mutations, (mutationFn, mutationName) => { 331 | registerMutation(this, mutationName, mutationFn) 332 | }); 333 | 334 | this._vm = new Vue({ 335 | data: { 336 | state: options.state 337 | } 338 | }); 339 | } 340 | 341 | get state() { 342 | // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式 343 | return this._vm._data.state; 344 | } 345 | commit(type) { 346 | this.mutations[type](); 347 | } 348 | dispatch(type) { 349 | return this.actions[type](); 350 | } 351 | } 352 | 353 | function registerMutation(store, mutationName, mutationFn) { 354 | store.mutations[mutationName] = () => { 355 | mutationFn.call(store, store.state); 356 | } 357 | } 358 | 359 | function registerAction(store, actionName, actionFn) { 360 | store.actions[actionName] = () => { 361 | actionFn.call(store, store) 362 | } 363 | } 364 | 365 | function registerGetter(store, getterName, getterFn) { 366 | Object.defineProperty(store.getters, getterName, { 367 | get: () => { 368 | return getterFn(store.state) 369 | } 370 | }) 371 | } 372 | 373 | // 将对象中的每一个值放入到传入的函数中作为参数执行 374 | function forEachValue(obj, fn) { 375 | Object.keys(obj).forEach(key => fn(obj[key], key)); 376 | } 377 | 378 | function vuexInit() { 379 | const options = this.$options 380 | if (options.store) { 381 | // 组件内部设定了store,则优先使用组件内部的store 382 | this.$store = typeof options.store === 'function' ? 383 | options.store() : 384 | options.store 385 | } else if (options.parent && options.parent.$store) { 386 | // 组件内部没有设定store,则从根App.vue下继承$store方法 387 | this.$store = options.parent.$store 388 | } 389 | } 390 | 391 | 界面代码如下: 392 | 393 | export default { 394 | name: 'app', 395 | created() { 396 | console.log('打印出this.$store.getters.getStatePlusOne的结果',this.$store.getters.getStatePlusOne); 397 | }, 398 | mounted() { 399 | setTimeout(() => { 400 | this.$store.commit('incrementFive'); 401 | console.log('store state自增5后的结果', this.$store.state.count); 402 | }, 2000); 403 | setTimeout(() => { 404 | this.$store.dispatch('countPlusSix'); 405 | console.log('store dispatch自增6后的结果', this.$store.state.count); 406 | }, 3000); 407 | }, 408 | computed: { 409 | count() { 410 | return this.$store.state.count; 411 | } 412 | } 413 | } 414 | 运行结果: 成功在3秒之后dipatch自增6输出11 415 | 416 |

五. module方法的实现

417 | main.js代码如下: 418 | 419 | const pageA = { 420 | state: { 421 | count: 100 422 | }, 423 | mutations: { 424 | incrementA(state) { 425 | state.count++; 426 | } 427 | }, 428 | actions: { 429 | incrementAAction(context) { 430 | context.commit('incrementA'); 431 | } 432 | } 433 | } 434 | 435 | let store = new Vuex.Store({ 436 | modules: { 437 | a: pageA 438 | }, 439 | state: { 440 | count: 0 441 | }, 442 | actions: { 443 | countPlusSix(context) { 444 | context.commit('plusSix'); 445 | } 446 | }, 447 | mutations: { 448 | incrementFive(state) { 449 | // console.log('初始state', JSON.stringify(state)); 450 | state.count = state.count + 5; 451 | }, 452 | plusSix(state) { 453 | state.count = state.count + 6; 454 | } 455 | }, 456 | getters: { 457 | getStatePlusOne(state) { 458 | return state.count + 1 459 | } 460 | } 461 | 462 | }, Vue); 463 | 464 | store.js的代码如下: 465 | 466 | let _Vue; 467 | export class Store { 468 | constructor(options = {}, Vue) { 469 | _Vue = Vue 470 | Vue.mixin({ beforeCreate: vuexInit }) 471 | this.getters = {}; 472 | this._mutations = {}; // 在私有属性前加_ 473 | this._wrappedGetters = {}; 474 | this._actions = {}; 475 | this._modules = new ModuleCollection(options) 476 | const { dispatch, commit } = this; 477 | this.commit = (type) => { 478 | return commit.call(this, type); 479 | } 480 | this.dispatch = (type) => { 481 | return dispatch.call(this, type); 482 | } 483 | const state = options.state; 484 | const path = []; // 初始路径给根路径为空 485 | installModule(this, state, path, this._modules.root); 486 | this._vm = new Vue({ 487 | data: { 488 | state: state 489 | } 490 | }); 491 | } 492 | 493 | get state() { 494 | // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式 495 | return this._vm._data.state; 496 | } 497 | commit(type) { 498 | this._mutations[type].forEach(handler => handler()); 499 | } 500 | dispatch(type) { 501 | return this._actions[type][0](); 502 | } 503 | } 504 | 505 | class ModuleCollection { 506 | constructor(rawRootModule) { 507 | this.register([], rawRootModule) 508 | } 509 | register(path, rawModule) { 510 | const newModule = { 511 | _children: {}, 512 | _rawModule: rawModule, 513 | state: rawModule.state 514 | } 515 | if (path.length === 0) { 516 | this.root = newModule; 517 | } else { 518 | const parent = path.slice(0, -1).reduce((module, key) => { 519 | return module._children(key); 520 | }, this.root); 521 | parent._children[path[path.length - 1]] = newModule; 522 | } 523 | if (rawModule.modules) { 524 | forEachValue(rawModule.modules, (rawChildModule, key) => { 525 | this.register(path.concat(key), rawChildModule); 526 | }) 527 | } 528 | } 529 | } 530 | 531 | function installModule(store, rootState, path, module) { 532 | if (path.length > 0) { 533 | const parentState = rootState; 534 | const moduleName = path[path.length - 1]; 535 | _Vue.set(parentState, moduleName, module.state) 536 | } 537 | const context = { 538 | dispatch: store.dispatch, 539 | commit: store.commit, 540 | } 541 | const local = Object.defineProperties(context, { 542 | getters: { 543 | get: () => store.getters 544 | }, 545 | state: { 546 | get: () => { 547 | let state = store.state; 548 | return path.length ? path.reduce((state, key) => state[key], state) : state 549 | } 550 | } 551 | }) 552 | if (module._rawModule.actions) { 553 | forEachValue(module._rawModule.actions, (actionFn, actionName) => { 554 | registerAction(store, actionName, actionFn, local); 555 | }); 556 | } 557 | if (module._rawModule.getters) { 558 | forEachValue(module._rawModule.getters, (getterFn, getterName) => { 559 | registerGetter(store, getterName, getterFn, local); 560 | }); 561 | } 562 | if (module._rawModule.mutations) { 563 | forEachValue(module._rawModule.mutations, (mutationFn, mutationName) => { 564 | registerMutation(store, mutationName, mutationFn, local) 565 | }); 566 | } 567 | forEachValue(module._children, (child, key) => { 568 | installModule(store, rootState, path.concat(key), child) 569 | }) 570 | 571 | } 572 | 573 | function registerMutation(store, mutationName, mutationFn, local) { 574 | const entry = store._mutations[mutationName] || (store._mutations[mutationName] = []); 575 | entry.push(() => { 576 | mutationFn.call(store, local.state); 577 | }); 578 | } 579 | 580 | function registerAction(store, actionName, actionFn, local) { 581 | const entry = store._actions[actionName] || (store._actions[actionName] = []) 582 | entry.push(() => { 583 | return actionFn.call(store, { 584 | commit: local.commit, 585 | state: local.state, 586 | }) 587 | }); 588 | } 589 | 590 | function registerGetter(store, getterName, getterFn, local) { 591 | Object.defineProperty(store.getters, getterName, { 592 | get: () => { 593 | return getterFn( 594 | local.state, 595 | local.getters, 596 | store.state 597 | ) 598 | } 599 | }) 600 | } 601 | 602 | // 将对象中的每一个值放入到传入的函数中作为参数执行 603 | function forEachValue(obj, fn) { 604 | Object.keys(obj).forEach(key => fn(obj[key], key)); 605 | } 606 | 607 | function vuexInit() { 608 | const options = this.$options 609 | if (options.store) { 610 | // 组件内部设定了store,则优先使用组件内部的store 611 | this.$store = typeof options.store === 'function' ? 612 | options.store() : 613 | options.store 614 | } else if (options.parent && options.parent.$store) { 615 | // 组件内部没有设定store,则从根App.vue下继承$store方法 616 | this.$store = options.parent.$store 617 | } 618 | } 619 | 620 | 主界面代码如下: 621 | 622 | 631 | 632 | 663 | 664 | pageA页面如下: 665 | 666 | 671 | 672 | 682 | 683 | 运行结果: 在5秒后A页面触发incrementAAction,主界面中的countA变化为101,成功 684 | 685 | 自此:基本用了150行左右的代码实现了vuex 80%左右的功能了,其中还有namespace等不能够使用,其他基本都和源代码语法相同,如果你有兴趣仔细再看看,可以移步[github仓库代码](https://github.com/jackiewillen?tab=repositories),代码是建立在阅读了vuex源代码之后写的,所以看完了本文的代码,再去看vuex的代码,相信你一定会一目了然 686 | 687 |

六. 实现:Vue.use(Vuex)

688 | 最后为了和vuex源代码做到最相似,同样使用Vue.use(Vuex),使用如下的代码进行实现: 689 | 690 | export function install(_Vue) { 691 | Vue = _Vue; 692 | Vue.mixin({ 693 | beforeCreate: function vuexInit() { 694 | const options = this.$options; 695 | if (options.store) { 696 | this.$store = options.store; 697 | } else if (options.parent && options.parent.$store) { 698 | this.$store = options.parent.$store; 699 | } 700 | } 701 | }) 702 | } 703 | 704 | 参考资料: 705 | > 706 | >[Build a Vuex Module ](https://serversideup.net/build-vuex-module/) 707 | > 708 | >[How does a minimal Vuex implementation look like?](https://medium.com/@sadickjunior/how-does-a-minimal-vuex-implementation-looks-like-find-out-c2c2e13619cb) 709 | > 710 | >[从0开始写一个自己的Vuex](https://segmentfault.com/a/1190000010888395) 711 | > 712 | >[vuex 源码:如何实现一个简单的 vuex](https://juejin.im/post/5a7a935851882524713dcd05) 713 | > 714 | >[Vue 源码(三) —— Vuex](https://zhuanlan.zhihu.com/p/48516116) 715 | > 716 | >[浅谈Vue.use](https://segmentfault.com/a/1190000012296163) 717 | > 718 | >[Vuex官方文档](https://vuex.vuejs.org/zh/guide/) 719 | > 720 | >[vuex Github仓库](https://github.com/vuejs/vuex) 721 | 722 | -------------------------------------------------------------------------------- /my-own-vuex/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /my-own-vuex/README.md: -------------------------------------------------------------------------------- 1 | # my-own-vuex 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /my-own-vuex/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /my-own-vuex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-own-vuex", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "vue": "^2.5.21", 12 | "vuex": "^3.1.0" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "^3.3.0", 16 | "@vue/cli-plugin-eslint": "^3.3.0", 17 | "@vue/cli-service": "^3.3.0", 18 | "babel-eslint": "^10.0.1", 19 | "eslint": "^5.8.0", 20 | "eslint-plugin-vue": "^5.0.0", 21 | "vue-template-compiler": "^2.5.21" 22 | }, 23 | "eslintConfig": { 24 | "root": true, 25 | "env": { 26 | "node": true 27 | }, 28 | "extends": [ 29 | "plugin:vue/essential", 30 | "eslint:recommended" 31 | ], 32 | "rules": { 33 | "no-console": "off" 34 | }, 35 | "parserOptions": { 36 | "parser": "babel-eslint" 37 | } 38 | }, 39 | "postcss": { 40 | "plugins": { 41 | "autoprefixer": {} 42 | } 43 | }, 44 | "browserslist": [ 45 | "> 1%", 46 | "last 2 versions", 47 | "not ie <= 8" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /my-own-vuex/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | my-own-vuex 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /my-own-vuex/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | -------------------------------------------------------------------------------- /my-own-vuex/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | // import Vuex from './myVuex/index' 4 | import Vuex from 'vuex' 5 | 6 | Vue.use(Vuex); 7 | 8 | const pageA = { 9 | state: { 10 | count: 100 11 | }, 12 | mutations: { 13 | incrementA(state) { 14 | state.count++; 15 | } 16 | }, 17 | actions: { 18 | incrementAAction(context) { 19 | context.commit('incrementA'); 20 | } 21 | } 22 | } 23 | 24 | let store = new Vuex.Store({ 25 | modules: { 26 | a: pageA 27 | }, 28 | state: { 29 | count: 0 30 | }, 31 | actions: { 32 | countPlusSix(context) { 33 | context.commit('plusSix'); 34 | } 35 | }, 36 | mutations: { 37 | incrementFive(state) { 38 | // console.log('初始state', JSON.stringify(state)); 39 | state.count = state.count + 5; 40 | }, 41 | plusSix(state) { 42 | state.count = state.count + 6; 43 | } 44 | }, 45 | getters: { 46 | getStatePlusOne(state) { 47 | return state.count + 1 48 | } 49 | } 50 | 51 | }); 52 | 53 | new Vue({ 54 | store, 55 | render: h => h(App), 56 | }).$mount('#app') 57 | 58 | 59 | 60 | Vue.config.productionTip = false; -------------------------------------------------------------------------------- /my-own-vuex/src/myVuex/index.js: -------------------------------------------------------------------------------- 1 | import { Store, install } from './store'; 2 | export default { 3 | Store, 4 | install 5 | } -------------------------------------------------------------------------------- /my-own-vuex/src/myVuex/store.js: -------------------------------------------------------------------------------- 1 | let Vue; 2 | export class Store { 3 | constructor(options = {}) { 4 | this.getters = {}; 5 | this._mutations = {}; // 在私有属性前加_ 6 | this._wrappedGetters = {}; 7 | this._actions = {}; 8 | this._modules = new ModuleCollection(options) 9 | const { dispatch, commit } = this; 10 | this.commit = (type) => { 11 | return commit.call(this, type); 12 | } 13 | this.dispatch = (type) => { 14 | return dispatch.call(this, type); 15 | } 16 | const state = options.state; 17 | const path = []; // 初始路径给根路径为空 18 | this._vm = new Vue({ 19 | data: { 20 | state: state 21 | } 22 | }); 23 | installModule(this, state, path, this._modules.root); 24 | 25 | } 26 | 27 | get state() { 28 | // return this.options.state; // 无法完成页面中的双向绑定,所以改用this._vm的形式 29 | return this._vm._data.state; 30 | } 31 | commit(type) { 32 | this._mutations[type].forEach(handler => handler()); 33 | } 34 | dispatch(type) { 35 | return this._actions[type][0](); 36 | } 37 | } 38 | 39 | class ModuleCollection { 40 | constructor(rawRootModule) { 41 | this.register([], rawRootModule) 42 | } 43 | register(path, rawModule) { 44 | const newModule = { 45 | _children: {}, 46 | _rawModule: rawModule, 47 | state: rawModule.state 48 | } 49 | if (path.length === 0) { 50 | this.root = newModule; 51 | } else { 52 | const parent = path.slice(0, -1).reduce((module, key) => { 53 | return module._children(key); 54 | }, this.root); 55 | parent._children[path[path.length - 1]] = newModule; 56 | } 57 | if (rawModule.modules) { 58 | forEachValue(rawModule.modules, (rawChildModule, key) => { 59 | this.register(path.concat(key), rawChildModule); 60 | }) 61 | } 62 | } 63 | } 64 | 65 | function installModule(store, rootState, path, module) { 66 | if (path.length > 0) { 67 | const parentState = rootState; 68 | const moduleName = path[path.length - 1]; 69 | Vue.set(parentState, moduleName, module.state) 70 | } 71 | const context = { 72 | dispatch: store.dispatch, 73 | commit: store.commit, 74 | } 75 | const local = Object.defineProperties(context, { 76 | getters: { 77 | get: () => store.getters 78 | }, 79 | state: { 80 | get: () => { 81 | let state = store.state; 82 | return path.length ? path.reduce((state, key) => state[key], state) : state 83 | } 84 | } 85 | }) 86 | if (module._rawModule.actions) { 87 | forEachValue(module._rawModule.actions, (actionFn, actionName) => { 88 | registerAction(store, actionName, actionFn, local); 89 | }); 90 | } 91 | if (module._rawModule.getters) { 92 | forEachValue(module._rawModule.getters, (getterFn, getterName) => { 93 | registerGetter(store, getterName, getterFn, local); 94 | }); 95 | } 96 | if (module._rawModule.mutations) { 97 | forEachValue(module._rawModule.mutations, (mutationFn, mutationName) => { 98 | registerMutation(store, mutationName, mutationFn, local) 99 | }); 100 | } 101 | forEachValue(module._children, (child, key) => { 102 | installModule(store, rootState, path.concat(key), child) 103 | }) 104 | 105 | } 106 | 107 | function registerMutation(store, mutationName, mutationFn, local) { 108 | const entry = store._mutations[mutationName] || (store._mutations[mutationName] = []); 109 | entry.push(() => { 110 | mutationFn.call(store, local.state); 111 | }); 112 | } 113 | 114 | function registerAction(store, actionName, actionFn, local) { 115 | const entry = store._actions[actionName] || (store._actions[actionName] = []) 116 | entry.push(() => { 117 | return actionFn.call(store, { 118 | commit: local.commit, 119 | state: local.state, 120 | }) 121 | }); 122 | } 123 | 124 | function registerGetter(store, getterName, getterFn, local) { 125 | Object.defineProperty(store.getters, getterName, { 126 | get: () => { 127 | return getterFn( 128 | local.state, 129 | local.getters, 130 | store.state 131 | ) 132 | } 133 | }) 134 | } 135 | 136 | // 将对象中的每一个值放入到传入的函数中作为参数执行 137 | function forEachValue(obj, fn) { 138 | Object.keys(obj).forEach(key => fn(obj[key], key)); 139 | } 140 | 141 | export function install(_Vue) { 142 | Vue = _Vue; 143 | Vue.mixin({ 144 | beforeCreate: function vuexInit() { 145 | const options = this.$options; 146 | if (options.store) { 147 | this.$store = options.store; 148 | } else if (options.parent && options.parent.$store) { 149 | this.$store = options.parent.$store; 150 | } 151 | } 152 | }) 153 | } -------------------------------------------------------------------------------- /my-own-vuex/src/pageA.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | --------------------------------------------------------------------------------