WinddSnow

Pinia-State-Management-Part2-Plugins-Persistence-TypeScript-Integration-Compo...

字数统计: 5k阅读时长: 22 min
2026/08/01

第111课:Pinia 状态管理(下)——插件、持久化、与 TypeScript 集成、组合式 Store

上一课我们掌握了 Pinia 的核心三要素:stategettersactions,并通过 Setup Store 语法构建了响应式的购物车模块。在实际项目中,状态管理还需要解决三个进阶问题:如何将关键状态持久化到本地存储以在刷新后恢复?如何为每个 Store 添加通用逻辑(如日志、错误处理)而不侵入业务代码?如何在 TypeScript 中获得从定义到使用的全链路类型安全?Pinia 通过插件系统内置持久化中间件pinia-plugin-persistedstate)以及对 TypeScript 的一流支持优雅地解决了这些问题。本节课将逐一深入这三个进阶主题,并展示如何将多个 Store 组合为可扩展的架构,最终通过一个带认证和购物车的实战案例将所有知识点串联。


1. Pinia 插件系统:为所有 Store 注入通用能力

Pinia 插件是一个函数,它接收一个 context 对象,可以在每个 Store 实例化时执行自定义逻辑。插件能做的事情包括:为所有 Store 添加公共属性或方法、监听 $patch 变更进行日志记录、与外部持久化库集成、甚至在 state 变化时触发副作用。插件通过 pinia.use(plugin) 注册。

1.1 插件的结构与注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// main.js
import { createPinia } from 'pinia';

const pinia = createPinia();

// 注册一个简单的日志插件
pinia.use((context) => {
// context 包含以下属性:
// - context.pinia: Pinia 实例
// - context.app: Vue 应用实例
// - context.store: 当前被创建的 Store 实例
// - context.options: 定义该 Store 时传入的选项

const { store } = context;

// 监听 state 变更
store.$subscribe((mutation, state) => {
console.log(`[${mutation.storeId}] state changed:`, mutation.events);
});

// 监听 action 调用
store.$onAction(({ name, args, after, onError }) => {
console.log(`[${store.$id}] action "${name}" called with`, args);
after((result) => {
console.log(`[${store.$id}] action "${name}" resolved with`, result);
});
onError((error) => {
console.error(`[${store.$id}] action "${name}" failed:`, error);
});
});
});

关键机制

  • 插件函数在每个 Store 第一次被调用 useStore()触发。它在 Store 实例创建后、暴露给组件之前执行。
  • store.$subscribe(callback) 类似于 Vue 的 watch,监听 state 变化。mutation.events 包含本次变更的详细信息(key、newValue、oldValue)。
  • store.$onAction(callback) 是一个 action 拦截器。它在 action 执行前触发,通过 afteronError 回调可以分别在 action 成功或失败后执行逻辑。这比直接包装 action 更优雅,因为它与 action 的具体实现解耦。

1.2 通过插件为所有 Store 添加公共属性

插件可以直接向 store 对象上挂载属性或方法,这些属性会被添加到每个 Store 实例上。为了避免与 Store 自身的 state/getters/actions 命名冲突,社区约定插件注入的属性通常以 $ 前缀命名。

1
2
3
4
5
6
7
// 插件:为所有 Store 添加一个 toast 方法
pinia.use(({ store }) => {
store.$toast = (message, type = 'info') => {
console.log(`[${type}] ${message}`);
// 实际项目中这里可以调用全局的 toast 组件
};
});

在任何 Store 内部即可通过 this.$toast 或在组件中通过 store.$toast 调用。

1.3 插件的典型应用场景

场景 实现方式
日志记录 store.$subscribe + store.$onAction
错误统一处理 $onActiononError 回调中上报错误
持久化 store.$subscribe 将 state 写入 localStorage
权限校验 在 action 执行前检查用户权限
全局状态快照 使用 store.$state 配合 watch 进行定期备份

2. 持久化:pinia-plugin-persistedstate

Web 应用中经常需要将某些 Store 的状态保存到 localStoragesessionStorage,以便在页面刷新后恢复。手动在每个 Store 中添加 watchlocalStorage 读写逻辑繁琐且重复。**pinia-plugin-persistedstate** 是 Pinia 官方推荐的持久化插件,通过简单的配置即可为任意 Store 开启自动持久化。

2.1 安装与启用

1
npm install pinia-plugin-persistedstate
1
2
3
4
5
6
// main.js
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

2.2 在 Store 中使用

