Pinia学习笔记之--核心概念Plugins and TypeScript

本文介绍了Pinia如何使用插件进行扩展,包括为store添加新属性、选项、方法,改变actions,实现副作用等。通过`pinia.use()`安装插件,可以返回一个对象向所有store添加静态属性,或者直接在store中设置属性。同时,文章讨论了如何在插件中读取和处理store的自定义选项,如创建debounce选项来防抖函数。此外,还提到了如何使用TypeScript对这些插件和新属性进行类型定义。

Plugins

多亏了一个低级API, Pinia stores可以完全扩展。下面是一些你可以做的事情清单:

  • stores增加新属性
  • 在定义stores时添加新选项
  • stores增加新方法
  • 包装现有的方法
  • 改变甚至取消actions
  • 实现像Local Storage一样的副作用
  • 仅应用于特定的stores

使用pinia.use()将插件添加到pinia实例中。最简单的例子是通过返回一个对象向所有stores添加一个静态属性:

import { createPinia } from 'pinia'
// 为每个store增加一个名为`secret`的属性,在插件被安装创建之后
// 可以在不同的文件中
function SecretPiniaPlugin() {
    return { secret: 'the cake is a lie' }
}
const pinia = createPinia()
// 给pinia传入插件
pinia.use(SecretPiniaPlugin)

// 在其他文件中
const stroe = useStore()
store.secret // the cake is a lie

介绍

一个Pinia插件是一个函数,可以选择返回要添加到store中的属性,它接收一个可选参数,context

export function myPiniaPlugin(context) {
  context.pinia // 通过`createPinia()`创建的pinia
  context.app // 通过`createApp()`创建的当前实例 (Vue 3 only)
  context.store // 插件的store库
  context.options // 在定义 store时传给 `defineStore()`的参数
  // ...
}

这个函数被通过pinia.use()传递给pinia:

pinia.use(myPiniaPlugin)

Plugins只应用于stroes创建后,pinia被传递到应用程序,否则他们不会被应用。

增加Store

你可以通过在插件中简单地返回一个属性对象来为每个store添加属性:

pinia.use(() => ({ hello: 'world' }))

你也可以直接在store中设置属性,但是如果可能的话,使用return的返回版本,以便他们在devtools中自动被跟踪:

pinia.use(({ store } => {
    store.hello = 'world'
}))

插件返回的任何属性都会被devtools自动跟踪,所以为了让hellodevtools中可见,如果你想在devtools中调试,确保在开发模式下增加store._customProperties

pinia.use(({ store }) => {
    store.hello = 'world'
    // 请确保您的绑定程序处理此问题。Webpack和vite在默认情况下应该会这样做
    if(process.env.NODE_ENV === 'development') {
        // 添加任何你想在store中调试的key
        store._customProperties.add('hello')
    }
})

需要注意的是,每个store都会使用响应式包装,并且会自动打开它包含的任何Ref ``(Ref (), computed(),…):

const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // 每个store都有一个单独的hello
  store.hello = ref('secret')
  // 他会自动解包
  store.hello // 'secret'

  // 所有的store将会共享`shared`的值
  store.shared = sharedRef
  store.shared // 'shared'
})

这就是为什么你可以接收所有的计算属性而不用.value和为什么他们是响应式的。

添加新的状态

可以在定义store时创建新的选项,以便以后从插件中使用它们。例如,你可以创建一个debounce选项,允许你对任何操作进行debounce:

defineStore('search', {
    actions: {
        searchContacts() {
            // ...
        },
    },
    // 在后续的插件中可以被读取
    debounce: {
        // 防抖函数可以将searchContacts延迟300ms
        searchContacts: 300
    }
})

插件可以读取这个选项,重新包装替代原始的值:

// 使用第三方库
import debounce from 'lodash/debunce'
pinia.use(({ options, store }) => {
    if (options.debounce) {
        // 可以重写actions
        return Object.keys(options.debounce).reduce((debouncedActions, action) => {
            debouncedActions[action] = debounce(
                store[action],
                options.debounce[action]
            )
            return debouncedActions
        }, {})
    }
})

请注意,当使用setup语法时,自定义参数作为第三个参数被传入:

defineStore(
    'search',
    () => {
        // ...
    },
    {
        // 在后续的插件中可以被读取
        debounce: {
            // 防抖函数可以将searchContacts延迟300ms
            searchContacts: 300
        }
    }
)

TypeScript

上面展示的一切都可以被类型支持,所以你不需要使用any或者@ts-ignore

Typing plugins

Pinia插件的类型如下:

import { PiniaPluginContext } from 'pinia'
export function myPiniaPlugin(context: PiniaPluginContext) {
    // ...
}

新属性类型

