深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

这篇具有很好参考价值的文章主要介绍了深入 Pinia:从代码出发探索 Vue 状态管理的奥秘。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


深入 Pinia:从代码出发探索 Vue 状态管理的奥秘


一、 🏞️创建源码分析环境

🍍 项目地址:https://github.com/mk965/read-pinia文章来源地址https://www.toymoban.com/news/detail-424229.html

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_1

1. 创建一个 vue 项目

npm init vue@latest
# or
yarn create vite

2. Pinia 源码入口

🚗源码地址: github.com/vuejs/pinia

📦打包文件: rollup.config.js

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

3. 复制 Pinia 源码 & 在 main.ts 中初始化 Pinia 插件

pinia/packages/pinia/src 目录下的所有文件复制到我们之前生成项目的/src/pinia 中。

main.ts 中安装初始化 Pinia:

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

4. 添加必要仓库依赖

此时通过 yarn dev 启动项目时,会报缺少依赖。

rollup.config.js 第122行,可以看到依赖分别有

  • vue-demi:一个可以帮助我们开发在vue2、vue3上通用的 vue 库的开发工具。
  • vue:vue 项目。
  • @vue/composition-api:vue 组合式 api 插件。
121 | const external = ['vue-demi', 'vue', '@vue/composition-api']

在我们的项目中安装好这两个库(vue 在创建项目时已经安装了)。

yarn add vue-demi @vue/composition-api

5. 添加必要环境变量

此时通过 yarn dev 启动项目时,会报饮用错误。

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

环境变量位于 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 中添加缺少的环境变量:

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

6. 环境测试

src/pinia/createPinia.ts 中输出字符串,查看控制台是否正常打印,如正常打印则源码分析环境正常运行:

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_1


二、🧐源码分析(1)——执行流程

defineStorecreatePiniauseStore 等关键函数内打印日志来确定执行顺序:

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

可以确定执行顺序为:

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

其中 defineStore() 是在引用阶段被调用,并返回 useStore() 函数,之后便开始 Vue 的流程。注册插件等,最后在页面内调用 useStore(),创建 Store 等步骤。


三、🧐源码分析(2)——createPinia

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

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

显然 createPinia 函数返回的是一个 vue插件 。通过插件的方式安装到 vue 中。

1. 源码目录

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

export { createPinia } from './createPinia'

显然 createPinia 函数的源码目录为: src/pinia/createPinia.ts

2. createPinia

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_2

在函数的最开始,通过 effectScope 声明了一个 ref,并赋值给了state,我们将其 简单理解为声明了一个 ref 并赋值给 state

💡 effectScope 创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。对于该 API 的使用细节,请查阅对应的 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';

/**
 * 创建应用程序要使用的Pinia实例
 */