在 Setup Store 中,通过 defineStore 的第三个参数(选项对象)配置持久化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// stores/auth.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useAuthStore = defineStore(
'auth',
() => {
const token = ref(null);
const user = ref(null);

const isAuthenticated = computed(() => !!token.value);

function login(newToken, userData) {
token.value = newToken;
user.value = userData;
}

function logout() {
token.value = null;
user.value = null;
}

return { token, user, isAuthenticated, login, logout };
},
{
persist: true, // 开启持久化,使用默认配置(存储到 localStorage)
}
);

默认情况下,persist: true 会将整个 Store 的所有 state 持久化到 localStorage 中,键名为 Store 的 ID(此处为 'auth')。

2.3 自定义持久化行为

通过 persist 对象可以精细控制持久化策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
persist: {
key: 'my-auth', // 存储键名(默认使用 Store ID)
storage: sessionStorage, // 存储介质(默认 localStorage),也可传入自定义存储对象
pick: ['token'], // 白名单:仅持久化这些 state 属性
omit: ['user'], // 黑名单:排除这些 state 属性(与 pick 二选一)
beforeRestore: (ctx) => { // 恢复数据前的回调
console.log('即将恢复状态:', ctx);
},
afterRestore: (ctx) => { // 恢复数据后的回调
console.log('状态已恢复:', ctx.store.$state);
},
serializer: { // 自定义序列化器(默认使用 JSON)
serialize: JSON.stringify,
deserialize: JSON.parse,
},
}
}

关键选项

  • pickomit 用于精细控制哪些 state 被持久化。敏感信息(如临时 UI 状态、加载标志、错误信息)应该通过 omit 排除,避免刷新后显示过期的“加载中”或“错误提示”。
  • storage 可以传入 sessionStorage(仅当前会话有效),或实现 getItem/setItem/removeItem 的自定义存储对象(如用于 React Native 的 AsyncStorage)。
  • serializer 允许自定义序列化逻辑,例如使用 @nuxt/devalue 处理 JSON 无法序列化的类型(DateBigIntundefined 等)。

2.4 手动触发持久化与清空

插件为每个 Store 添加了 $hydrate()$persist() 方法(可选使用):

1
2
3
4
5
const store = useAuthStore();
// 手动重新从存储中恢复状态
store.$hydrate();
// 手动将当前状态写入存储
store.$persist();

此外,store.$reset() 会同时清空内存状态和持久化存储中的数据。


3. TypeScript 集成:从 Store 定义到组件使用的全链路类型安全

Pinia 专为 TypeScript 设计,Setup Store 语法中的 refcomputed 自动推导类型,几乎无需手动标注。然而,在以下场景中,你需要了解如何精确地提供类型信息。

3.1 Store 的完整类型定义

在 Setup Store 中,返回对象的类型就是 Store 的类型。Pinia 会自动从返回值推导出 Store 的类型。如果你希望显式导出类型(例如在 index.ts 中统一导出),可以使用 ReturnType

1
2
3
4
5
6
7
8
9
10
11
12
13
// stores/counter.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const double = computed(() => count.value * 2);
function increment() { count.value++; }
return { count, double, increment };
});

// 显式导出 Store 类型(可选,通常不需要)
export type CounterStore = ReturnType<typeof useCounterStore>;

3.2 在组件中使用时无需额外标注

1
2
3
4
5
6
7
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter';

const counter = useCounterStore();
// counter.count 的类型自动推导为 number
// counter.increment 的类型自动推导为 () => void
</script>

由于 TypeScript 的类型推导能力,useCounterStore() 的返回值类型被自动推断为 Store<"counter", { count: Ref<number>; double: ComputedRef<number>; increment: () => void; }>(内部经过 Pinia 的类型转换,在组件中访问时 Ref 自动解包,counter.count 的类型为 number)。你不需要手动声明类型。

3.3 在 Action 中使用 this 的类型

Setup Store 中没有 this——所有变量都在 setup 函数的闭包内。因此不需要像 Options Store 那样关心 this 类型。这也是 Setup Store 在 TypeScript 下更自然的原因之一。

3.4 为 Getters 和 Actions 添加 JSDoc 类型注释

即使是纯 TypeScript 环境,JSDoc 注释也有助于编辑器提供更好的智能提示和文档。推荐为所有导出的函数添加 JSDoc:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 添加商品到购物车。
* 若商品已存在,则增加其数量;否则新增条目。
* @param product - 商品对象,需包含 id、name、price
*/
function addItem(product: { id: number; name: string; price: number }) {
const existing = items.value.find(item => item.id === product.id);
if (existing) {
existing.quantity += 1;
} else {
items.value.push({ ...product, quantity: 1 });
}
}

4. 组合式 Store:按业务领域拆分与组合

