国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

這篇具有很好參考價值的文章主要介紹了深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。


深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘


一、 ???創(chuàng)建源碼分析環(huán)境

?? 項目地址:https://github.com/mk965/read-pinia文章來源地址http://www.zghlxwxcb.cn/news/detail-424229.html

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_1

1. 創(chuàng)建一個 vue 項目

npm init vue@latest
# or
yarn create vite

2. Pinia 源碼入口

??源碼地址: github.com/vuejs/pinia

??打包文件: rollup.config.js

??入口文件: packages/pinia/src/index.ts

3. 復(fù)制 Pinia 源碼 & 在 main.ts 中初始化 Pinia 插件

pinia/packages/pinia/src 目錄下的所有文件復(fù)制到我們之前生成項目的/src/pinia 中。

main.ts 中安裝初始化 Pinia:

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

4. 添加必要倉庫依賴

此時通過 yarn dev 啟動項目時,會報缺少依賴。

rollup.config.js 第122行,可以看到依賴分別有

  • vue-demi:一個可以幫助我們開發(fā)在vue2、vue3上通用的 vue 庫的開發(fā)工具。
  • vue:vue 項目。
  • @vue/composition-api:vue 組合式 api 插件。
121 | const external = ['vue-demi', 'vue', '@vue/composition-api']

在我們的項目中安裝好這兩個庫(vue 在創(chuàng)建項目時已經(jīng)安裝了)。

yarn add vue-demi @vue/composition-api

5. 添加必要環(huán)境變量

此時通過 yarn dev 啟動項目時,會報飲用錯誤。

Uncaught ReferenceError: __DEV__ is not defined
    at rootStore.ts:97:3

環(huán)境變量位于 rollup.config.js 第167行:

const replacements = {
	__COMMIT__: `"${process.env.COMMIT}"`,
	__VERSION__: `"${pkg.version}"`,
	__DEV__:
		(isBundlerESMBuild && !isRawESMBuild) || (isNodeBuild && !isProduction)
			? // preserve to be handled by bundlers
			`(process.env.NODE_ENV !== 'production')`
			: // hard coded dev/prod builds
			JSON.stringify(!isProduction),
	// this is only used during tests
	__TEST__:
		(isBundlerESMBuild && !isRawESMBuild) || isNodeBuild
			? `(process.env.NODE_ENV === 'test')`
			: 'false',
	__FEATURE_PROD_DEVTOOLS__: isBundlerESMBuild
		? `(typeof __VUE_PROD_DEVTOOLS__ !== 'undefined' && __VUE_PROD_DEVTOOLS__)`
		: 'false',
	// If the build is expected to run directly in the browser (global / esm builds)
	__BROWSER__: JSON.stringify(isRawESMBuild),
	// is targeting bundlers?
	__BUNDLER__: JSON.stringify(isBundlerESMBuild),
	__GLOBAL__: JSON.stringify(isGlobalBuild),
	// is targeting Node (SSR)?
	__NODE_JS__: JSON.stringify(isNodeBuild),
}

我們在 vite.config.ts 中添加缺少的環(huán)境變量:

export default defineConfig({
  plugins: [vue()],
  define: {
    __DEV__: true,
    __TEST__: true
  }
})

6. 環(huán)境測試

src/pinia/createPinia.ts 中輸出字符串,查看控制臺是否正常打印,如正常打印則源碼分析環(huán)境正常運行:

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_1


二、??源碼分析(1)——執(zhí)行流程

defineStore、 createPinia、 useStore 等關(guān)鍵函數(shù)內(nèi)打印日志來確定執(zhí)行順序:

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

可以確定執(zhí)行順序為:

defineStore() ? main.ts ? createPinia() ? useStore()

其中 defineStore() 是在引用階段被調(diào)用,并返回 useStore() 函數(shù),之后便開始 Vue 的流程。注冊插件等,最后在頁面內(nèi)調(diào)用 useStore(),創(chuàng)建 Store 等步驟。


三、??源碼分析(2)——createPinia

使用 Pinia 前需要在 vue 中初始化一個注冊 Pinia,注冊的方法是使用 :

import { createPinia } from 'pinia';
app.use(createPinia());

顯然 createPinia 函數(shù)返回的是一個 vue插件 。通過插件的方式安裝到 vue 中。

1. 源碼目錄

src/pinia/index.ts 目錄中可以找到:

export { createPinia } from './createPinia'

顯然 createPinia 函數(shù)的源碼目錄為: src/pinia/createPinia.ts 。

2. createPinia

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_2

在函數(shù)的最開始,通過 effectScope 聲明了一個 ref,并賦值給了state,我們將其 簡單理解為聲明了一個 ref 并賦值給 state 。

?? effectScope 創(chuàng)建一個 effect 作用域,可以捕獲其中所創(chuàng)建的響應(yīng)式副作用 (即計算屬性和偵聽器),這樣捕獲到的副作用可以一起處理。對于該 API 的使用細節(jié),請查閱對應(yīng)的 RFC。

import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore';
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi';
import { registerPiniaDevtools, devtoolsPlugin } from './devtools';
import { USE_DEVTOOLS } from './env';
import { StateTree, StoreGeneric } from './types';

/**
 * 創(chuàng)建應(yīng)用程序要使用的Pinia實例
 */
export function createPinia(): Pinia {
	console.log('?? createPinia run!');
	/**
	 * effectScope:
	 * 創(chuàng)建一個 effect 作用域,可以捕獲其中所創(chuàng)建的響應(yīng)式副作用 (即計算屬性和偵聽器),這樣捕獲到的副作用可以一起處理。對于該 API 的使用細節(jié),請查閱對應(yīng)的 RFC。
	 */
	const scope = effectScope(true);
	// NOTE: 在這里,我們可以檢查窗口對象的狀態(tài),并直接設(shè)置它
	// 如果有類似Vue 3 SSR的東西
	const state = scope.run<Ref<Record<string, StateTree>>>(() => ref<Record<string, StateTree>>({}))!;

	// 所有需要安裝的插件
	let _p: Pinia['_p'] = [];
	// 在調(diào)用 app.use(pinia) 前需要安裝的插件
	let toBeInstalled: PiniaPlugin[] = [];

	// 使用 markRaw 包裹的 pinia 使其不會變?yōu)轫憫?yīng)式
	const pinia: Pinia = markRaw({
		// app.use 執(zhí)行的邏輯
		install(app: App) {
			// 設(shè)置當(dāng)前使用的 pinia 實例
			setActivePinia(pinia);
			// 如果是 vue2 ,全局注冊已經(jīng)在 PiniaVuePlugin 完成,所以這段邏輯將跳過
			if (!isVue2) {
				// app 實例
				pinia._a = app;
				// 通過 provide 傳遞 pinia 實例,提供給后續(xù)使用
				app.provide(piniaSymbol, pinia);
				// 設(shè)置全局屬性 $pinia
				app.config.globalProperties.$pinia = pinia;
				/* istanbul ignore else */
				if (USE_DEVTOOLS) {
					registerPiniaDevtools(app, pinia);
        }
				// 處理未執(zhí)行插件
        toBeInstalled.forEach((plugin) => _p.push(plugin));
        // 處理完插件后清空
				toBeInstalled = [];
			}
		},

    /**
     * 為 Pinia 提供安裝插件的能力
     * @param plugin 
     * @returns Pinia
     */
    use(plugin) {
      // 如果 use 階段初始化完成則暫存 toBeInstalled 中
			if (!this._a && !isVue2) {
				toBeInstalled.push(plugin);
			} else {
				_p.push(plugin);
			}
			return this;
		},

		_p, // 所有的 pinia 插件
		// it's actually undefined here
		// @ts-expect-error
		_a: null,   // vue 實例,在 install 階段設(shè)置
		_e: scope,  // pinia 的作用域?qū)ο?,每個 store 都有單獨的 scope
		_s: new Map<string, StoreGeneric>(),  // store 緩存,key 為 pinia 的 id,value 為 pinia 對外暴漏的數(shù)據(jù)
		state,      // pinia 所有的 state 的合集,key 為 pinia 的 id,value 為 store 下所有的 state
	});

	// pinia devtools rely on dev only features so they cannot be forced unless
	// pinia開發(fā)工具依賴于僅用于開發(fā)的功能,因此除非
	// the dev build of Vue is used. Avoid old browsers like IE11.
	// 使用Vue的開發(fā)版本。避免使用像IE11這樣的舊瀏覽器。
	if (USE_DEVTOOLS && typeof Proxy !== 'undefined') {
		pinia.use(devtoolsPlugin);
	}

	return pinia;
}

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_2


