从0实现简易版的vuex

简介: 从0实现简易版的vuex

从0实现简易版的vuex


想要更好的使用一个插件,可以尝试理解其实现的方式。

当然,了解一个优秀的插件,本身也会增强自己的能力。

本文,努力从零开始实现一个简易版的vuex,期间会用到很多编程思想,希望自己越来越灵活使用。

TL;DR

  • state是响应式的,巧用Vue
  • gettersObject.defineProperty实现和state的紧密相关
  • mutations是定义commit方法,commit的this需要固定为store实例
  • actions是定义dispatch,逻辑神似mutations
  • 翻到文末可以直接看vuex.js的简易版代码

vuex 的初版样子

先可以用vue create xx创建一个项目,不带vuex的。

先看看,如果有vuex插件的main.js

网络异常,图片无法展示
|

!!!特别注意

  • {state:{},mutations:...},是用户传的参数
  • store 虽然可以this.$store.state,但这个 state 不完全是用户传的 state,而是处理过的 state,这两有本质区别
  • 同样,用户传过来的其他属性,也会做处理,这样才有后期的this.$store.getters.xx等等
  • 换言之,store就是对用户传的参数做各种处理,以方便用户操作她的数据。

从这推理出vuex,应该具有的特征:

  • Vue.use表明,vuex 肯定有install方法
  • new Vuex.Store表明,vuex 导出对象里,有个Store的类
  • 每个组件内部都可以this.$store表明,需要注入$store

如果对插件一脸懵的话,可以简单看下vue 插件的入门说明

第一版vuex.js就出来了:

网络异常,图片无法展示
|

但这样,$storestore实例并没有挂钩,此时可以借助Vue.mixins的beforeCreate钩子拿到当前的 Vue 实例,从而拿到实例的$options

export default {
  install(Vue) {
    Vue.mixin({
      beforeCreate() {
        // 这里的this是vue的实例,其参数store就是store实例
        (!Vue.prototype.$store) && (Vue.prototype.$store = this.$options.store;)
      }
    });
  },
  Store
};

改进:不要轻易在原型上面添加属性,应该只在根实例有store的时候才设置$store,子实例会拿到根实例的$store

网络异常,图片无法展示
|

github源码 切换到c1分支

处理用户传的 state

store 实例的state可以出现在视图里,值变化的时候,视图也一并更新。 所以,state是被劫持的,这里投机取巧的用下Vue

// vuex.js
class Store {
  constructor(options) {
    this.options = options;
    this.state = new Vue({ data: options.state });
  }
}
<!-- App.vue -->
<div id="app">
  {{ $store.state.a }}
  <button @click="$store.state.a++">
    增加
  </button>
</div>

github源码 切换到c2分支

!!!因为state是用Vue进行响应式,所有vuex重度依赖vue,不能脱离vue使用

处理用户传的 getters

  • 用户传的getters是一个函数集合
  • 但是实际使用中,属性值是函数的返回值
  • 属性依旧是劫持的,这边因为是函数,所以不能再投机取巧了
// vuex.js
constructor(options) {
    this.options = options;
    this.state = new Vue({ data: options.state });
    if (options.getters) {
      this.getters = {};
      Object.keys(options.getters).forEach(key => {
        //   这里必须是属性劫持
        Object.defineProperty(this.getters, key, {
          get: () => {
            return options.getters[key](this.state);
          }
        });
      });
    }
  }
// main.js
state: { a: 1, b: 2 },
getters: { a1(state) { return state.a + 1; } }
<!-- app.vue -->
<div id="app">
  {{ $store.state.a }} {{ $store.getters.a1 }}
  <button @click="$store.state.a++">
    增加
  </button>
</div>

网络异常,图片无法展示
|

github源码 切换到c3分支

处理 mutations

mutations,传的参数是一个函数集合的对象,使用的时候commit('函数名',payload)

代码翻译:

mutations:{
  addA(state,payload){state.a+=payload}
}
// 使用的时候
this.$store.commit('addA',2)

由此推理出,vuex 其实写了一个commit方法。这个就很简单了,直接溜上来。

// vuex.js
class Store {
  constructor(options) {
    //  ...
    if (options.mutations) {
      this.mutations = { ...options.mutations };
    }
  }
  commit(mutationName, ...payload) {
    console.log(mutationName, ...payload);
    this.mutations[mutationName](this.state, ...payload);
  }
}

网络异常,图片无法展示
|

// <button @click="$store.commit('addA', 2)"> 增加 </button>
const store = new Vuex.Store({
  state: { a: 1, b: 2 },
  getters: {
    a1(state) {
      return state.a + 1;
    },
  },
  mutations: {
    addA(state, num) {
      state.a += num;
    },
  },
});

github源码 切换到c4分支

处理 actions

actionsmutations是很相似的。

