vue手写系列 —— mini-vuex

519 阅读2分钟

手写 —— mini-vuex

  • 第1步:使用 Store 类管理数据,使用 _state 【存储数据】,使用 _mutations 来存储数据修改的函数

    • _state_mutationsStore 的私有属性
  • 第2步:把 _state 变量包裹成【响应式数据--》ref / reactive
  • 第4步:定义 install 方法, 用于将 store 提供给 Vue 组件使用【通过 provide / inject 做数据共享】
  • 第5步:在 Store 类中定义 install 方法,用于 app.use(store) 时执行这个函数
  • 第6步:使用单例模式向外暴露唯一 Store 实例对象
  • 第7步:向外暴露 useStorecreateStore

5.png

第1步:定义 Store 类,声明 _state 和 _mutations 私有属性

  • 需要定义 options、_state、_mutations 的类型
import { reactive, UnwrapRef } from "vue";
​
interface Options<S> {
    state: () => S; // 这里只定义了函数类型
    mutations: {
        [key: string]: (state: UnwrapRef<S>, payload?: any) => void;
    }
}
​
interface StateType<S> {
    data: S
}
​
type MutationType<S> = {
  [k in keyof Options<S>["mutations"]]: (state: UnwrapRef<S>, payload?: any) => void;
};
​
​
// 泛型参数 S 用于约束 state 的类型
class Store<S> {
    // _state 是一个响应式数据,
    private _state: UnwrapRef<StateType<S>>;
    private _mutations: MutationType<S>;
    constructor(options: Options<S>) {
        this._state = reactive({
            data: options.state()
        });
        this._mutations = options.mutations;
    }
}

第2步:定义 state getter 属性方法

...
class Store<S extends object> {
    ...
    get state(): UnwrapRef<S> {
        return this._state.data;
    }
}

第3步:定义 commit 方法

...
class Store<S extends object> {
    ...
    commit(type: string, payload?: any) {
        const entry = this._mutations[type];
        entry && entry(this.state, payload);
    }
}

第4步:定义 install 方法用于在 Vue 中注册 store

  • main.ts 入口处 app.use(store) 的时候会执行这个 install 函数
import { App } from "vue";
...
class Store<S> {
    static STORE_KEY = '__store__'; // 用作 project/inject 的属性名
    ...
    install(app: App) {
        // 向所有组件提供 __store__ 属性,值为 Store 实例对象
        app.provide(Store.STORE_KEY, this);
    }
}

第5步:使用单例模式向外暴露唯一的 Store 实例对象

...
class Store<S> {
    static STORE_KEY = "__store__"; // 用作 project/inject 的属性名
    static instance: Store<any> | null = null; // 单例模式
    static getInstance<S>(options: Options<S>): Store<S> {
        if (!Store.instance) Store.instance = new Store<S>(options);
        return Store.instance;
    }
    ...
}
    
// 向外暴露唯一个 Store 对象
export function createStore<S extends object>(options: Options<S>) {
  return Store.getInstance<S>(options);
}

第6步:向外暴露 useStore 方法

import { inject } from "vue";
...
export function useStore<S extends object>(): Store<S> {
  return inject(Store.STORE_KEY)!;
}

第7步:在 store/index.ts 中创建 store

import { createStore } from "./gvuex";
​
const store = createStore<RootState>({
  state() {
    return {
      count: 666,
    };
  },
  mutations: {
    add(state) {
      state.count++;
    },
  },
});
​
export default store;

第8步:在 main.ts 入口文件中注册 store

main.ts

import { createApp } from "vue";
import App from "./App.vue";
​
import store from "./store";
​
createApp(App).use(store).mount("#app");

第9步:在组件中使用 store

<template>
  <div @click="add">{{ count }}</div>
</template>
​
<script setup lang="ts">
import { computed } from "vue";
import { useStore } from "../../store/gvuex2";
const store = useStore<RootState>();
const count = computed(() => store.state.count);
​
function add() {
  store.commit("add");
}
</script>

高度问题.gif