四、??源碼分析(3)——defineStore

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_3

1. 三種創(chuàng)建方式

defineStore 所在位置: src/pinia/store.ts 進入文件之后可以看到通過函數(shù)重載的方式提供給我們?nèi)N參數(shù)傳遞方式:

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

其中參數(shù)含義如下:

  • id:store 的 id,必須唯一。

  • options: 與 Vue 的選項式 API 類似,我們也可以傳入一個帶有 idstate、actionsgetters屬性的 Option 對象。

  • storeSetup:以 setup 的方式創(chuàng)建,與 Vue 的 setup 函數(shù) 相似。在 storeSetup 中:

    • ref() 等同于 state。
    • computed() 等同于 getters
    • function() 等同于 actions。

2. defineStore 的執(zhí)行邏輯

defineStore 函數(shù)中,并沒有很特別的邏輯,首先是對三種創(chuàng)建方式進行兼容,然后定義一個名為 useStore 的函數(shù),然后返回 useStore

useStore 具體做了什么下節(jié)分析。

export function defineStore(
    // TODO: add proper types from above
    idOrOptions: any,
    setup?: any,
    setupOptions?: any
): StoreDefinition {
    let id: string
    let options:
        | DefineStoreOptions<
            string,
            StateTree,
            _GettersTree<StateTree>,
            _ActionsTree
        >
        | DefineSetupStoreOptions<
            string,
            StateTree,
            _GettersTree<StateTree>,
            _ActionsTree
        >
    
    // 此處對三種創(chuàng)建方式進行兼容處理
    const isSetupStore = typeof setup === 'function'
    if (typeof idOrOptions === 'string') {
        id = idOrOptions
        // the option store setup will contain the actual options in this case
        options = isSetupStore ? setupOptions : setup
    } else {
        options = idOrOptions
        id = idOrOptions.id
    }
    
    // useStore
    function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
        // ... 
    }

    useStore.$id = id;

    // 將 useStore 函數(shù)返回出去,但不會立即調(diào)用,在組件內(nèi)使用 store 時才會調(diào)用。
    // 所以在 defineStore 中只是做了些兼容邏輯,然后返回一個函數(shù),返回的這個函數(shù)真正調(diào)用時才會觸發(fā)更多邏輯。
    return useStore;
}

雖然我們前面定義了一個 store,但在我們使用 <script setup> 調(diào)用 useStore() (或者使用 setup() 函數(shù),像所有的組件那樣) 之前,store 實例是不會被創(chuàng)建的。

<script setup>
		import { useMainStore1 } from '@/stores/counter'
		// 可以在組件中的任意位置訪問 `store` 變量 ?
		const store = useMainStore1();
</script>

4. useStore

在之前我們分析了 defineStore 方法調(diào)用的時候返回了 useStore 方法,接下來看一下此方法究竟干了些什么。

function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
	Log('useStore()');

	// 獲取當(dāng)前 vue 實例
	const currentInstance = getCurrentInstance();
	pinia =
		// 在 test 模式下,忽略提供的參數(shù),因為我們總是可以通過 getActivePinia() 獲取 pinia 實例
		// 如果 是test模式 && activePinia不為空 && activePinia是test模式 則為空 否則 返回參數(shù)中的pinia
		// 或者 如果獲取到了當(dāng)前實例 并且 存在piniaSymbol 返回 inject(piniaSymbol, null) 否則 返回空
		(__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
		// 這里的 inject(piniaSymbol) 是在 createPinia 的 install 中 app.provide(piniaSymbol, pinia);
		(currentInstance && inject(piniaSymbol, null));

	console.log('pinia 實例 ==>', pinia);

	// 將當(dāng)前 pinia 實例設(shè)置為激活的 pinia
	// 如果存在多個 pinia 實例,方便后續(xù)邏輯獲取當(dāng)前pinia實例
	if (pinia) setActivePinia(pinia);

	// 在 dev環(huán)境 并且 獲取不到當(dāng)前 pinia 實例,則說明未全局注冊,拋出錯誤
	if (__DEV__ && !activePinia) {
		throw new Error(`[??]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` + `\tconst pinia = createPinia()\n` + `\tapp.use(pinia)\n` + `This will fail in production.`);
	}

	// 將激活的 pinia 實例賦值給 pinia 變量,確保 pinia === activePinia。防止 setActivePinia 出錯導(dǎo)致兩個變量不一致
	pinia = activePinia!;

	// 如果 pinia 的 store 緩存中沒有當(dāng)前的 id,則創(chuàng)建新的 store,
	// 否則直接獲取緩存中 store。
	if (!pinia._s.has(id)) {
		// 創(chuàng)建 store 并將其注冊在 pinia._s 中
		if (isSetupStore) {
			// 組合式
			createSetupStore(id, setup, options, pinia);
		} else {
			// 選項式
			createOptionsStore(id, options as any, pinia);
		}

		/* istanbul ignore else */
		if (__DEV__) {
			// @ts-expect-error: not the right inferred type
			useStore._pinia = pinia;
		}
	}

	// 獲取 pinia 緩存中的 store
	const store: StoreGeneric = pinia._s.get(id)!;

	// 開發(fā)環(huán)境 并且 是熱更新
	if (__DEV__ && hot) {
		const hotId = '__hot:' + id;
		const newStore = isSetupStore ? createSetupStore(hotId, setup, options, pinia, true) : createOptionsStore(hotId, assign({}, options) as any, pinia, true);

		hot._hotUpdate(newStore);

		// cleanup the state properties and the store from the cache
		delete pinia.state.value[hotId];
		pinia._s.delete(hotId);
	}

	// save stores in instances to access them devtools
	if (
		__DEV__ &&
		IS_CLIENT &&
		currentInstance &&
		currentInstance.proxy &&
		// avoid adding stores that are just built for hot module replacement
		!hot
	) {
		const vm = currentInstance.proxy;
		const cache = '_pStores' in vm ? vm._pStores! : (vm._pStores = {});
		cache[id] = store;
	}

	// StoreGeneric cannot be casted towards Store
	return store as any;
}