大型应用中,单个 Store 如果包罗万象,会变得臃肿且难以维护。Pinia 鼓励按业务领域拆分 Store,然后在需要的地方自由组合它们。这种“多 Store”架构天然避免了 Vuex 的模块嵌套和命名空间问题。

4.1 拆分原则

  • 一个 Store 负责一个业务领域useAuthStore 管理认证,useCartStore 管理购物车,useProductStore 管理商品列表。
  • Store 之间可以互相引用:在 useCheckoutStore 中可以直接调用 useCartStore().totalPriceuseAuthStore().user。这比 Vuex 的 rootGetters['cart/totalPrice'] 更直观、类型安全。
  • 公共工具函数提取为 Composable:如果多个 Store 需要共享一段逻辑(如防抖、格式化),应提取为 composable 函数,而非放在某个 Store 中让其他 Store 引用。Store 应专注于状态管理,通用工具函数放在 composables/ 目录下。

4.2 示例:认证 Store + 购物车 Store 协作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// stores/auth.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(null);
const user = ref<{ id: number; name: string; role: string } | null>(null);

const isAuthenticated = computed(() => !!token.value);
const isAdmin = computed(() => user.value?.role === 'admin');

async function login(username: string, password: string) {
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error('登录失败');
const data = await res.json();
token.value = data.token;
user.value = data.user;
}

function logout() {
token.value = null;
user.value = null;
}

return { token, user, isAuthenticated, isAdmin, login, logout };
}, { persist: { pick: ['token', 'user'] } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// stores/cart.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}

export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([]);

const totalPrice = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2)
);

function addItem(product: Omit<CartItem, 'quantity'>) { /* 实现同上 */ }
function removeItem(id: number) { /* 实现同上 */ }
function clearCart() { items.value = []; }

return { items, totalPrice, addItem, removeItem, clearCart };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// stores/checkout.ts
import { defineStore } from 'pinia';
import { computed } from 'vue';
import { useCartStore } from './cart';
import { useAuthStore } from './auth';

