Open WJ-Yuan opened 1 year ago
函数的功能是创建一个整个 app 的 pinia 实例,主要是理解 effectScope 和 markRaw 两个 vue3 API 的作用
export function createPinia(): Pinia {
// 创建一个 effect 作用域,可以捕获其中的响应式副作用
const scope = effectScope(true);
// 转发 {} 的 state 值
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({}),
)!;
let _p: Pinia['_p'] = [];
let toBeInstalled: PiniaPlugin[] = [];
// 使其不具有响应
const pinia: Pinia = markRaw({
install(app: App) {
// 将当前 pinia 设置为 activePinia
setActivePinia(pinia);
if (!isVue2) {
pinia._a = app;
// vue3 版本将 pinia 绑定到全局
app.provide(piniaSymbol, pinia);
app.config.globalProperties.$pinia = pinia;
if (USE_DEVTOOLS) {
registerPiniaDevtools(app, pinia);
}
toBeInstalled.forEach((plugin) => _p.push(plugin));
toBeInstalled = [];
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin);
} else {
_p.push(plugin);
}
return this;
},
_p,
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
});
if (USE_DEVTOOLS && typeof Proxy !== 'undefined') {
pinia.use(devtoolsPlugin);
}
// 返回 Pinia 值
return pinia;
}
主体功能代码如上,未注释的是一些关于插件的处理逻辑,暂时不关注这部分。总结下其实总体的思路就是:
我们都知道 Pinia 有两种形式来定义 store,因此在这个函数里
export function defineStore<Id extends string, SS>(
id: Id,
storeSetup: () => SS,
options?: DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>,
): StoreDefinition<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>;
export function defineStore(
idOrOptions: any,
setup?: any,
setupOptions?: any,
): StoreDefinition {
let id: string;
let options:
| DefineStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>
| DefineSetupStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>;
// ...
// 上述省略部分代码主要是做 Id 还是 option 的判断
// 主要关注这部分
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// 判断下是否已经有全局 state,如果有,获取到它
const hasContext = hasInjectionContext();
pinia =
(__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
(hasContext ? inject(piniaSymbol, null) : null);
if (pinia) setActivePinia(pinia);
pinia = activePinia!;
// 如果 id 没有重复,新建 store
if (!pinia._s.has(id)) {
// 判断 store 的 形式,如果是 setup 形式定义,则采用 createSetupStore 创建
if (isSetupStore) {
createSetupStore(id, setup, options, pinia);
} else {
// option 形式定义次采用 createOptionsStore 形式创建
createOptionsStore(id, options as any, pinia);
}
}
// 现在获取全局 state 中的 store
const store: StoreGeneric = pinia._s.get(id)!;
// 返回 store
return store as any;
}
useStore.$id = id;
// 返回 useStore 这个函数,当我们在组件中调用时,获取到其返回值 store
return useStore;
}
option store 的整体思路是将 option 的选项转化到 setup 函数里,然后调用 createSetupStore
实现,具体的转化是
function createOptionsStore<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A extends _ActionsTree,
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean,
): Store<Id, S, G, A> {
// 解构 options
const { state, actions, getters } = options;
// 初始 state
const initialState: StateTree | undefined = pinia.state.value[id];
let store: Store<Id, S, G, A>;
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, id, state ? state() : {});
} else {
// 将 state 的值赋值给 value
pinia.state.value[id] = state ? state() : {};
}
}
// 将 state 转为 ref
const localState =
__DEV__ && hot
? toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id]);
return assign(
localState,
actions, // action 不变
Object.keys(getters || {}).reduce((computedGetters, name) => {
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia);
const store = pinia._s.get(id)!;
if (isVue2 && !store._r) return;
return getters![name].call(store, store);
}),
);
return computedGetters;
}, {} as Record<string, ComputedRef>), // 将 getters 按顺序转为 computed 且不可修改
);
}
// 调用 createSetupStore
store = createSetupStore(id, setup, options, pinia, hot, true);
return store as any;
}
现在来看 createSetupStore,它是整个 Pinia 创建 store 的核心,其核心思想就是将 setup 中的数据放入到一个 effectScope 中,使其响应
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree,
>(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean,
): Store<Id, S, G, A> {
let scope!: EffectScope;
const partialStore = {
_p: pinia,
$id,
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {},
$dispose,
} as _StoreWithState<Id, S, G, A>;
const store: Store<Id, S, G, A> = reactive(partialStore);
// 将其加入pinia
pinia._s.set($id, store);
const runWithContext =
(pinia._a && pinia._a.runWithContext) || fallbackRunWithContext;
// 创建一个 effect scope 来监测其中的变化
const setupStore = pinia._e.run(() => {
scope = effectScope();
return runWithContext(() => scope.run(setup));
})!;
// 合并 store 和 setup store
assign(store, setupStore);
return store;
}
解构 store,实现思路就是将其中的每一项都使用 toRef
/ toRefs
转为响应式
export function storeToRefs<SS extends StoreGeneric>(
store: SS,
): StoreToRefs<SS> {
if (isVue2) {
// vue2 版本直接用 toRefs
return toRefs(store);
} else {
// vue3 版本避免污染原始数据,先将 store 转为不响应
store = toRaw(store);
// 新建一个空白对象
const refs = {} as StoreToRefs<SS>;
// 循环遍历 store 属性
for (const key in store) {
// 取值
const value = store[key];
// toRef 响应
if (isRef(value) || isReactive(value)) {
refs[key] = toRef(store, key);
}
}
return refs;
}
}