從上邊代碼中,可以發(fā)現(xiàn)最關(guān)鍵的兩個函數(shù)是 createSetupStore、 createOptionsStore,分別是創(chuàng)建 組合式Store選項式Store。里邊包含了創(chuàng)建 store 的關(guān)鍵邏輯,下面分別來看一下。

5. createSetupStore

createSetupStore 的作用是創(chuàng)建一個組合式的 store,之后的 createOptionsStore 其實也是把 option 轉(zhuǎn)化后調(diào)用 createSetupStore 來創(chuàng)建 store。createSetupStore 的源碼很長,我們分批研究。對于一些變量的定義等內(nèi)容在此省略,只關(guān)注最核心邏輯,詳細的注釋可以查看 Github 中的源碼。

(1) 參數(shù)

createSetupStore 總共接收了6個參數(shù):

  • $id :當(dāng)前 Store 的 ID,
  • setup defineStore 或者 createOptionsStore 傳入的 setup 函數(shù)
  • options 配置選項,state、getter、actions 等
  • pinia Pinia 實例
  • hot 熱更新相關(guān)
  • isOptionsStore 是否是 選項式 Store 創(chuàng)建
/**
 * 創(chuàng)建組合式 Store
 * @param $id Store ID
 * @param setup defineStore 傳入的 setup 函數(shù)
 * @param options 配置選項
 * @param pinia Pinia 實例
 * @param hot 熱更新相關(guān)
 * @param isOptionsStore 是否是 選項式 Store 創(chuàng)建
 * @returns 創(chuàng)建的 store
 */
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> {
	
	// ...
	
	return store;
}

(2) 創(chuàng)建 Store

此過程中,創(chuàng)建一個 setupStore 常量,創(chuàng)建了一個作用域并執(zhí)行了 setup 函數(shù),獲取到 setup 函數(shù)中返回的內(nèi)容,也就是我們定義的 state、getter、action 等內(nèi)容。

在此過程中,state 的內(nèi)容也會被存儲到 pinia.state 中。action 則會被 wrapAction 處理。

對每一項 action 進行處理,目的是為了支持 $onAction 方法,此方法會在執(zhí)行 action 時執(zhí)行回調(diào)函數(shù),回調(diào)函數(shù)可以接收三個參數(shù)分別是:被調(diào)用的 storeaction 的名字、傳遞給 action 的參數(shù)。在 store 中還會有一些基礎(chǔ)操作的 API ,請看下節(jié)。

// 在當(dāng)前 pinia 實例的緩存中新建一個作用域,在作用域中執(zhí)行 setup 函數(shù)
// 執(zhí)行的結(jié)果為 store 。 example: { count: ObjectRefImpl, increment: Function () }
const setupStore = pinia._e.run(() => {
	scope = effectScope();
	return scope.run(() => setup());
})!;

// 覆蓋現(xiàn)有操作以支持 $onAction
for (const key in setupStore) {
	const prop = setupStore[key];

	// ((如果是 ref) 并且 (不是 computed)) 或者 (是 reactive)
	if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
		// 如果是 optionsStore 方式創(chuàng)建,option 結(jié)構(gòu)已經(jīng)在 createOptionsStore 將其加入 pinia
		if (!isOptionsStore) {
			// 將 ref 轉(zhuǎn)移到 pinia state 以保持一切同步
			if (isVue2) {
				set(pinia.state.value[$id], key, prop);
			} else {
				pinia.state.value[$id][key] = prop;
			}
		}
	// 否則,如果是函數(shù)類型,那么它就是一個 action
	} else if (typeof prop === 'function') {
		// 如果是重寫這個值,應(yīng)該避免使用 wrapAction 重復(fù)包裝
		const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop);
		// 這是一個熱更新模塊替換 store,因為 hotUpdate 方法需要在正確的上下文中執(zhí)行它
		if (isVue2) {
			set(setupStore, key, actionValue);
		} else {
			setupStore[key] = actionValue;
		}

		// 將 actions 存儲到插件配置的 actions 數(shù)組,以便它們可以在插件中使用
		optionsForPlugin.actions[key] = prop;
	}
}

(3) 基礎(chǔ) API

在 Pinia 的 store 中存在很多基礎(chǔ) API,比如:獲取 store id $id、增加 action 調(diào)用回調(diào) $onAction()、重置 store $reset()、變更 store $patch()、訂閱 $subscribe()、移除 store $dispose、獲取所有 state $state 等。我們逐個分析。

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

基礎(chǔ)的 API 首先被儲存在 partialStore 中,然后創(chuàng)建一個 store 常量,并且把這些基礎(chǔ) API 和 store 的內(nèi)容都合并到 store 常量中。

/**
 * 具有 state 和 功能 的基本 store,但不能直接使用。
 */
const partialStore = {
	_p: pinia,
	$id,
	$onAction,
	$patch,
	$reset,
	$subscribe,
	$dispose,
} as _StoreWithState<Id, S, G, A>;

(4) Store 和 基礎(chǔ) API 合并

在 (2) 和 (3) 中我們創(chuàng)建了 store 的基本內(nèi)容和基礎(chǔ)的API,現(xiàn)在新建一個變量,并把它們合并到一塊:

/**
 * 創(chuàng)建一個響應(yīng)式的 store 對象
 * 將基礎(chǔ)函數(shù)合并到 store 中
 */
const store: Store<Id, S, G, A> = reactive(
	__DEV__ || USE_DEVTOOLS
		? assign(
				{
					_hmrPayload,
					_customProperties: markRaw(new Set<string>()), // devtools custom properties
				},
				partialStore
				// must be added later
				// setupStore
		  )
		: partialStore
) as unknown as Store<Id, S, G, A>;

assign(toRaw(store), setupStore)

現(xiàn)在,還缺少一個獲取所有 state 得屬性: $state ,我們使用 definePropertystore 增加 $state 屬性 :

// 使用它而不是 computed with setter 可以在任何地方創(chuàng)建它,而無需將計算的生命周期鏈接到首次創(chuàng)建 store 的任何地方。
// 給 store 定義 $state 屬性,方便獲取全部的 state
Object.defineProperty(store, '$state', {
	get: () => (__DEV__ && hot ? hotState.value : pinia.state.value[$id]),
	set: (state) => {
		/* istanbul ignore if */
		if (__DEV__ && hot) {
			throw new Error('cannot set hotState');
		}
		$patch(($state) => {
			assign($state, state);
		});
	},
});

