likes
comments
collection
share

用简短的60行代码实现mini-vuex

作者站长头像
站长
· 阅读数 8

前言

60 行实现了部分功能,并没有全部实现,但是代码非常简单

功能包括:

  • State
  • Mutations
  • dispatch 【注:vuex中是操作异步,我这里并没有这样写,目的是实现功能】

如图:

用简短的60行代码实现mini-vuex

mini-vuex 工作原理

  1. 初始化 Store:首先,在 Vue 应用中初始化 Vuex 的 Store。这个 Store 包含了应用的所有状态、操作。
  2. 组件中访问状态:在 Vue 组件中,通过 this.$store 访问到 Vuex 的 Store 对象。这使得组件能够直接从 Store 中读取状态。
  3. 提交 Mutation 修改状态:当组件需要修改状态时,它会通过提交 Mutation 的方式来请求状态的修改。Mutation 是同步操作,它们包含了状态的修改逻辑。
  4. 状态变更:当一个 Mutation 被提交后,它会修改 Store 中的状态。这个状态的修改是响应式的,所以任何依赖于这个状态的组件都会立即更新。

实现mini-vuex

清楚了工作原理这些我们现在分步来实现 mini-vuex

一、初始化 Store

想要使用 Vue.use() 那就必须得实现一个 install 那么我们来实现这一段,代码如下:

let Vue

export Class Store {
}

function install(_Vue) {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate: vuexInit,
  });
  function vuexInit() {
    const options = this.$options;
    if (options.store) {
      this.$store =
        typeof options.store === "function" ? options.store() : options.store;
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store;
    }
  }
}

export default {
  Store,
  install,
};

vuexInit 这个方法的主要目的是在 Vue 组件中初始化 Vuex store。

在 Vue 组件的选项对象中,如果存在 store 属性,那么这个方法会检查 store 属性的类型。如果 store 是一个函数,那么它会调用这个函数并将返回的结果赋值给 this.$store。如果 store 不是一个函数,那么它会直接将 store 的值赋给 this.$store

如果在 Vue 组件的选项对象中没有 store 属性,但是存在 parent 属性,并且 parent 对象有 $store 属性,那么这个方法会将 parent$store 属性的值赋给 this.$store。使得在组件中可以通过 this.$store 访问到 Vuex store。

二、组件中访问状态

简单理解就是 我想通过 this.$store.state.xxx 访问到某个具体属性,上述代码中,我们是并没有对 Store 这个类进行赋值的,那么接下来,我们就实现这个功能

let Vue;

export class Store {
  constructor({ state = {}, mutations = {} } = {}) {
    // 使用一个 Vue 实例来存储状态树
    this._vm = new Vue({
      data: state,
    });
  }
  
  get state() {
    return this._vm._data;
  }
}

我们在外部存储了一份 Vue ,在我们 install 的时候 Vue = _Vue 完成存储,所以我们在 constructor 中定义了一个存储状态树将 state 保存在Vue上,实现了响应式,通过我们的 get关键字 ,当你访问 this.state 会执行这个 get state() 返回 this._vm._data,即 Vue 实例的数据对象。这样,通过这个 getter 函数,你可以直接访问 Vue 实例的数据对象,而无需直接访问 Vue 实例的 _data 属性

三、提交 Mutation 修改状态 和 状态变更

我们实现 this.$store.dispatch("事件名","值") ,通过他来改变和更新我们的状态

let Vue;

export class Store {
  constructor({ state = {}, mutations = {} } = {}) {
    // 将 dispatch 绑定到自身
    const dispatch = this.dispatch;
    this.dispatch = (...args) => {
      dispatch.apply(this, args);
    };
    // 使用一个 Vue 实例来存储状态树
    this._vm = new Vue({
      data: state,
    });

    this._mutations = Object.create(null); // 用于存储可调用的 mutation 函数
    this._setupMutations(mutations); // 初始化 mutations
  }
  
  get state() {
    return this._vm._data;
  }

  _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) {
      throw new Error(`[vuex] Unknown action type: ${type}`);
    }
    mutation(state, ...payload);
  }
}

首先将当前对象的 dispatch 方法引用赋值给 dispatch 变量。然后,将 this.dispatch 重新定义为一个新的函数,这个新的函数接收任意数量的参数(由 ...args 表示),并调用 dispatch 函数,同时保证 dispatch 函数内部的 this 上下文仍然指向当前对象。

然后实现我们的 dispatch ,我们将取出对应的 mutation 函数,将 state 传入 mutation 于是 我们可以看这一段

const mutations = {
  ADD_TODO: (state, todo) => {
    state.todos.push(todo);
  },
};

如上面代码,我们会在这里 (state,payload)=>{} 访问到值并调用,state 指向的就是响应式,后面就是你要传递的值

小结

到这里我们的 mini-vuex 就完成了,感兴趣的朋友可以去实现一个 todo 案例,那么我们总结一下几点,可能面试能说的:

  • Vuex 通过混入的方式实现挂载 this.$store
  • Vuex 内部保存了一份Vue,通过 new Vue 实现数据响应式 所以变更状态,才可以更改视图