当你为stores增加一个新属性时,你也应该为PiniaCustomProperties拓展一个接口类型。

import 'pinia'
declare module 'pinia' {
    export interface PiniaCustomProperties {
        // 通过使用setter,我们可以同时允许string和refs
        set hello(value: string | Ref<string>)
        get hello(): string
        // 也可以定义简单值
        simpleNumber: number
    }
}

它可以被重写和安全读取

pinia.use(({ store }) => {
    store.hello = 'Hola'
    store.hello = ref('Hola')
    store.simpleNumber = Math.random()
    // 错误的类型,报错
    store.simpleNumber = ref(Math.random())
})

PiniaCustomProperties是一个泛型类型,允许您引用store的属性。想象一下下面的例子,我们将初始选项复制为$options(这只适用于选项stores):

pinia.use(({ options }) => ({ $options: options }))

我们可以通过使用PiniaCustomProperties的4个泛型类型来正确地输入它:

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties<Id, S, G, A> {
    $options: {
      id: Id
      state?: () => S
      getters?: G
      actions?: A
    }
  }
}

Tip

当在泛型中扩展类型时,它们的命名必须与源代码中的名称完全一致。Id不能命名为idIS不能命名为State。以下是每个字母所代表的含义:

  • S: State
  • G: Getters
  • A: Actions
  • SS: Setup Store / Store

新状态类型

当添加新的状态属性时(同时添加到storestore.$state),您需要将类型添加到PiniaCustomStateProperties。与PiniaCustomProperties不同的是,它只接收State泛型:

import 'pinia'
declare module 'pinia' {
    export interface PiniaCustomStateProperties<S> {
        hello: string
    }
}

创建新options的类型

当为defineStore()创建新选项时,您应该扩展DefineStoreOptionsBase。与PiniaCustomProperties不同的是,它只公开两种泛型:StateStore类型,允许您限制可以定义的类型。例如,你可以使用action的名称:

import 'pinia'

declare module 'pinia' {
  export interface DefineStoreOptionsBase<S, Store> {
    // allow defining a number of ms for any of the actions
    debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  }
}

Tip
还有一个StoreGetters类型用于从Store类型中提取getter。您还可以通过分别扩展DefineStoreOptionsDefineSetupStoreOptions类型来扩展setup storesoption stores的选项。