export function createPinia(): Pinia {
	console.log('🍍 createPinia run!');
	/**
	 * effectScope:
	 * 创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。对于该 API 的使用细节,请查阅对应的 RFC。
	 */
	const scope = effectScope(true);
	// NOTE: 在这里,我们可以检查窗口对象的状态,并直接设置它
	// 如果有类似Vue 3 SSR的东西
	const state = scope.run<Ref<Record<string, StateTree>>>(() => ref<Record<string, StateTree>>({}))!;

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

	// 使用 markRaw 包裹的 pinia 使其不会变为响应式
	const pinia: Pinia = markRaw({
		// app.use 执行的逻辑
		install(app: App) {
			// 设置当前使用的 pinia 实例
			setActivePinia(pinia);
			// 如果是 vue2 ,全局注册已经在 PiniaVuePlugin 完成,所以这段逻辑将跳过
			if (!isVue2) {
				// app 实例
				pinia._a = app;
				// 通过 provide 传递 pinia 实例,提供给后续使用
				app.provide(piniaSymbol, pinia);
				// 设置全局属性 $pinia
				app.config.globalProperties.$pinia = pinia;
				/* istanbul ignore else */
				if (USE_DEVTOOLS) {
					registerPiniaDevtools(app, pinia);
        }
				// 处理未执行插件
        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 阶段设置
		_e: scope,  // pinia 的作用域对象,每个 store 都有单独的 scope
		_s: new Map<string, StoreGeneric>(),  // store 缓存,key 为 pinia 的 id,value 为 pinia 对外暴漏的数据
		state,      // pinia 所有的 state 的合集,key 为 pinia 的 id,value 为 store 下所有的 state
	});

	// pinia devtools rely on dev only features so they cannot be forced unless
	// pinia开发工具依赖于仅用于开发的功能,因此除非
	// the dev build of Vue is used. Avoid old browsers like IE11.
	// 使用Vue的开发版本。避免使用像IE11这样的旧浏览器。
	if (USE_DEVTOOLS && typeof Proxy !== 'undefined') {
		pinia.use(devtoolsPlugin);
	}

	return pinia;
}

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_2


四、🧐源码分析(3)——defineStore

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_3

1. 三种创建方式

defineStore 所在位置: src/pinia/store.ts 进入文件之后可以看到通过函数重载的方式提供给我们三种参数传递方式:

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

其中参数含义如下:

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

  • options: 与 Vue 的选项式 API 类似,我们也可以传入一个带有 idstateactionsgetters属性的 Option 对象。

  • storeSetup:以 setup 的方式创建,与 Vue 的 setup 函数 相似。在 storeSetup 中:

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

2. defineStore 的执行逻辑

defineStore 函数中,并没有很特别的逻辑,首先是对三种创建方式进行兼容,然后定义一个名为 useStore 的函数,然后返回 useStore

useStore 具体做了什么下节分析。

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
        >
    
    // 此处对三种创建方式进行兼容处理
    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 函数返回出去,但不会立即调用,在组件内使用 store 时才会调用。
    // 所以在 defineStore 中只是做了些兼容逻辑,然后返回一个函数,返回的这个函数真正调用时才会触发更多逻辑。
    return useStore;
}

虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore() (或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的。

<script setup>
		import { useMainStore1 } from '@/stores/counter'
		// 可以在组件中的任意位置访问 `store` 变量 ✨
		const store = useMainStore1();
</script>

4. useStore

在之前我们分析了 defineStore 方法调用的时候返回了 useStore 方法,接下来看一下此方法究竟干了些什么。

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

	// 获取当前 vue 实例
	const currentInstance = getCurrentInstance();
	pinia =
		// 在 test 模式下,忽略提供的参数,因为我们总是可以通过 getActivePinia() 获取 pinia 实例
		// 如果 是test模式 && activePinia不为空 && activePinia是test模式 则为空 否则 返回参数中的pinia
		// 或者 如果获取到了当前实例 并且 存在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);

	// 将当前 pinia 实例设置为激活的 pinia
	// 如果存在多个 pinia 实例,方便后续逻辑获取当前pinia实例
	if (pinia) setActivePinia(pinia);

	// 在 dev环境 并且 获取不到当前 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 出错导致两个变量不一致
	pinia = activePinia!;

	// 如果 pinia 的 store 缓存中没有当前的 id,则创建新的 store,
	// 否则直接获取缓存中 store。
	if (!pinia._s.has(id)) {
		// 创建 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)!;

	// 开发环境 并且 是热更新
	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;
}

从上边代码中,可以发现最关键的两个函数是 createSetupStorecreateOptionsStore,分别是创建 组合式Store选项式Store。里边包含了创建 store 的关键逻辑,下面分别来看一下。

5. createSetupStore

createSetupStore 的作用是创建一个组合式的 store,之后的 createOptionsStore 其实也是把 option 转化后调用 createSetupStore 来创建 store。createSetupStore 的源码很长,我们分批研究。对于一些变量的定义等内容在此省略,只关注最核心逻辑,详细的注释可以查看 Github 中的源码。

(1) 参数

createSetupStore 总共接收了6个参数:

  • $id :当前 Store 的 ID,
  • setup defineStore 或者 createOptionsStore 传入的 setup 函数
  • options 配置选项,state、getter、actions 等
  • pinia Pinia 实例
  • hot 热更新相关
  • isOptionsStore 是否是 选项式 Store 创建
/**
 * 创建组合式 Store
 * @param $id Store ID
 * @param setup defineStore 传入的 setup 函数
 * @param options 配置选项
 * @param pinia Pinia 实例
 * @param hot 热更新相关
 * @param isOptionsStore 是否是 选项式 Store 创建
 * @returns 创建的 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) 创建 Store

此过程中,创建一个 setupStore 常量,创建了一个作用域并执行了 setup 函数,获取到 setup 函数中返回的内容,也就是我们定义的 state、getter、action 等内容。

在此过程中,state 的内容也会被存储到 pinia.state 中。action 则会被 wrapAction 处理。

对每一项 action 进行处理,目的是为了支持 $onAction 方法,此方法会在执行 action 时执行回调函数,回调函数可以接收三个参数分别是:被调用的 storeaction 的名字传递给 action 的参数。在 store 中还会有一些基础操作的 API ,请看下节。

// 在当前 pinia 实例的缓存中新建一个作用域,在作用域中执行 setup 函数
// 执行的结果为 store 。 example: { count: ObjectRefImpl, increment: Function () }
const setupStore = pinia._e.run(() => {
	scope = effectScope();
	return scope.run(() => setup());
})!;

// 覆盖现有操作以支持 $onAction
for (const key in setupStore) {
	const prop = setupStore[key];

	// ((如果是 ref) 并且 (不是 computed)) 或者 (是 reactive)
	if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
		// 如果是 optionsStore 方式创建,option 结构已经在 createOptionsStore 将其加入 pinia
		if (!isOptionsStore) {
			// 将 ref 转移到 pinia state 以保持一切同步
			if (isVue2) {
				set(pinia.state.value[$id], key, prop);
			} else {
				pinia.state.value[$id][key] = prop;
			}
		}
	// 否则,如果是函数类型,那么它就是一个 action
	} else if (typeof prop === 'function') {
		// 如果是重写这个值,应该避免使用 wrapAction 重复包装
		const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop);
		// 这是一个热更新模块替换 store,因为 hotUpdate 方法需要在正确的上下文中执行它
		if (isVue2) {
			set(setupStore, key, actionValue);
		} else {
			setupStore[key] = actionValue;
		}

		// 将 actions 存储到插件配置的 actions 数组,以便它们可以在插件中使用
		optionsForPlugin.actions[key] = prop;
	}
}

(3) 基础 API

在 Pinia 的 store 中存在很多基础 API,比如:获取 store id $id、增加 action 调用回调 $onAction()、重置 store $reset()、变更 store $patch()、订阅 $subscribe()、移除 store $dispose、获取所有 state $state 等。我们逐个分析。

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

基础的 API 首先被储存在 partialStore 中,然后创建一个 store 常量,并且把这些基础 API 和 store 的内容都合并到 store 常量中。

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

(4) Store 和 基础 API 合并

在 (2) 和 (3) 中我们创建了 store 的基本内容和基础的API,现在新建一个变量,并把它们合并到一块:

/**
 * 创建一个响应式的 store 对象
 * 将基础函数合并到 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)

现在,还缺少一个获取所有 state 得属性: $state ,我们使用 definePropertystore 增加 $state 属性 :

// 使用它而不是 computed with setter 可以在任何地方创建它,而无需将计算的生命周期链接到首次创建 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() 方法是对自定义插件的支持,在这里我们需要对安装的插件进行处理,调用左右的插件函数,并给函数传入 store app piain options 四个参数。

// apply 全部插件
pinia._p.forEach((extender) => {
	console.log("插件安装:", extender);
	// 如果使用开发工具
	/* istanbul ignore else */
	if (USE_DEVTOOLS) {
		const extensions = scope.run(() =>
			// 调用插件,并传入参数
			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('插件获取到的参数:', prop);
	return {
		$aaa: (param: string) => {
			console.log('这里是插件安装到 Pinia 上的功能');
			console.log('prop', prop);
			console.log('param', param);
		},
	};
});

app.use(pinia);

使用插件:

store.$aaa('传递的参数');

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

6. createOptionsStore

createOptionsStore 的代码量比较少,从下面的代码可以发现,基本的逻辑就是从 options 中获取到 stateactionsgetters,定义一个 setup 函数并调用 createSetupStore 创建 Store,还要将 getters 转换为 computed

/**
 * 创建 选项式 store
 * @param id Store ID
 * @param options 配置选项
 * @param pinia Pinia 实例
 * @param hot 热更新相关
 * @returns 创建的 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 函数
	 * @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 中创建 state
		const localState =
			__DEV__ && hot
				? // 使用 ref() 解包状态中的引用
				  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 转为 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;
}

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_3


五、🧐源码分析(4)—— store 的基础 API 实现

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_3

1. $id

这个没啥好说的,就是 createSetupStore 参数中的 $id

2. $onAction

设置一个回调,当一个 action 即将被调用时,就会被调用。 回调接收一个对象, 其包含被调用 action 的所有相关信息:

  • store: 被调用的 store
  • name: action 的名称
  • args: 传递给 action 的参数

除此之外,它会接收两个函数, 允许在 action 完成或失败时执行的回调。

它还会返回一个用来删除回调的函数。 请注意,当在组件内调用 store.$onAction() 时,除非 detached 被设置为 true, 否则当组件被卸载时,它将被自动清理掉。

在 Pinia 的源码中,关于 $onAction 的代码是这样的:

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

可以发现, $onAction 就是给 addSubscription 函数绑定了个 nullthis 和一个参数,再来看一下这个 addSubscription 是何方神圣:

export const noop = () => {};

/**
 * 添加订阅
 * @param subscriptions 订阅者数组
 * @param callback 回调
 * @param detached
 * @param onCleanup 当清楚订阅时的回调
 * @returns 清除订阅的回调
 */
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);
		// 如果存在这个订阅,在订阅数组中移除掉,并执行回调
		if (idx > -1) {
			subscriptions.splice(idx, 1);
			// 执行移除订阅回调
			onCleanup();
		}
	};

	// detached 为 true 时,在当前作用于停止时,不会删除此订阅,为 false 时会移除此订阅
	// getCurrentScope 如果有的话,返回当前活跃的 effect 作用域
	if (!detached && getCurrentScope()) {
		// onScopeDispose: 在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。
		onScopeDispose(removeSubscription);
	}

	// 返回移除订阅的函数
	return removeSubscription;
}