(5) 對于 Pinia 自定義插件的處理

在之前的 createPinia() 方法中,Pinia 實例上存在一個 use() 方法是對自定義插件的支持,在這里我們需要對安裝的插件進行處理,調(diào)用左右的插件函數(shù),并給函數(shù)傳入 store app piain options 四個參數(shù)。

// apply 全部插件
pinia._p.forEach((extender) => {
	console.log("插件安裝:", extender);
	// 如果使用開發(fā)工具
	/* istanbul ignore else */
	if (USE_DEVTOOLS) {
		const extensions = scope.run(() =>
			// 調(diào)用插件,并傳入?yún)?shù)
			extender({
				store,
				app: pinia._a,
				pinia,
				options: optionsForPlugin,
			})
		)!;
		Object.keys(extensions || {}).forEach((key) => store._customProperties.add(key));
		assign(store, extensions);
	} else {
		// 這里將插件返回的屬性合并到 store 中
		assign(
			store,
			scope.run(() =>
				extender({
					store,
					app: pinia._a,
					pinia,
					options: optionsForPlugin,
				})
			)!
		);
	}
});

我們可以這樣給 Piain 安裝插件:

const pinia = createPinia();

// 給 pinia 安裝插件
pinia.use((prop) => {
	Log('Pinia 插件使用');
	console.log('插件獲取到的參數(shù):', prop);
	return {
		$aaa: (param: string) => {
			console.log('這里是插件安裝到 Pinia 上的功能');
			console.log('prop', prop);
			console.log('param', param);
		},
	};
});

app.use(pinia);

使用插件:

store.$aaa('傳遞的參數(shù)');

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

6. createOptionsStore

createOptionsStore 的代碼量比較少,從下面的代碼可以發(fā)現(xiàn),基本的邏輯就是從 options 中獲取到 state、 actions、 getters,定義一個 setup 函數(shù)并調(diào)用 createSetupStore 創(chuàng)建 Store,還要將 getters 轉(zhuǎn)換為 computed。

/**
 * 創(chuàng)建 選項式 store
 * @param id Store ID
 * @param options 配置選項
 * @param pinia Pinia 實例
 * @param hot 熱更新相關(guān)
 * @returns 創(chuàng)建的 store
 */
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> {
	Log('createOptionsStore()');
	const { state, actions, getters } = options;

	const initialState: StateTree | undefined = pinia.state.value[id];

	let store: Store<Id, S, G, A>;

	/**
	 * 自定義一個 setup 函數(shù)
	 * @returns store
	 */
	function setup() {
		if (!initialState && (!__DEV__ || !hot)) {
			/* istanbul ignore if */
			if (isVue2) {
				set(pinia.state.value, id, state ? state() : {});
			} else {
				pinia.state.value[id] = state ? state() : {};
			}
		}

		// 避免在 pinia.state.value 中創(chuàng)建 state
		const localState =
			__DEV__ && hot
				? // 使用 ref() 解包狀態(tài)中的引用
				  toRefs(ref(state ? state() : {}).value)
				: toRefs(pinia.state.value[id]);

		return assign(
			localState,
			actions,
			Object.keys(getters || {}).reduce((computedGetters, name) => {
				if (__DEV__ && name in localState) {
					// getter 不能和 state 屬性同名
					console.warn(`[??]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`);
				}

				// 把 getter 轉(zhuǎn)為 computed
				computedGetters[name] = markRaw(
					computed(() => {
						setActivePinia(pinia);
						// it was created just before
						const store = pinia._s.get(id)!;

						// allow cross using stores
						/* istanbul ignore next */
						if (isVue2 && !store._r) return;

						// @ts-expect-error
						// return getters![name].call(context, context)
						// TODO: avoid reading the getter while assigning with a global variable
						return getters![name].call(store, store);
					})
				);
				return computedGetters;
			}, {} as Record<string, ComputedRef>)
		);
	}

	store = createSetupStore(id, setup, options, pinia, hot, true);

	return store as any;
}

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_3


五、??源碼分析(4)—— store 的基礎(chǔ) API 實現(xiàn)

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_3

1. $id

這個沒啥好說的,就是 createSetupStore 參數(shù)中的 $id。

2. $onAction

設(shè)置一個回調(diào),當(dāng)一個 action 即將被調(diào)用時,就會被調(diào)用。 回調(diào)接收一個對象, 其包含被調(diào)用 action 的所有相關(guān)信息:

  • store: 被調(diào)用的 store
  • name: action 的名稱
  • args: 傳遞給 action 的參數(shù)

除此之外,它會接收兩個函數(shù), 允許在 action 完成或失敗時執(zhí)行的回調(diào)。

它還會返回一個用來刪除回調(diào)的函數(shù)。 請注意,當(dāng)在組件內(nèi)調(diào)用 store.$onAction() 時,除非 detached 被設(shè)置為 true, 否則當(dāng)組件被卸載時,它將被自動清理掉。

在 Pinia 的源碼中,關(guān)于 $onAction 的代碼是這樣的:

const partialStore = {
	// ...
	$onAction: addSubscription.bind(null, actionSubscriptions),
	// ...
}

可以發(fā)現(xiàn), $onAction 就是給 addSubscription 函數(shù)綁定了個 nullthis 和一個參數(shù),再來看一下這個 addSubscription 是何方神圣:

export const noop = () => {};

/**
 * 添加訂閱
 * @param subscriptions 訂閱者數(shù)組
 * @param callback 回調(diào)
 * @param detached
 * @param onCleanup 當(dāng)清楚訂閱時的回調(diào)
 * @returns 清除訂閱的回調(diào)
 */
export function addSubscription<T extends _Method>(
	subscriptions: T[], 
	callback: T, 
	detached?: boolean, 
	onCleanup: () => void = noop
) {
	subscriptions.push(callback);

	// 移除訂閱
	const removeSubscription = () => {
		const idx = subscriptions.indexOf(callback);
		// 如果存在這個訂閱,在訂閱數(shù)組中移除掉,并執(zhí)行回調(diào)
		if (idx > -1) {
			subscriptions.splice(idx, 1);
			// 執(zhí)行移除訂閱回調(diào)
			onCleanup();
		}
	};

	// detached 為 true 時,在當(dāng)前作用于停止時,不會刪除此訂閱,為 false 時會移除此訂閱
	// getCurrentScope 如果有的話,返回當(dāng)前活躍的 effect 作用域
	if (!detached && getCurrentScope()) {
		// onScopeDispose: 在當(dāng)前活躍的 effect 作用域上注冊一個處理回調(diào)函數(shù)。當(dāng)相關(guān)的 effect 作用域停止時會調(diào)用這個回調(diào)函數(shù)。
		onScopeDispose(removeSubscription);
	}

	// 返回移除訂閱的函數(shù)
	return removeSubscription;
}

