Skip to content

Pinia 核心原理

js
import { effectScope, ref } from "vue";
import { piniaSymbol } from "./global";

export function createPinia() {
    const store = new Map(); // 存放所有的store
    const scope = effectScope(true); // 创建作用域统一管理响应式
    const state = scope.run(() => ref({})); // 管理所有的state

    const pinia = {
        install, // vue 插件注册
        store,
        scope,
        state, // pinia 需要直接维护一份 state 便于使用
    };
    return pinia;
}

function install(app) {
    app.provide(piniaSymbol, this);
}

js
import {
    inject,
    isReactive,
    reactive,
    effectScope,
    isRef,
    computed,
    ref,
} from "vue";
import { normalization } from "./utils";
import { piniaSymbol } from "./global";
import { isFunction, isComputed } from "./utils";

/**
 * 参数的情况有三种:
 * 1. options: defineStore({id, state, ...})
 * 2. id + options: defineStore(id, {state, ...})
 * 3. id + setup: defineStore(id, () => {})
 */
export function defineStore(...args) {
    const { id, options, setup } = normalization(args);

    const isSetup = isFunction(setup);

    function useStore() {
        const pinia = inject(piniaSymbol);
        // 判断是否以及存在 store
        if (!pinia.store.get(id)) {
            // 没有 store 就创建
            // 判断是 setup 还是 options
            if (isSetup) {
                createSetupStore(pinia, id, setup); // setup 方式创建store
            } else {
                createOptionStore(pinia, id, options); // options方式创建store
            }
        }
        // 返回 id 对应的 store
        return pinia.store.get(id);
    }
    return useStore;
}

/**
 * 创建setup方式的store
 * @param {*} pinia pinia实例
 * @param {*} id store 的id
 * @param {*} setup setup函数
 */
function createSetupStore(pinia, id, setup) {
    const setupStore = setup();

    // 需要对 state 进行处理
    const result = pinia.scope.run(() => {
        let storeScope = effectScope(true); // 创建子作用域,未来可以单独调用 stop 方法
        return storeScope.run(() => compileSetup(pinia, id, setupStore));
    });

    const store = reactive({});
    // 合并store
    Object.assign(store, result); // 使用Object.assign 将result的属性合并到store,保持store的响应式
    pinia.store.set(id, store); // 存储store
    return store; // 可选
}

/**
 * 对state进行额外编译
 * 因为pinia需要直接维护一份state便于使用
 * state的形式为 {
 *   optionTodoList: {
 *      todoList: ref([])
 *   },
 *   setupTodoList: {
 *      todoList: ref([])
 *   }
 * }
 */
function compileSetup(pinia, id, setupStore) {
    // 判断 pinia.state 中是否已有当前 id 对应的仓库的 state(pinia.state 是 ref)
    if (!pinia.state.value[id]) {
        // 如果没有则创建
        pinia.state.value[id] = {};

        for (let key in setupStore) {
            const el = setupStore[key];
            // 判断是否是state
            // 由于computed也是ref因此需要排除
            if ((isRef(el) && !isComputed(el)) || isReactive(el)) {
                // 说明是state
                pinia.state.value[id][key] = el;
            }
        }
    }

    return {
        ...setupStore,
    };
}

/**
 * 创建 options 方式的 store
 * @param {*} pinia pinia实例
 * @param {*} id store的id
 * @param {*} options 选项
 */
function createOptionStore(pinia, id, options) {
    const store = reactive({});
    // 作用域处理
    const result = pinia.scope.run(() => {
        let storeScope = effectScope();
        return storeScope.run(() => {
            return compileOptions(pinia, id, options, store);
        });
    });

    Object.assign(store, result);
    pinia.store.set(id, store);
    return store;
}

function compileOptions(pinia, id, options, store) {
    const { state, getters, actions } = options;
    // state: 是个函数,需要将返回的对象中的每个数据利用 ref 包装成响应式; 同时在 pinia.state 中处理
    // getters: 将每个 getters 处理成一个 computed; this 处理
    // actions:  this 处理
    const storeState = createStoreState(pinia, id, state);
    const storeGetters = createStoreGetters(store, getters);
    const storeActions = createStoreActions(store, actions);

    return {
        ...storeState,
        ...storeGetters,
        ...storeActions,
    };
}

function createStoreState(pinia, id, state) {
    /**
     *  state: () => ({
            todoList: [
                {
                    id: 1,
                    content: "学习vue3",
                    completed: false,
                },
            ],
        })
     */
    // 1. 修改 state 为响应式
    const storeState = state ? state() : {};
    for (let stateName in storeState) {
        storeState[stateName] = ref(storeState[stateName]);
    }
    // 2. 在 pinia.state 中保存
    if (!pinia.state.value[id]) {
        pinia.state.value[id] = storeState; // 就算没有 state 也要保存空对象
    }

    return storeState;
}

function createStoreGetters(store, getters) {
    /**
     *  getters: {
            count() {
                return this.todoList.length;
            },
        },
     */
    // 1. 将 getters 的每一项变成一个 computed, computed(() => count.call(store))
    const storeGetters = {};
    if (!getters) return storeGetters; // 如果 getters 为空或未定义,直接返回空对象

    for (let getterName in getters) {
        storeGetters[getterName] = computed(() =>
            getters[getterName].call(store)
        );
    }

    return storeGetters;
}

function createStoreActions(store, actions) {
    const storeActions = {};
    if (!actions) return storeActions;
    for (let actionName in actions) {
        // storeActions[actionName] = actions[actionName].bind(store);
        storeActions[actionName] = function (...args) {
            return actions[actionName].apply(store, args);
        };
    }

    return storeActions;
}