3. $patch

将一个 state 补丁应用于当前状态。允许传递嵌套值。

$patch 允许两种参数传递方式,传入一个函数,或一个 state 的补丁。

/**
 * $patch 函数传递方式
 * @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 = [];
	}
	// 对两种传参方式进行兼容
	// 如果参数是函数
	if (typeof partialStateOrMutator === 'function') {
		// 如果是函数,直接调用,并把 state 传过去
		partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>);
		// 收集订阅,分别保存类型、id、事件
		subscriptionMutation = {
			type: MutationType.patchFunction,
			storeId: $id,
			events: debuggerEvents as DebuggerEvent[],
		};
	} else {
		// 如果传来的是 object
		// merge 参数对象到当前 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,不会触发 $subscribe 中的 callback,所以需要手动进行订阅发布
	triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id] as UnwrapRef<S>);
}

其中 triggerSubscriptions 方法是发布者,执行订阅函数的回调:

/**
 * 触发订阅者回调
 * @param subscriptions 订阅数组
 * @param args 传给回调的参数
 */
export function triggerSubscriptions<T extends _Method>(subscriptions: T[], ...args: Parameters<T>) {
	subscriptions.slice().forEach((callback) => {
		callback(...args);
	});
}

4. $reset

通过建立一个新的状态对象,将 store 重设为初始状态。