3. $patch

將一個 state 補丁應(yīng)用于當(dāng)前狀態(tài)。允許傳遞嵌套值。

$patch 允許兩種參數(shù)傳遞方式,傳入一個函數(shù),或一個 state 的補丁。

/**
 * $patch 函數(shù)傳遞方式
 * @param stateMutation
 * @example store.$patch((state) => state.count += 200);
 */
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void;
/**
 * $patch 對象傳遞方式
 * @param partialState
 * @example store.$patch({ count: 100 });
 */
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void;
function $patch(partialStateOrMutator: _DeepPartial<UnwrapRef<S>> | ((state: UnwrapRef<S>) => void)): void {
	Log('$patch', partialStateOrMutator);
	// 訂閱收集器,保存收集到的訂閱者
	let subscriptionMutation: SubscriptionCallbackMutation<S>;
	isListening = isSyncListening = false;
	// 重置 debugger 事件,因為 patches 是同步的
	/* istanbul ignore else */
	if (__DEV__) {
		debuggerEvents = [];
	}
	// 對兩種傳參方式進行兼容
	// 如果參數(shù)是函數(shù)
	if (typeof partialStateOrMutator === 'function') {
		// 如果是函數(shù),直接調(diào)用,并把 state 傳過去
		partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>);
		// 收集訂閱,分別保存類型、id、事件
		subscriptionMutation = {
			type: MutationType.patchFunction,
			storeId: $id,
			events: debuggerEvents as DebuggerEvent[],
		};
	} else {
		// 如果傳來的是 object
		// merge 參數(shù)對象到當(dāng)前 store 的 state
		mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);
		subscriptionMutation = {
			type: MutationType.patchObject,
			payload: partialStateOrMutator,
			storeId: $id,
			events: debuggerEvents as DebuggerEvent[],
		};
	}
	//
	const myListenerId = (activeListener = Symbol());
	nextTick().then(() => {
		if (activeListener === myListenerId) {
			isListening = true;
		}
	});
	isSyncListening = true;
	// 在上方邏輯中,我們將 isListening isSyncListening 重置為 false,不會觸發(fā) $subscribe 中的 callback,所以需要手動進行訂閱發(fā)布
	triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id] as UnwrapRef<S>);
}

其中 triggerSubscriptions 方法是發(fā)布者,執(zhí)行訂閱函數(shù)的回調(diào):

/**
 * 觸發(fā)訂閱者回調(diào)
 * @param subscriptions 訂閱數(shù)組
 * @param args 傳給回調(diào)的參數(shù)
 */
export function triggerSubscriptions<T extends _Method>(subscriptions: T[], ...args: Parameters<T>) {
	subscriptions.slice().forEach((callback) => {
		callback(...args);
	});
}

4. $reset

通過建立一個新的狀態(tài)對象,將 store 重設(shè)為初始狀態(tài)。

/**
 * $reset
 * 只有 選項式 構(gòu)建的才可以使用此方法,
 * 因為 state: () => ({count: 1}) 是一個函數(shù),只要重新調(diào)用就可以獲取原始值,
 * 而 組合式 構(gòu)建的話 state 以 ref() 的形式實現(xiàn),無法獲取原始值。
 */
const $reset = isOptionsStore
	? function $reset(this: _StoreWithState<Id, S, G, A>) {
			const { state } = options as DefineStoreOptions<Id, S, G, A>;
			// 取出 options 中的 state 函數(shù)重新執(zhí)行,以獲取到原始 state
			const newState = state ? state() : {};
			// 使用 $patch 更新 state,并分發(fā)訂閱
			this.$patch(($state) => {
				assign($state, newState);
			});
	  }
	: /* istanbul ignore next */
	__DEV__
	? () => {
			// 如果是組合式語法構(gòu)建的話,拋出錯誤,因為 ref() 不能獲取到原始值
			throw new Error(`??: Store "${$id}" is built using the setup syntax and does not implement $reset().`);
	  }
	: // noop 是個空函數(shù),生產(chǎn)環(huán)境不拋出錯誤
	  noop;

5. $subscribe

設(shè)置一個回調(diào),當(dāng)狀態(tài)發(fā)生變化時被調(diào)用。它會返回一個用來移除此回調(diào)的函數(shù)。 請注意,當(dāng)在組件內(nèi)調(diào)用 store.$subscribe() 時,除非 detached 被設(shè)置為 true, 否則當(dāng)組件被卸載時,它將被自動清理掉。

/**
 * 當(dāng)狀態(tài)發(fā)生變化時被調(diào)用
 * 它會返回一個用來移除此回調(diào)的函數(shù)
 * @param callback 回調(diào)
 * @param options 配置
 * @returns 返回一個取消訂閱的函數(shù),調(diào)用次函數(shù)時訂閱就被取消了
 */
function $subscribe(callback, options = {}) {
	Log('$subscribe', options);
	// 取消訂閱函數(shù)
	const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
	// effectScope:創(chuàng)建一個 effect 作用域,可以補貨其中所創(chuàng)建的響應(yīng)式副作用 (即計算屬性和偵聽器),這里用于捕獲 watch,以便于銷毀store的時候統(tǒng)一處理。
	const stopWatcher = scope.run(() =>
		// 從這里可以看出 pinia 的訂閱響應(yīng)式主要是依賴 vue 的 watch
		watch(
			() => pinia.state.value[$id] as UnwrapRef<S>,
			(state) => {
				if (options.flush === 'sync' ? isSyncListening : isListening) {
					callback(
						{
							storeId: $id,
							type: MutationType.direct,
							events: debuggerEvents as DebuggerEvent,
						},
						state
					);
				}
			},
			assign({}, $subscribeOptions, options)
		)
	)!;

	return removeSubscription;
}

其中, addSubscription 函數(shù)可以查看2. $onAction 。

6. $dispose

停止 store 的相關(guān)作用域,并從 store 注冊表中刪除它。 插件可以覆蓋此方法來清理已添加的任何副作用函數(shù)。 例如, devtools 插件停止顯示來自 devtools 的已停止的 store。

/**
 * $dispose
 * 停止 store 的相關(guān)作用域,并從 store 注冊表中刪除它。
 * 插件可以覆蓋此方法來清理已添加的任何副作用函數(shù)。 例如, devtools 插件停止顯示來自 devtools 的已停止的 store。
 */
function $dispose() {
	scope.stop();
	subscriptions = [];
	actionSubscriptions = [];
	pinia._s.delete($id);
}

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_3


六、??源碼分析(5)—— 輔助函數(shù)

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_4

Pinia 也提供了一組類似 Vuex 的 映射 state 的輔助函數(shù)。你可以用和之前一樣的方式來定義 Store。這里不做使用的介紹,用法請看官網(wǎng):https://pinia.vuejs.org/zh/introduction.html

??源碼目錄: src/pinia/mapHelpers.ts