actions:{
  // 注意!!!,这里的第一个参数是store实例
  addA({commit},payload){setTimeout(()=>{commit('addA',payload)},1000)}
}
// 使用的时候
this.$store.dispatch('addA',100)

这下更容易了,直接copy

commit(mutationName, ...payload) {
    this.mutations[mutationName](this.state, ...payload);
  }
  dispatch(actionName, ...payload) {
    // 注意这里是this,不是this.state
    this.actions[actionName](this, ...payload);
  }

网络异常,图片无法展示
|

// <button @click="$store.dispatch('addA', 2)"> 1s后增加100 </button>
const store = new Vuex.Store({
  // ...
  actions: {
    addA(store, num) {
      setTimeout(() => {
        store.commit("addA", num);
      }, 1000);
    }
  },
});

github源码 切换到c5分支

优化

  • commit做处理的时候,最好用下切片思维,这样方便修改逻辑
  • commit里面的this,最好固定执行store实例,因为这样在action那边的时候,可以直接解构赋值
  • action也一样
// vuex.js
constructor(options){
  // ...
    if (options.mutations) {
      this.mutations = {};
      Object.keys(options.mutations).forEach(mutationName => {
        // 切片思维,这里上下都可以加逻辑
        this.mutations[mutationName] = (...payload) => {
          options.mutations[mutationName](...payload);
        };
      });
    }
}
// 将this始终执行store实例
commit = (mutationName, ...payload) => {
  this.mutations[mutationName](this.state, ...payload);
}; 

action操作一样,不在赘述代码。

actions: {
  addA({commit}, num) {
    // 这里可以解构了!!!
    setTimeout(() => {
      commit("addA", num);
    }, 1000);
  }
},

github源码 切换到c6分支。

还有模块空间的内容,考虑到篇幅较长,就不在本文继续了。

附注:vuex.js的所有代码

let Vue;
class Store {
  constructor(options) {
    this.options = options;
    this.state = new Vue({ data: options.state });
    if (options.getters) {
      this.getters = {};
      Object.keys(options.getters).forEach(key => {
        Object.defineProperty(this.getters, key, {
          get: () => {
            return options.getters[key](this.state);
          }
        });
      });
    }
    if (options.mutations) {
      this.mutations = {};
      Object.keys(options.mutations).forEach(mutationName => {
        this.mutations[mutationName] = (...payload) => {
          options.mutations[mutationName](...payload);
        };
      });
    }
    if (options.actions) {
      this.actions = {};
      Object.keys(options.actions).forEach(actionName => {
        this.actions[actionName] = (...payload) => {
          options.actions[actionName](...payload);
        };
      });
    }
  }
  commit = (mutationName, ...payload) => {
    this.mutations[mutationName](this.state, ...payload);
  };
  dispatch = (actionName, ...payload) => {
    this.actions[actionName](this, ...payload);
  };
}
export default {
  install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
      beforeCreate() {
        // 这里的this是vue的实例,其参数store就是store实例
        const hasStore = this.$options.store;
        // 根实例的store
        hasStore
          ? (this.$store = this.$options.store)
          : this.$parent && (this.$store = this.$parent.$store);
      }
    });
  },
  Store
};


目录
相关文章
|
2月前
|
存储 缓存 JavaScript
深入了解 Pinia:现代 Vue 应用的状态管理利器
深入了解 Pinia:现代 Vue 应用的状态管理利器
深入了解 Pinia:现代 Vue 应用的状态管理利器
|
12月前
|
存储 JavaScript API
Vue(第十五课)Pinia组件库的基本知识(一)
Vue(第十五课)Pinia组件库的基本知识(一)
129 0
|
JavaScript API
浅尝Vue最新状态管理工具Pinia(实战使用Pinia管理登录状态)
pinia是vue新的状态管理工具,也称作vuex5,本文讲解Pinia的使用方法
857 0
浅尝Vue最新状态管理工具Pinia(实战使用Pinia管理登录状态)
|
15天前
|
存储 JavaScript API
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比
24 2
|
2月前
|
存储 前端开发 JavaScript
vuex项目实例
vuex项目实例
vuex项目实例
|
2月前
(学习笔记)抛弃 Vuex,使用 Pinia
(学习笔记)抛弃 Vuex,使用 Pinia
39 1
|
7月前
|
JavaScript 数据管理
【Vue2.0学习】—Vuex工作原理图(二十五)
【Vue2.0学习】—Vuex工作原理图(二十五)
|
9月前
|
存储 JavaScript 前端开发
“高级Vue状态管理 - Vuex的魅力与应用“
“高级Vue状态管理 - Vuex的魅力与应用“
97 2
|
8月前
|
存储 资源调度 JavaScript
vuex是什么?如何使用?使用他的功能场景?
vuex是什么?如何使用?使用他的功能场景?
36 0
|
10月前
|
JavaScript
Vue2向Vue3过度Vuex状态管理工具快速入门
Vue2向Vue3过度Vuex状态管理工具快速入门
73 0