/**
 * $reset
 * 只有 选项式 构建的才可以使用此方法,
 * 因为 state: () => ({count: 1}) 是一个函数,只要重新调用就可以获取原始值,
 * 而 组合式 构建的话 state 以 ref() 的形式实现,无法获取原始值。
 */
const $reset = isOptionsStore
	? function $reset(this: _StoreWithState<Id, S, G, A>) {
			const { state } = options as DefineStoreOptions<Id, S, G, A>;
			// 取出 options 中的 state 函数重新执行,以获取到原始 state
			const newState = state ? state() : {};
			// 使用 $patch 更新 state,并分发订阅
			this.$patch(($state) => {
				assign($state, newState);
			});
	  }
	: /* istanbul ignore next */
	__DEV__
	? () => {
			// 如果是组合式语法构建的话,抛出错误,因为 ref() 不能获取到原始值
			throw new Error(`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`);
	  }
	: // noop 是个空函数,生产环境不抛出错误
	  noop;

5. $subscribe

设置一个回调,当状态发生变化时被调用。它会返回一个用来移除此回调的函数。 请注意,当在组件内调用 store.$subscribe() 时,除非 detached 被设置为 true, 否则当组件被卸载时,它将被自动清理掉。

/**
 * 当状态发生变化时被调用
 * 它会返回一个用来移除此回调的函数
 * @param callback 回调
 * @param options 配置
 * @returns 返回一个取消订阅的函数,调用次函数时订阅就被取消了
 */