1. mapActions

mapActions 有兩種傳參方式,兩種傳參方式第一個參數(shù)都是 defineStore 中返回的 useStore 函數(shù)。

  • 傳入一個對象,key 為映射到 methods 中的名字,value 為 action 的名字。
  • 傳入一個數(shù)組,item 為 action 的名字。

下面是 mapActions 的源碼,主要思想就是調(diào)用 useStore 方法得到 Store ,然后取出需要的 action 并返回。

/**
 * 這個方法需要傳入 useStore 和一個對象,可以在導(dǎo)入過程中給 action 改名,對象 key 為 action 的新名字,value 為 action 的舊名字
 * 通過生成一個傳遞到組件的 methods 字段的對象, 允許直接使用 store 的 action,而不需要使用組合式 API(setup())。 該對象的值是 action, 而鍵是產(chǎn)生的方法名稱。
 *
 * @example
 * ```js
 * export default {
 *   methods: {
 *     // other methods properties
 *     // useCounterStore has two actions named `increment` and `setCount`
 *     ...mapActions(useCounterStore, { moar: 'increment', setIt: 'setCount' })
 *   },
 *
 *   created() {
 *     this.moar()
 *     this.setIt(2)
 *   }
 * }
 * ```
 *
 * @param useStore - defineStore 返回的 useStore
 * @param keyMapper - 為 action 定義新名稱的對象
 */
export function mapActions<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A,
  KeyMapper extends Record<string, keyof A>
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keyMapper: KeyMapper
): _MapActionsObjectReturn<A, KeyMapper>
/**
 * 這個方法需要傳入 useStore 和一個數(shù)組,數(shù)組內(nèi)容為需要導(dǎo)入的 action 名稱
 * 通過生成一個傳遞到組件的 methods 字段的對象, 允許直接使用 store 的 action,而不需要使用組合式 API(setup())。 該對象的值是 action, 而鍵是產(chǎn)生的方法名稱。
 *
 * @example
 * ```js
 * export default {
 *   methods: {
 *     // other methods properties
 *     ...mapActions(useCounterStore, ['increment', 'setCount'])
 *   },
 *
 *   created() {
 *     this.increment()
 *     this.setCount(2) // pass arguments as usual
 *   }
 * }
 * ```
 *
 * @param useStore - defineStore 返回的 useStore
 * @param keys - 要映射的 action 名稱數(shù)組
 */
export function mapActions<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keys: Array<keyof A>
): _MapActionsReturn<A>
/**
 * 通過生成一個傳遞到組件的 methods 字段的對象, 允許直接使用 store 的 action,而不需要使用組合式 API(setup())。 該對象的值是 action, 而鍵是產(chǎn)生的方法名稱。
 *
 * @param useStore - defineStore 返回的 useStore
 * @param keysOrMapper - array or object
 */
export function mapActions<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A,
  KeyMapper extends Record<string, keyof A>
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keysOrMapper: Array<keyof A> | KeyMapper
): _MapActionsReturn<A> | _MapActionsObjectReturn<A, KeyMapper> {
  return Array.isArray(keysOrMapper)
    // 如果傳入的是數(shù)組,遍歷這個數(shù)組取出所有 action 名稱
    ? keysOrMapper.reduce((reduced, key) => {
        // @ts-expect-error
        reduced[key] = function (
          // 如果組件的具體類型無法獲得,或者你并不關(guān)心組件的具體類型,那么可以使用 ComponentPublicInstance
          this: ComponentPublicInstance,
          ...args: any[]
        ) {
          return useStore(this.$pinia)[key](...args)
        }
        return reduced
      }, {} as _MapActionsReturn<A>)
    // 如果傳入的是對象,keysOrMapper[key] 值為 action 名稱
    : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
        // key 為新 name 
        // @ts-expect-error
        reduced[key] = function (
          this: ComponentPublicInstance,
          ...args: any[]
        ) {
          return useStore(this.$pinia)[keysOrMapper[key]](...args)
        }
        return reduced
      }, {} as _MapActionsObjectReturn<A, KeyMapper>)
}

2. $mapStore

通過生成一個對象,傳遞到組件的 computed 字段 以允許在不使用組合式 API(setup()) 的情況下使用 store。 它接受一個 store 定義的列表參數(shù)。

/**
 * 通過生成一個對象,傳遞到組件的 computed 字段 以允許在不使用組合式 API(setup())的情況下使用 store。 它接受一個 store 定義的列表參數(shù)。
 *
 * @example
 * ```js
 * export default {
 *   computed: {
 *     // other computed properties
 *     ...mapStores(useUserStore, useCartStore)
 *   },
 *
 *   created() {
 *     this.userStore // store with id "user"
 *     this.cartStore // store with id "cart"
 *   }
 * }
 * ```
 *
 * @param stores - 要映射到 object 的 stores 列表
 */
export function mapStores<Stores extends any[]>(
  // 所有參數(shù)放入 stores 數(shù)組,所以 store 不需要在包裹一層數(shù)組
  ...stores: [...Stores]
): _Spread<Stores> {
  // 直接將 store 通過參數(shù)傳遞即可,不需要放到數(shù)組中,如果放到了數(shù)組中就拋出警告
  if (__DEV__ && Array.isArray(stores[0])) {
    console.warn(
      `[??]: Directly pass all stores to "mapStores()" without putting them in an array:\n` +
        `Replace\n` +
        `\tmapStores([useAuthStore, useCartStore])\n` +
        `with\n` +
        `\tmapStores(useAuthStore, useCartStore)\n` +
        `This will fail in production if not fixed.`
    )
    stores = stores[0]
  }

  // 遍歷所有傳進來的 useStore 并執(zhí)行,然后 return 出去就得到了所有的 store
  return stores.reduce((reduced, useStore) => {
    // $id 是 defineStore 添加的
    // @ts-expect-error: $id is added by defineStore
    reduced[useStore.$id + mapStoreSuffix] = function (
      this: ComponentPublicInstance
    ) {
      return useStore(this.$pinia)
    }
    return reduced
  }, {} as _Spread<Stores>)
}

3. $mapState

通過生成一個對象,并傳遞至組件的 computed 字段, 以允許在不使用組合式 API(setup())的情況下使用一個 store 的 state 和 getter。 該對象的值是 state 屬性/getter, 而鍵是生成的計算屬性名稱。 你也可以選擇傳遞一個自定義函數(shù),該函數(shù)將接收 store 作為其第一個參數(shù)。 注意,雖然它可以通過 this 訪問組件實例,但它沒有標注類型。

