pinia介绍与使用

617 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

pinia的诞生与背景

Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。

为什么要使用 Pinia?

  • Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
  • 插件:使用插件扩展 Pinia 功能
  • 为 JS 用户提供适当的 TypeScript 支持或autocompletion
  • 服务器端渲染支持

与 Vuex 的比较

  • mutations 不再存在。 他们经常被认为是非常 冗长。
  • 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
  • 不再有modules 的嵌套结构。 您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系。
  • 没有命名空间模块。 鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。

注册pinia

在vue3使用Pinia时先需要全局注册一下

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

store

定义store

 Store 是使用 defineStore() 定义的,并且它需要一个唯一****名称,作为第一个参数传递:

import { defineStore } from 'pinia'  
// useStore 可以是 useUser、useCart 之类的任何东西 
// 第一个参数是应用程序中 store 的唯一 id 
export const useStore = defineStore('main', {   // other options... })

这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use...  是跨可组合项的约定,以使其符合你的使用习惯。

使用 store

我们正在 定义 一个 store,因为在 setup() 中调用 useStore() 之前不会创建 store:

import { useStore } from '@/stores/counter'  
export default {   
   setup() {     
       const store = useStore()      
       return {       
       // 您可以返回整个 store 实例以在模板中使用它       
       store,     
       }   
   }, 
}

您可以根据需要定义任意数量的 store ,并且您应该在不同的文件中定义每个 store 以充分利用 pinia(例如自动允许您的包进行代码拆分和TypeScript推理)。

一旦 store 被实例化,你就可以直接在 store 上访问 stategetters 和 actions 中定义的任何属性。 我们将在接下来的页面中详细介绍这些内容,但自动补全会对您有所帮助。

请注意,store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构

export default defineComponent({   
    setup() {     
        const store = useStore()     // ❌ 这不起作用,因为它会破坏响应式     
        // 这和从 props 解构是一样的     
        const { name, doubleCount } = store      
        name // "eduardo"     
        doubleCount // 2      
        return {       
            // 一直会是 "eduardo"       
            name,       // 一直会是 2       
            doubleCount,       // 这将是响应式的       
            doubleValue: computed(() => store.doubleCount),       
        }   
    }, 
})

state

定义state

大多数时候,state 是 store 的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia 中,状态被定义为返回初始状态的函数。 Pinia 在服务器端和客户端都可以工作。

import { defineStore } from 'pinia'  
const useStore = defineStore('storeId', 
    {   
    // 推荐使用 完整类型推断的箭头函数   
    state: () => {     
        return {       
        // 所有这些属性都将自动推断其类型       
        counter: 0,       
        name: 'Eduardo',       
        isAdmin: true,     
        }   
    }, 
})

访问 “state”

默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态:

const store = useStore()  
store.counter++

重置状态

您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:

const store = useStore()  
store.$reset()

改变状态

直接修改

const store = useStore()  
store.counter++

调用$patch修改

它允许您使用部分“state”对象同时应用多个更改

const store = useStore()  
store.$patch({   
    counter: store.counter + 1,   
    name: 'Abalam', 
})

但是,使用这种语法应用某些突变非常困难或代价高昂:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。 正因为如此,$patch 方法也接受一个函数来批量修改集合内部分对象的情况:

const store = useStore()  
store.$patch((state) => {   
    state.items.push({ 
        name: 'shoes', quantity: 1 
    })   
    state.hasChanged = true 
})

替换state修改

const store = useStore()  
store.$state = { 
    counter: 666, name: 'Paimon' 
}

订阅状态

可以通过 store 的 subscribe() 方法查看状态及其变化,类似于Vuex的 subscribe方法。与常规的 watch() 相比,使用 subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用 subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次

cartStore.$subscribe((mutation, state) => {   
    // import { MutationType } from 'pinia'   
    mutation.type // 'direct' | 'patch object' | 'patch function'   
    // 与 cartStore.$id 相同   
    mutation.storeId // 'cart'   
    // 仅适用于 mutation.type === 'patch object'   
    mutation.payload // 补丁对象传递给 to cartStore.$patch()    
    // 每当它发生变化时,将整个状态持久化到本地存储   
    localStorage.setItem('cart', JSON.stringify(state)) 
})

默认情况下,state subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 当前组件的 state subscription

export default {   
    setup() {     
        const someStore = useSomeStore()      
        // 此订阅将在组件卸载后保留     
        someStore.$subscribe(callback, { 
            detached: true 
        })      
        // ...   
    }, 
}

Getters

Getter 完全等同于 Store 状态的 计算值。 它们可以用 defineStore() 中的 getters 属性定义。 他们接收“状态”作为第一个参数以鼓励箭头函数的使用:

export const useStore = defineStore('main', {   
    state: () => ({     
        counter: 0,   
    }),   
    getters: {     
        doubleCount: (state) => state.counter * 2,   
    }, 
})

大多数时候,getter 只会依赖状态,但是,他们可能需要使用其他 getter。 正因为如此,我们可以在定义常规函数时通过 this 访问到 整个 store 的实例, 但是需要定义返回类型(在 TypeScript 中)

export const useStore = defineStore('main', {   
    state: () => ({     
        counter: 0,   
    }),   
    getters: {     
        // 自动将返回类型推断为数字     
        doubleCount(state) {       
            return state.counter * 2     
        },     
        // 返回类型必须明确设置     
        doublePlusOne(): number {       
            return this.counter * 2 + 1     
        },   
    }, 
})

然后你可以直接在 store 实例上访问 getter:

<template>   
    <p>Double count is {{ store.doubleCount }}</p> 
</template>  
<script> 
    export default {   
        setup() {     
            const store = useStore()      
            return { store }   
        }, 
    }
</script>

访问其他 getter

与计算属性一样,您可以组合多个 getter。 通过 this 访问任何其他 getter。 即使您不使用 TypeScript,您也可以使用 JSDoc 提示您的 IDE 类型:

export const useStore = defineStore('main', {   
    state: () => ({     
        counter: 0,   
    }),   
    getters: {     
        // 类型是自动推断的,因为我们没有使用 `this`     
        doubleCount: (state) => state.counter * 2,     
        // 这里需要我们自己添加类型(在 JS 中使用 JSDoc)。 我们还可以     
        // 使用它来记录 getter     
        /**      
         * 返回计数器值乘以二加一。      
         *      
         * @returns {number}      
         */     
         doubleCountPlusOne() {       
         // 自动完成 ✨       
             return this.doubleCount + 1     
         },   
     }, 
 })

将参数传递给 getter

Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:

export const useStore = defineStore('main', {   
    getters: {     
        getUserById: (state) => {       
            return (userId) => state.users.find((user) => user.id === userId) 
        },   
    }, 
})

并在组件中使用:

<script> 
    export default {   
        setup() {     
            const store = useStore()      
            return { 
                getUserById: store.getUserById 
            }   
        }, 
    } 
</script> 

<template>   
    <p>User 2: {{ getUserById(2) }}</p> 
</template>

请注意,在执行此操作时,getter 不再缓存,它们只是您调用的函数。

访问其他 Store 的getter

要使用其他存储 getter,您可以直接在 getter 内部使用它:

import { useOtherStore } from './other-store'  
export const useStore = defineStore('main', {   
    state: () => ({     
        // ...   
    }),   
    getters: {     
        otherGetter(state) {       
        const otherStore = useOtherStore()       
        return state.localData + otherStore.data     
        },   
    }, 
})

Action

Actions 相当于组件中的 methods。 它们可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑

export const useStore = defineStore('main', {   
    state: () => ({     
        counter: 0,   
    }),   
    actions: {     
        increment() {       
            this.counter++    
        },     
        randomizeCounter() {   
            this.counter = Math.round(100 * Math.random())    
        },   
    }, 
})

与 getters 一样,操作可以通过 this 访问 whole store instance 并提供完整类型(和自动完成✨)支持。 与它们不同,actions 可以是异步的,您可以在其中await 任何 API 调用甚至其他操作! 

export const useStore = defineStore('main', {   
    state: () => ({     
        counter: 0,   
    }),  
    actions: {     
        async asyncAction() {       
            this.counter = await new Promise((resolve) => {        
                setTimeout(() => {           
                    resolve(100);         
                }, 3000);       
            });     
        },   
    }, 
})

访问其他 store 操作

import { useAuthStore } from './auth-store'  
export const useSettingsStore = defineStore('settings', {   
    state: () => ({    
        // ...   
    }),   
    actions: {     
        async fetchUserPreferences(preferences) {   
            const auth = useAuthStore()      
            if (auth.isAuthenticated) { 
                this.preferences = await fetchPreferences()  
            } else {      
                throw new Error('User must be authenticated')      
            }     
        },  
    }, 
})

使用action

export default {   
    setup() {    
        const store = useStore()   
        store.increment()  
    }, 
}

订阅Actions

可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数。 以类似的方式,onError 允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用

这是一个在运行 action 之前和它们 resolve/reject 之后记录的示例

const unsubscribe = someStore.$onAction(   
    ({     
        name, // action 的名字     
        store, // store 实例     
        args, // 调用这个 action 的参数     
        after, // 在这个 action 执行完毕之后,执行这个函数    
        onError, // 在这个 action 抛出异常的时候,执行这个函数   
    }) => {    
        // 记录开始的时间变量     
        const startTime = Date.now()    
        // 这将在 `store` 上的操作执行之前触发     
        console.log(`Start "${name}" with params [${args.join(', ')}].`)      
        // 如果 action 成功并且完全运行后,after 将触发。     
        // 它将等待任何返回的 promise     
        after((result) => {       
            console.log(
            `Finished "${name}" after ${Date.now() - startTime         }ms.\nResult: ${result}.`       
            )    
        })      
        // 如果 action 抛出或返回 Promise.reject ,onError 将触发     
        onError((error) => {       
            console.warn(         
            `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`      
            )     
        })  
    } 
)  
    // 手动移除订阅 unsubscribe()

默认情况下,action subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 内)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给当前组件的 detach action subscription

export default {   
    setup() {     
        const someStore = useSomeStore()      // 此订阅将在组件卸载后保留
        someStore.$onAction(callback, true)      // ...  
    }, 
}