export const useCheckoutStore = defineStore('checkout', () => {
const cart = useCartStore();
const auth = useAuthStore();

const canCheckout = computed(() =>
auth.isAuthenticated && cart.items.length > 0
);

async function checkout() {
if (!canCheckout.value) throw new Error('无法结算');

const order = {
userId: auth.user!.id,
items: cart.items,
total: cart.totalPrice,
};

await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${auth.token}`,
},
body: JSON.stringify(order),
});

cart.clearCart();
}

return { canCheckout, checkout };
});

关键模式

  • useCheckoutStore 在 setup 函数中直接调用 useCartStore()useAuthStore()。这种 Store 间的组合是 Pinia 的常态——每个 Store 是一个独立的 JavaScript 模块,可以在任何地方被导入和使用。
  • 认证 Store 配置了持久化,仅保存 tokenuser(不保存其他临时状态),刷新后用户自动恢复登录态。
  • 结算 Store 不暴露自己的 state(当前示例中仅返回 computed 和 action),它更像是一个“业务逻辑聚合器”,协调其他两个 Store。

5. 综合实战:带认证的购物车应用

以下代码整合了认证持久化、购物车管理和结算逻辑,展示了一个完整的 Pinia 多 Store 架构。

1
2
3
4
5
6
7
8
9
10
// main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import App from './App.vue';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

createApp(App).use(pinia).mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- App.vue -->
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
import { useCartStore } from '@/stores/cart';
import { useCheckoutStore } from '@/stores/checkout';

const auth = useAuthStore();
const cart = useCartStore();
const checkout = useCheckoutStore();
</script>

<template>
<div class="app">
<header>
<h1>🛒 Pinia Shop</h1>
<div class="header-right">
<span v-if="auth.isAuthenticated">
{{ auth.user?.name }}
<button @click="auth.logout">退出</button>
</span>
<button v-else @click="auth.login('user', 'pass')">登录</button>
<span class="cart-badge">购物车: {{ cart.items.length }} 件</span>
</div>
</header>

<main>
<section class="products">
<ProductList />
</section>

<section class="cart-panel">
<h2>购物车</h2>
<CartList />
<div v-if="checkout.canCheckout">
<button @click="checkout.checkout()">结算</button>
</div>
</section>
</main>
</div>
</template>

测试持久化

  1. 登录应用(auth.login 模拟登录)后,刷新页面——tokenuser 被持久化,无需重新登录。
  2. 将商品加入购物车,刷新页面——购物车数据持久化(cart Store 未配置 persist),购物车被清空。若需要持久化购物车,只需在 cart Store 中添加 { persist: true } 配置即可。

6. Pinia 架构最佳实践总结

实践 说明
优先使用 Setup Store 与 Vue 3 Composition API 心智模型一致,TypeScript 类型推导最佳。
按业务领域拆分 Store 一个 Store 一个职责,避免巨型 Store。
Store 间自由组合 直接在 Store 的 setup 函数中调用其他 useXxxStore()
敏感信息配置 pick/omit 持久化时仅保存必要数据,排除 loadingerror 等临时状态。
插件实现横切关注点 日志、错误上报、全局 toast 等跨 Store 逻辑应通过插件实现。
工具函数提取为 Composable 避免 Store 之间通过“公共 Store”共享工具函数——应使用 composable。
TypeScript 类型推导 无需手动导出 Store 类型,直接使用 useStore() 的返回值类型。

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 在 Pinia 插件中,要监听 action 的调用和结果,应使用哪个方法?
    A. store.$subscribe
    B. store.$patch
    C. store.$onAction
    D. store.$reset

  2. (单选) pinia-plugin-persistedstate 默认将 state 持久化到哪种存储介质?
    A. sessionStorage
    B. localStorage
    C. IndexedDB
    D. Cookie

  3. (填空) 在持久化配置中,要仅保存部分 state 属性,应使用 ______ 选项指定白名单。

  4. (多选) 关于 Pinia 与 TypeScript 集成的描述,哪些是正确的?
    A. Setup Store 中 ref 的类型会自动推导,无需手动标注。
    B. 需要手动导出 Store<"id", { ... }> 类型才能在组件中使用。
    C. 组件中通过 useStore() 获取的 Store 实例具有完整类型推导。
    D. 在 action 中可以使用 this 访问 state,且类型安全。

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要为 Pinia 创建一个全局错误处理插件。要求如下:

  • 插件应监听所有 Store 的 action 调用。
  • 当任何 action 抛出异常时,插件应:
    • 在控制台输出 [StoreName] action "actionName" failed: + 错误信息。
    • 将错误存储到一个全局的错误队列 Store(useErrorStore)中,供 UI 组件消费。
  • 同时,提供 clearErrors() action 用于清空错误列表。
  • 使用 TypeScript 编写,插件注册在 main.ts 中,useErrorStore 独立为一个 Store 文件。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 Pinia 插件代码和 useErrorStore 的完整实现。提示词中需明确指定 $onAction 的回调参数、错误存储结构和清空逻辑。

三、Agent 模式下的提示词示例

你是一个资深前端开发 Agent。请为一个 Vue 3 + Pinia + TypeScript 项目创建全局错误处理插件和错误 Store。需要创建以下文件:

  1. src/stores/error.ts:使用 Setup Store 语法定义 useErrorStore。State 包含 errorsref<Array<{ id: number; storeId: string; actionName: string; message: string; timestamp: number }>>([]))。Actions 包含 addError(storeId: string, actionName: string, message: string)(push 新错误对象,id 为 Date.now())和 clearErrors()(重置为 [])。Getters 包含 hasErrorscomputed(() => errors.value.length > 0))和 latestError(返回 errors.value[errors.value.length - 1]null)。
  2. src/plugins/errorLogger.ts:导出一个 Pinia 插件函数。使用 ({ store }) 参数,调用 store.$onAction(({ name, onError }) => { onError((error) => { const errorStore = useErrorStore(); errorStore.addError(store.$id, name, error.message); console.error([${store.$id}] action “${name}” failed:, error); }); })
  3. src/main.ts:引入 Pinia、piniaPluginPersistedstate(如有需要)和 errorLogger 插件。创建 Pinia 实例后 pinia.use(errorLogger)
  4. 所有代码添加 JSDoc 注释,确保 TypeScript 类型安全。完成后列出所有文件内容。

四、面试真题与参考答案

题目(蚂蚁集团前端面试题):

请解释 Pinia 插件的设计模式和典型的应用场景。如果你需要实现一个“未保存更改”的全局拦截器(即当用户离开页面时,如果某个 Store 中有未保存的数据,弹出确认框),你会如何设计这个插件?请给出核心实现思路和关键代码片段。

参考答案

Pinia 插件是一个接收 context 的函数,在每个 Store 创建时被调用。通过 context.store 可以访问当前 Store 实例,从而注入公共属性(如 $toast)、监听状态变化($subscribe)、拦截 action($onAction)或与外部系统交互。典型应用包括:日志记录、错误上报、持久化、权限校验、全局行为注入等。

设计“未保存更改”拦截器的核心思路:

  1. 在每个 Store 中统一维护一个 isDirty 状态(标记是否有未保存的更改)。可以通过插件在 Store 创建时自动添加 isDirty 属性,或者让各 Store 自行声明(后者更灵活)。
  2. store.$subscribe 中,当 state 发生变化(且非通过 $patch 从外部恢复),将 isDirty 设为 true。当 action 成功保存后,将 isDirty 设为 false
  3. 注册全局的 window.beforeunload 事件(浏览器关闭/刷新拦截),以及 Vue Router 的 beforeEach 导航守卫(页面内路由切换拦截),遍历所有 Store 检查是否有 isDirty === true。若有,弹出确认框。若用户确认离开,通过插件提供的全局方法将所有 Store 的 isDirty 设为 false 以避免多次提示。
  4. 插件暴露一个 registerStore(store) 方法,由各 Store 在 setup 中主动注册自己。或者插件通过 pinia.state.value 遍历所有已创建的 Store 实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 插件核心代码示意
pinia.use(({ store }) => {
store.isDirty = false;

store.$subscribe(() => {
store.isDirty = true;
});

const originalReset = store.$reset;
store.$reset = function (...args) {
store.isDirty = false;
return originalReset.apply(this, args);
};
});

// 全局拦截
window.addEventListener('beforeunload', (e) => {
const allStores = /* 获取所有 Store 实例 */;
if (allStores.some(s => s.isDirty)) {
e.preventDefault();
e.returnValue = '';
}
});

关键设计在于将“是否脏”的状态管理权交给插件,而不是在每个 Store 中重复编写相同逻辑。同时,通过 $subscribe 自动追踪变更,减少了开发者手动维护 isDirty 的心智负担。


课后练习答案

一、概念自测答案

  1. C

    • 解析:$onAction 用于监听 action 调用,可通过 afteronError 处理成功和失败。$subscribe 监听 state 变化,$patch 修改 state,$reset 重置 state。
  2. B

    • 解析:默认使用 localStorage,可通过 storage 选项修改为 sessionStorage 或自定义存储。
  3. pick

    • 解析:pick 指定白名单,仅持久化这些字段;omit 指定黑名单,排除这些字段。
  4. A、C

    • 解析:B 错误,Pinia 自动推导类型,无需手动导出 Store 类型。D 错误,Setup Store 中没有 this,所有状态通过闭包访问。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请用 TypeScript 编写一个 Pinia 插件和一个 useErrorStore,实现全局 action 错误捕获和存储。要求:

  • useErrorStore:state 包含 errors 数组,actions addError 和 clearErrors,getters hasErrors 和 latestError。
  • 插件:在 store.$onAction 的 onError 中调用 useErrorStore().addError 并 console.error。
  • main.ts 中注册插件。
  • 所有代码添加 JSDoc,确保类型安全。输出三个文件的内容。”
CATALOG
  1. 1. 第111课:Pinia 状态管理(下)——插件、持久化、与 TypeScript 集成、组合式 Store
    1. 1.1. 1. Pinia 插件系统:为所有 Store 注入通用能力
      1. 1.1.1. 1.1 插件的结构与注入
      2. 1.1.2. 1.2 通过插件为所有 Store 添加公共属性
      3. 1.1.3. 1.3 插件的典型应用场景
    2. 1.2. 2. 持久化:pinia-plugin-persistedstate
      1. 1.2.1. 2.1 安装与启用
      2. 1.2.2. 2.2 在 Store 中使用
      3. 1.2.3. 2.3 自定义持久化行为
      4. 1.2.4. 2.4 手动触发持久化与清空
    3. 1.3. 3. TypeScript 集成:从 Store 定义到组件使用的全链路类型安全
      1. 1.3.1. 3.1 Store 的完整类型定义
      2. 1.3.2. 3.2 在组件中使用时无需额外标注
      3. 1.3.3. 3.3 在 Action 中使用 this 的类型
      4. 1.3.4. 3.4 为 Getters 和 Actions 添加 JSDoc 类型注释
    4. 1.4. 4. 组合式 Store:按业务领域拆分与组合
      1. 1.4.1. 4.1 拆分原则
      2. 1.4.2. 4.2 示例:认证 Store + 购物车 Store 协作
    5. 1.5. 5. 综合实战:带认证的购物车应用
    6. 1.6. 6. Pinia 架构最佳实践总结
    7. 1.7. 课后练习
      1. 1.7.1. 一、概念自测(选择题 / 填空题)
      2. 1.7.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.7.3. 三、Agent 模式下的提示词示例
      4. 1.7.4. 四、面试真题与参考答案
    8. 1.8. 课后练习答案
      1. 1.8.1. 一、概念自测答案
      2. 1.8.2. 二、AI 编程任务参考答案(提示词示例)