/**
 * 通過生成一個對象,并傳遞至組件的 computed 字段, 以允許在不使用組合式 API(setup())的情況下使用一個 store 的 state 和 getter。 該對象的值是 state 屬性/getter, 而鍵是生成的計算屬性名稱。 你也可以選擇傳遞一個自定義函數(shù),該函數(shù)將接收 store 作為其第一個參數(shù)。 注意,雖然它可以通過 this 訪問組件實例,但它沒有標注類型。
 *
 * @example
 * ```js
 * export default {
 *   computed: {
 *     // other computed properties
 *     // useCounterStore has a state property named `count` and a getter `double`
 *     ...mapState(useCounterStore, {
 *       n: 'count',
 *       triple: store => store.n * 3,
 *       // note we can't use an arrow function if we want to use `this`
 *       custom(store) {
 *         return this.someComponentValue + store.n
 *       },
 *       doubleN: 'double'
 *     })
 *   },
 *
 *   created() {
 *     this.n // 2
 *     this.doubleN // 4
 *   }
 * }
 * ```
 *
 * @param useStore - defineStore 中返回的 useStore
 * @param keyMapper - state 的屬性名 或 getters 的對象
 */
export function mapState<
    Id extends string,
    S extends StateTree,
    G extends _GettersTree<S>,
    A,
    KeyMapper extends Record<
        string,
        keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)
    >
>(
    useStore: StoreDefinition<Id, S, G, A>,
    keyMapper: KeyMapper
): _MapStateObjectReturn<Id, S, G, A, KeyMapper>

/**
 * Allows using state and getters from one store without using the composition
 * API (`setup()`) by generating an object to be spread in the `computed` field
 * of a component.
 *
 * @example
 * ```js
 * export default {
 *   computed: {
 *     // other computed properties
 *     ...mapState(useCounterStore, ['count', 'double'])
 *   },
 *
 *   created() {
 *     this.count // 2
 *     this.double // 4
 *   }
 * }
 * ```
 *
 * @param useStore - defineStore 中返回的 useStore
 * @param keys - state 的屬性名 或 getters 的數(shù)組
 */
export function mapState<
    Id extends string,
    S extends StateTree,
    G extends _GettersTree<S>,
    A,
    Keys extends keyof S | keyof G
>(
    useStore: StoreDefinition<Id, S, G, A>,
    // key數(shù)組,內(nèi)容僅限于 State 和 Getter 的 key
    keys: readonly Keys[]
): _MapStateReturn<S, G, Keys>

/**
 * Allows using state and getters from one store without using the composition
 * API (`setup()`) by generating an object to be spread in the `computed` field
 * of a component.
 *
 * @param useStore - defineStore 中返回的 useStore
 * @param keysOrMapper - array or object
 */
export function mapState<
    Id extends string,
    S extends StateTree,
    G extends _GettersTree<S>,
    A
>(
    useStore: StoreDefinition<Id, S, G, A>,
    keysOrMapper: any
): _MapStateReturn<S, G> | _MapStateObjectReturn<Id, S, G, A> {
    // 此處邏輯和 mapAction 很像
    return Array.isArray(keysOrMapper)
        ? keysOrMapper.reduce((reduced, key) => {
            reduced[key] = function (this: ComponentPublicInstance) {
            // 和 mapAction 的區(qū)別:mapAction 取出的是經(jīng)過 wrapAction 的 action ,然后在這調(diào)用了一下
            return useStore(this.$pinia)[key]
            } as () => any
            return reduced
        }, {} as _MapStateReturn<S, G>)
        : Object.keys(keysOrMapper).reduce((reduced, key: string) => {
            // @ts-expect-error
            reduced[key] = function (this: ComponentPublicInstance) {
            const store = useStore(this.$pinia)
            const storeKey = keysOrMapper[key]
            // 由于某種原因,TS 無法將 storeKey 的類型推斷為函數(shù)
            return typeof storeKey === 'function'
                ? (storeKey as (store: Store<Id, S, G, A>) => any).call(this, store)
                : store[storeKey]
            }
            return reduced
        }, {} as _MapStateObjectReturn<Id, S, G, A>)
}

4. $mapGetters

mapGetters 已廢棄,直接使用 mapState 即可。

/**
 * Alias for `mapState()`. You should use `mapState()` instead.
 * @deprecated use `mapState()` instead.
 */
export const mapGetters = mapState

5. $mapWritableState

在使用 $mapState 把 state 導(dǎo)入 computed 時,如果直接去修改 state 的值是不允許的。

深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘

$mapWritableState ********除了創(chuàng)建的計算屬性的 setter,其他與 mapState() 相同, 所以 state 可以被修改。 與 mapState() 不同的是,只有 state 屬性可以被添加。

/**
 * 除了創(chuàng)建的計算屬性的 setter,其他與 mapState() 相同, 所以 state 可以被修改。 與 mapState() 不同的是,只有 state 屬性可以被添加。
 *
 * @param useStore - store to map from
 * @param keyMapper - object of state properties
 */
export function mapWritableState<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A,
  KeyMapper extends Record<string, keyof S>
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keyMapper: KeyMapper
): _MapWritableStateObjectReturn<S, KeyMapper>
/**
 * Allows using state and getters from one store without using the composition
 * API (`setup()`) by generating an object to be spread in the `computed` field
 * of a component.
 *
 * @param useStore - store to map from
 * @param keys - array of state properties
 */
export function mapWritableState<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A,
  Keys extends keyof S
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keys: readonly Keys[]
): {
  [K in Keys]: {
    get: () => S[K]
    set: (value: S[K]) => any
  }
}
/**
 * Allows using state and getters from one store without using the composition
 * API (`setup()`) by generating an object to be spread in the `computed` field
 * of a component.
 *
 * @param useStore - store to map from
 * @param keysOrMapper - array or object
 */
export function mapWritableState<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A,
  KeyMapper extends Record<string, keyof S>
>(
  useStore: StoreDefinition<Id, S, G, A>,
  keysOrMapper: Array<keyof S> | KeyMapper
): _MapWritableStateReturn<S> | _MapWritableStateObjectReturn<S, KeyMapper> {
  // 也是對于數(shù)組和對象的分別處理
  // 返回包含 get 和 set 函數(shù)的對象,交給 computed 處理
  return Array.isArray(keysOrMapper)
    ? keysOrMapper.reduce((reduced, key) => {
        // @ts-ignore
        reduced[key] = {
          get(this: ComponentPublicInstance) {
            return useStore(this.$pinia)[key]
          },
          set(this: ComponentPublicInstance, value) {
            // it's easier to type it here as any
            return (useStore(this.$pinia)[key] = value as any)
          },
        }
        return reduced
      }, {} as _MapWritableStateReturn<S>)
    : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
        // @ts-ignore
        reduced[key] = {
          get(this: ComponentPublicInstance) {
            return useStore(this.$pinia)[keysOrMapper[key]]
          },
          set(this: ComponentPublicInstance, value) {
            // it's easier to type it here as any
            return (useStore(this.$pinia)[keysOrMapper[key]] = value as any)
          },
        }
        console.log(reduced)
        return reduced
      }, {} as _MapWritableStateObjectReturn<S, KeyMapper>)
}

??????? 本節(jié)代碼:https://github.com/mk965/read-pinia/tree/article_4


?結(jié)語