<think>好的,我现在需要回答用户关于在Vue中使用Pinia的问题。用户之前遇到了路由参数传递的问题,现在转向状态管理库Pinia的询问。首先,我需要确定用户对Pinia的了解程度。可能他们之前用过Vuex,想迁移到Pinia,或者刚开始接触状态管理。 首先,我应该介绍Pinia的基本概念,说明它和Vuex的关系,强调Pinia是官方推荐的状态管理工具,支持Vue2和Vue3。接着,需要分步骤讲解如何使用Pinia,包括安装、创建Store、在组件中使用,以及核心功能如状态、Getters、Actions。 用户可能有疑问,比如PiniaVuex的区别,为什么要用Pinia,或者如何组织多个Store。我需要覆盖这些常见问题,同时提供代码示例,确保用户能跟着操作。考虑到用户之前的路由问题,可能对配置过程比较敏感,所以安装步骤要详细,包括包管理器的命令。 在示例部分,需要展示一个典型的Store结构,比如用户信息的存储,包括state、getter和action。然后演示如何在组件中导入和使用,包括如何访问状态、调用actions。可能还需要提到组合式API和选项式API的不同用法,因为用户可能使用不同风格的Vue。 另外,用户可能会遇到响应式丢失的问题,需要提醒使用storeToRefs来解构保持响应式。插件和持久化存储也是常见需求,可以简要介绍如何通过插件实现,比如vuex-persistedstate的替代方案。 最后,总结Pinia的优势,比如TypeScript支持、模块化、易用性等,帮助用户理解迁移或采用的原因。确保语言简洁,结构清晰,避免技术术语过多,必要时提供代码块和注意事项。</think>以下是 VuePinia 状态管理库的深度使用指南,包含最新实践方案和关键细节说明: --- ### 📦 一、核心优势对比 | 特性 | Pinia | Vuex | |--------------------|-------------------------|------------------| | **Vue3支持** | 原生支持 | 需Vuex4 | | **TypeScript** | 零配置完美支持 | 需额外声明 | | **API设计** | 组合式+选项式 | 仅选项式 | | **模块化** | 自动代码分割 | 需手动注册模块 | | **包体积** | 1KB (gzip) | 10KB+ | --- ### 🛠️ 二、快速集成方案 1. **安装命令**(推荐pnpm): ```bash pnpm add pinia # 或 npm install pinia ``` 2. **初始化配置**(main.js/ts): ```typescript import { createApp } from 'vue' import { createPinia } from 'pinia' const app = createApp(App) const pinia = createPinia() // 可选:注册持久化插件 pinia.use(piniaPluginPersistedstate) app.use(pinia) app.mount('#app') ``` --- ### 🔨 三、Store创建规范 #### 1. 标准Store模板 ```typescript // stores/user.ts import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { // 推荐使用箭头函数保证类型推断 state: () => ({ id: 0, name: 'Guest', token: localStorage.getItem('token') || '', permissions: [] as string[] // 类型断言 }), getters: { isAdmin: (state) => state.permissions.includes('admin'), // 带参数的计算属性 hasPermission: (state) => (code: string) => { return state.permissions.includes(code) } }, actions: { async login(payload: { username: string; password: string }) { const { data } = await api.login(payload) this.token = data.token this.name = data.username localStorage.setItem('token', data.token) }, logout() { // 批量修改建议使用$patch this.$patch({ token: '', permissions: [] }) localStorage.removeItem('token') } }, // 持久化配置(需安装插件) persist: { paths: ['token'], // 仅持久化token字段 storage: sessionStorage // 切换存储介质 } }) ``` #### 2. 组合式Store(Vue3推荐) ```typescript export const useCounterStore = defineStore('counter', () => { const count = ref(0) const double = computed(() => count.value * 2) function increment(step = 1) { count.value += step } return { count, double, increment } }) ``` --- ### 🎯 四、组件中使用指南 #### 1. 基础调用 ```vue <script setup> import { useUserStore } from '@/stores/user' const userStore = useUserStore() // 直接访问状态 console.log(userStore.name) // 调用action const handleLogin = async () => { await userStore.login({ username: 'admin', password: '123456' }) } // 使用带参数的getter const hasEditPermission = userStore.hasPermission('edit') </script> <template> <div v-if="userStore.isAdmin">管理员面板</div> <button @click="userStore.increment()">点击计数: {{ userStore.count }}</button> </template> ``` #### 2. 响应式解构技巧 ```typescript import { storeToRefs } from 'pinia' const userStore = useUserStore() const { name, token } = storeToRefs(userStore) // 保持响应式 const { login } = userStore // 方法无需处理 ``` --- ### 🔧 五、高级实践技巧 #### 1. 跨Store调用 ```typescript // stores/cart.ts export const useCartStore = defineStore('cart', { actions: { async checkout() { const user = useUserStore() if (!user.isLoggedIn) { await user.loginAnonymously() } // 调用其他store逻辑... } } }) ``` #### 2. 状态热更新(HMR) ```typescript // vite.config.ts import { defineConfig } from 'vite' export default defineConfig({ plugins: [ vue({ template: { compilerOptions: { // 启用Pinia热更新 isCustomElement: (tag) => tag.startsWith('router-') } } }) ] }) ``` #### 3. 严格模式调试 ```typescript const pinia = createPinia() // 开发环境启用严格模式 if (import.meta.env.DEV) { pinia.use(({ store }) => { store.$subscribe((mutation, state) => { console.debug('[Pinia] Mutation:', mutation.type, mutation.payload) }) }) } ``` --- ### ⚠️ 六、常见问题排查 #### 1. 响应性丢失问题 **错误示例**: ```typescript const { count } = useCounterStore() // ❌ 失去响应式 ``` **正确方案**: ```typescript const counterStore = useCounterStore() // 方案1:直接使用store.count // 方案2:使用storeToRefs解构 const { count } = storeToRefs(counterStore) ``` #### 2. 循环依赖处理 当Store A 引用 Store B,同时 Store B 引用 Store A 时: ```typescript // 在函数内动态引用 actions: { fetchData() { const storeB = useStoreB() // 在方法内部引用 // ... } } ``` #### 3. SSR兼容方案 ```typescript // nuxt.config.ts export default defineNuxtConfig({ modules: ['@pinia/nuxt'], pinia: { autoImports: [ 'defineStore', ['defineStore', 'definePiniaStore'] // 别名配置 ] } }) ``` --- ### 📚 七、生态推荐 1. **持久化插件** `pinia-plugin-persistedstate`:数据持久化存储 ```bash pnpm add pinia-plugin-persistedstate ``` 2. **调试工具** 安装 [Vue Devtools](https://devtools.vuejs.org/) 可直观查看Pinia状态树 3. **测试工具** `@pinia/testing`:提供单元测试支持 ```typescript import { setActivePinia } from 'pinia' beforeEach(() => { setActivePinia(createPinia()) }) ``` --- 通过以上方案,您可以高效管理Vue应用中的复杂状态逻辑。Pinia的轻量化和TypeScript友好特性使其成为新一代Vue项目的首选状态管理工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝对零度HCL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值