function $subscribe(callback, options = {}) {
	Log('$subscribe', options);
	// 取消订阅函数
	const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
	// effectScope:创建一个 effect 作用域,可以补货其中所创建的响应式副作用 (即计算属性和侦听器),这里用于捕获 watch,以便于销毁store的时候统一处理。
	const stopWatcher = scope.run(() =>
		// 从这里可以看出 pinia 的订阅响应式主要是依赖 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 函数可以查看2. $onAction 。

6. $dispose

停止 store 的相关作用域,并从 store 注册表中删除它。 插件可以覆盖此方法来清理已添加的任何副作用函数。 例如, devtools 插件停止显示来自 devtools 的已停止的 store。

/**
 * $dispose
 * 停止 store 的相关作用域,并从 store 注册表中删除它。
 * 插件可以覆盖此方法来清理已添加的任何副作用函数。 例如, devtools 插件停止显示来自 devtools 的已停止的 store。
 */
function $dispose() {
	scope.stop();
	subscriptions = [];
	actionSubscriptions = [];
	pinia._s.delete($id);
}

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_3


六、🧐源码分析(5)—— 辅助函数

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_4

Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数。你可以用和之前一样的方式来定义 Store。这里不做使用的介绍,用法请看官网:https://pinia.vuejs.org/zh/introduction.html

🚪源码目录: src/pinia/mapHelpers.ts

1. mapActions

mapActions 有两种传参方式,两种传参方式第一个参数都是 defineStore 中返回的 useStore 函数。

  • 传入一个对象,key 为映射到 methods 中的名字,value 为 action 的名字。
  • 传入一个数组,item 为 action 的名字。

下面是 mapActions 的源码,主要思想就是调用 useStore 方法得到 Store ,然后取出需要的 action 并返回。

/**
 * 这个方法需要传入 useStore 和一个对象,可以在导入过程中给 action 改名,对象 key 为 action 的新名字,value 为 action 的旧名字
 * 通过生成一个传递到组件的 methods 字段的对象, 允许直接使用 store 的 action,而不需要使用组合式 API(setup())。 该对象的值是 action, 而键是产生的方法名称。
 *
 * @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 和一个数组,数组内容为需要导入的 action 名称
 * 通过生成一个传递到组件的 methods 字段的对象, 允许直接使用 store 的 action,而不需要使用组合式 API(setup())。 该对象的值是 action, 而键是产生的方法名称。
 *
 * @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 名称数组
 */
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, 而键是产生的方法名称。
 *
 * @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)
    // 如果传入的是数组,遍历这个数组取出所有 action 名称
    ? keysOrMapper.reduce((reduced, key) => {
        // @ts-expect-error
        reduced[key] = function (
          // 如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 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 定义的列表参数。

/**
 * 通过生成一个对象,传递到组件的 computed 字段 以允许在不使用组合式 API(setup())的情况下使用 store。 它接受一个 store 定义的列表参数。
 *
 * @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[]>(
  // 所有参数放入 stores 数组,所以 store 不需要在包裹一层数组
  ...stores: [...Stores]
): _Spread<Stores> {
  // 直接将 store 通过参数传递即可,不需要放到数组中,如果放到了数组中就抛出警告
  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 并执行,然后 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, 而键是生成的计算属性名称。 你也可以选择传递一个自定义函数,该函数将接收 store 作为其第一个参数。 注意,虽然它可以通过 this 访问组件实例,但它没有标注类型。

/**
 * 通过生成一个对象,并传递至组件的 computed 字段, 以允许在不使用组合式 API(setup())的情况下使用一个 store 的 state 和 getter。 该对象的值是 state 属性/getter, 而键是生成的计算属性名称。 你也可以选择传递一个自定义函数,该函数将接收 store 作为其第一个参数。 注意,虽然它可以通过 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 的数组
 */
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数组,内容仅限于 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 的区别:mapAction 取出的是经过 wrapAction 的 action ,然后在这调用了一下
            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 的类型推断为函数
            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 导入 computed 时,如果直接去修改 state 的值是不允许的。

深入 Pinia:从代码出发探索 Vue 状态管理的奥秘

$mapWritableState ********除了创建的计算属性的 setter,其他与 mapState() 相同, 所以 state 可以被修改。 与 mapState() 不同的是,只有 state 属性可以被添加。

/**
 * 除了创建的计算属性的 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> {
  // 也是对于数组和对象的分别处理
  // 返回包含 get 和 set 函数的对象,交给 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>)
}

🧑🏼‍💻 本节代码:https://github.com/mk965/read-pinia/tree/article_4


✨结语

代码虽然比较多,但核心逻辑还是借助 vue 的 ref 和 reactive 实现响应式。把 state 处理为 ref ,把 getters 处理成 computed ,提供一些基础方法,并使用单例模式返回一个实例。

完整注释代码:

🍍 项目地址:https://github.com/mk965/read-pinia

到了这里,关于深入 Pinia:从代码出发探索 Vue 状态管理的奥秘的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue使用 Pinia 进行状态管理

    Pinia 是一个适用于 Vue.js 的状态管理库,它采用了组合式 API 的理念,使得状态管理变得更加简单、直观和灵活。与传统的 Vuex 相比,Pinia 提供了更好的 TypeScript 支持,同时也更加适合大型应用程序和复杂状态逻辑的管理。 首先,我们需要在 Vue 项目中安装 Pinia。你可以通过

    2024年02月13日
    浏览(63)
  • vue3学习-Pinia状态管理

    定义一个Store 这个 name ,也称为 id ,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use… 是跨可组合项的约定,以使其符合你的使用习惯。 使用 store 一旦 store 被实例化,你就可以直接在 store 上访问 state 、 getters 和 actions 中定义的任何属性 store 是一

    2024年02月14日
    浏览(38)
  • Vue | Vue.js 全家桶 Pinia状态管理

    🖥️ Vue .js专栏:Node.js Vue.js 全家桶 Pinia状态管理 🧑‍💼 个人简介:一个不甘平庸的平凡人🍬 ✨ 个人主页:CoderHing的个人主页 🍀 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️ 👉 你的一键三连是我更新的最大动力❤️ 目录 一、Pinia和Vuex的对比 什么是Pinia呢? Pina和

    2024年01月16日
    浏览(50)
  • Vue最新状态管理工具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/ 最近在 前端的深入学习过程 中,接触了与 状态管理 相关的内容,涉及到 与vue2适配的vuex 和 与vue3适配的pinia ,因此

    2024年02月03日
    浏览(50)
  • vue3 快速入门系列 —— 状态管理 pinia

    其他章节请看: vue3 快速入门 系列 vue3 状态管理这里选择 pinia。 虽然 vuex4 已支持 Vue 3 的 Composition API,但是 vue3 官网推荐新的应用使用 pinia —— vue3 pinia redux、mobx、vuex、pinia都是集中式状态管理工具。与之对应的就是分布式。 Pinia 符合直觉 的 Vue.js 状态管理库 甚至让你忘

    2024年04月26日
    浏览(35)
  • vue:状态管理库及其部分原理(Vuex、Pinia)

    多组件的状态共享问题: 当多个组件需要访问和修改相同的数据时,我们需要在组件之间传递 props或者使用事件总线。当,应用就会变得难以维护和调试。 多组件状态同步问题: 当一个组件修改了状态,其他组件可能无法立即得知该变化。 状态变更的追踪问题: 无法追踪

    2024年01月19日
    浏览(48)
  • vue 全局状态管理(简单的store模式、使用Pinia)

    多个组件可能会依赖同一个状态时,我们有必要抽取出组件内的共同状态集中统一管理,存放在一个全局单例中,这样任何位置上的组件都可以访问其中的状态或触发动作 通过自定义一个store模式实现全局的状态管理,实例如下 有两个组件a、b共享store和store2两个状态,我们

    2024年02月13日
    浏览(50)
  • vue 3 第三十一章:状态管理(Pinia基础知识)

    状态管理是现代 Web 应用开发中的一个重要概念。Vue 3 中的状态管理库 Pinia ,是一个基于 Vue 3 Composition API 的状态管理库,它提供了一种 简单 、 灵活 的方式来管理应用程序的状态,同时还具有 高性能 和 可扩展性 。 Pinia 在某种程度上来说,也可以被叫做 Vuex5 ,因为它结合

    2024年02月07日
    浏览(90)
  • Vue3状态管理库Pinia——自定义持久化插件

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,正逐渐往全干发展 📃 个人状态: 研发工程师,现效力于中国工业软件事业 🚀 人生格言: 积跬步至千里,积小流成江海 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒

    2024年02月13日
    浏览(34)
  • vue2(Vuex)、vue3(Pinia)、react(Redux)状态管理

    Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。它使用集中式存储管理应用的所有组件的状态,以及规则保证状态只能按照规定的方式进行修改。 State(状态) :Vuex 使用单一状态树,即一个对象包含全部的应用层级状态。这个状态树对应着一个应用中的所有状态。 Gett

    2024年01月23日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包