代碼雖然比較多,但核心邏輯還是借助 vue 的 ref 和 reactive 實現(xiàn)響應(yīng)式。把 state 處理為 ref ,把 getters 處理成 computed ,提供一些基礎(chǔ)方法,并使用單例模式返回一個實例。

完整注釋代碼:

?? 項目地址:https://github.com/mk965/read-pinia

到了這里,關(guān)于深入 Pinia:從代碼出發(fā)探索 Vue 狀態(tài)管理的奧秘的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【Vue全家桶】Pinia狀態(tài)管理

    【Vue全家桶】Pinia狀態(tài)管理

    ??這里是前端程序員小張! ??人海茫茫,感謝這一秒你看到這里。希望我的文章對你的有所幫助! ??愿你在未來的日子,保持熱愛,奔赴山海! Pinia開始于大概2019年,其目的是設(shè)計一個擁有 組合式 API 的 Vue 狀態(tài)管理庫 目前同時兼容Vue2、Vue3,也并不要求你使用Compositio

    2023年04月09日
    瀏覽(104)
  • vue3學(xué)習(xí)-Pinia狀態(tài)管理

    定義一個Store 這個 name ,也稱為 id ,是必要的,Pinia 使用它來將 store 連接到 devtools。 將返回的函數(shù)命名為 use… 是跨可組合項的約定,以使其符合你的使用習(xí)慣。 使用 store 一旦 store 被實例化,你就可以直接在 store 上訪問 state 、 getters 和 actions 中定義的任何屬性 store 是一

    2024年02月14日
    瀏覽(17)
  • Vue | Vue.js 全家桶 Pinia狀態(tài)管理

    Vue | Vue.js 全家桶 Pinia狀態(tài)管理

    ??? Vue?.js專欄:Node.js Vue.js 全家桶 Pinia狀態(tài)管理 ????? 個人簡介:一個不甘平庸的平凡人?? ? 個人主頁:CoderHing的個人主頁 ?? 格言: ?? 路漫漫其修遠兮,吾將上下而求索?? ?? 你的一鍵三連是我更新的最大動力?? 目錄 一、Pinia和Vuex的對比 什么是Pinia呢? Pina和

    2024年01月16日
    瀏覽(121)
  • Vue最新狀態(tài)管理工具Pinia——徹底搞懂Pinia是什么

    Vue最新狀態(tài)管理工具Pinia——徹底搞懂Pinia是什么

    知識專欄 專欄鏈接 Vuex知識專欄 https://blog.csdn.net/xsl_hr/category_12158336.html?spm=1001.2014.3001.5482 Vuex官方文檔 https://vuex.vuejs.org/zh/ Pinia官方文檔 https://pinia.web3doc.top/ 最近在 前端的深入學(xué)習(xí)過程 中,接觸了與 狀態(tài)管理 相關(guān)的內(nèi)容,涉及到 與vue2適配的vuex 和 與vue3適配的pinia ,因此

    2024年02月03日
    瀏覽(53)
  • vue3 快速入門系列 —— 狀態(tài)管理 pinia

    vue3 快速入門系列 —— 狀態(tài)管理 pinia

    其他章節(jié)請看: vue3 快速入門 系列 vue3 狀態(tài)管理這里選擇 pinia。 雖然 vuex4 已支持 Vue 3 的 Composition API,但是 vue3 官網(wǎng)推薦新的應(yīng)用使用 pinia —— vue3 pinia redux、mobx、vuex、pinia都是集中式狀態(tài)管理工具。與之對應(yīng)的就是分布式。 Pinia 符合直覺 的 Vue.js 狀態(tài)管理庫 甚至讓你忘

    2024年04月26日
    瀏覽(16)
  • vue:狀態(tài)管理庫及其部分原理(Vuex、Pinia)

    多組件的狀態(tài)共享問題: 當(dāng)多個組件需要訪問和修改相同的數(shù)據(jù)時,我們需要在組件之間傳遞 props或者使用事件總線。當(dāng),應(yīng)用就會變得難以維護和調(diào)試。 多組件狀態(tài)同步問題: 當(dāng)一個組件修改了狀態(tài),其他組件可能無法立即得知該變化。 狀態(tài)變更的追蹤問題: 無法追蹤

    2024年01月19日
    瀏覽(30)
  • vue 全局狀態(tài)管理(簡單的store模式、使用Pinia)

    vue 全局狀態(tài)管理(簡單的store模式、使用Pinia)

    多個組件可能會依賴同一個狀態(tài)時,我們有必要抽取出組件內(nèi)的共同狀態(tài)集中統(tǒng)一管理,存放在一個全局單例中,這樣任何位置上的組件都可以訪問其中的狀態(tài)或觸發(fā)動作 通過自定義一個store模式實現(xiàn)全局的狀態(tài)管理,實例如下 有兩個組件a、b共享store和store2兩個狀態(tài),我們

    2024年02月13日
    瀏覽(21)
  • vue 3 第三十一章:狀態(tài)管理(Pinia基礎(chǔ)知識)

    狀態(tài)管理是現(xiàn)代 Web 應(yīng)用開發(fā)中的一個重要概念。Vue 3 中的狀態(tài)管理庫 Pinia ,是一個基于 Vue 3 Composition API 的狀態(tài)管理庫,它提供了一種 簡單 、 靈活 的方式來管理應(yīng)用程序的狀態(tài),同時還具有 高性能 和 可擴展性 。 Pinia 在某種程度上來說,也可以被叫做 Vuex5 ,因為它結(jié)合

    2024年02月07日
    瀏覽(292)
  • Vue3狀態(tài)管理庫Pinia——自定義持久化插件

    Vue3狀態(tài)管理庫Pinia——自定義持久化插件

    個人簡介 ?? 個人主頁: 前端雜貨鋪 ???♂? 學(xué)習(xí)方向: 主攻前端方向,正逐漸往全干發(fā)展 ?? 個人狀態(tài): 研發(fā)工程師,現(xiàn)效力于中國工業(yè)軟件事業(yè) ?? 人生格言: 積跬步至千里,積小流成江海 ?? 推薦學(xué)習(xí):??前端面試寶典 ??Vue2 ??Vue3 ??Vue2/3項目實戰(zhàn) ??Node.js??

    2024年02月13日
    瀏覽(30)
  • vue2(Vuex)、vue3(Pinia)、react(Redux)狀態(tài)管理

    vue2(Vuex)、vue3(Pinia)、react(Redux)狀態(tài)管理

    Vuex 是一個專為 Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式。它使用集中式存儲管理應(yīng)用的所有組件的狀態(tài),以及規(guī)則保證狀態(tài)只能按照規(guī)定的方式進行修改。 State(狀態(tài)) :Vuex 使用單一狀態(tài)樹,即一個對象包含全部的應(yīng)用層級狀態(tài)。這個狀態(tài)樹對應(yīng)著一個應(yīng)用中的所有狀態(tài)。 Gett

    2024年01月23日
    瀏覽(27)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包