Pinia 入门

2,256 阅读3分钟

Pinia 是对vuex 5 提案的测试,并以取得成功。Pinia 的作者 (Eduardo) 是 Vue.js 核心团队的一员。对这个项目的意图是重新设计使用全局 Store 的体验。 image.png

1. Pina介绍

  • pinia无需创建复杂的包装器来支持typescript,对于typescript类型判断是天然支持的,享受ide带来的自动补全,减少开发的心智负担

  • 减去了mutations的概念,只保留state,getters和anctions三个概念,减少代码冗余

  • 无需手动添加store,创建的store会在使用时自动添加

  • 没有模块module的概念,不用面对一个store下嵌套着许多模块,使用单文件store,可以直接导入其他store使用

  • 支持服务端渲染

2. Pinia安装

首先我们初始化一个vite+vue+ts工程

npm create vite pinia-demo -- --template vue-ts

2.1 安装pinia

npm i pinia@next

2.2 在入口文件中把Pinia挂载为Vue插件

打开项目,编辑src目录下的mian.ts文件,引入Pinia

// main.ts

import { createApp } from 'vue'
import App from './App.vue'

import { createPinia } from 'pinia'

createApp(App).use(createPinia()).mount('#app')

2.3 在文件中声明store

在src目录下创建一个store文件夹用来存放状态管理,然后新建一个counter.ts,我们来做一个简单的计数器状态应用

import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter',{
  state: () => {
    return {
      count: 0,
    }
  },
  ...
})

或写成如下

import { defineStore } from 'pinia'export const useCounterStore = defineStore({
  id: 'counter',
  state: () => {
    return {
      count: 0,
    }
  },
  ...
})

2.4 在组建中引入,并使用store

<script setup lang="ts">
import { useCounterStore } from './store/counter'
const useCounter = useCounterStore()
</script>

<template>
  <h2>{{ useCounter }}</h2>
  <h2>{{ useCounter.count }}</h2>
  <h2>{{ useCounter.doubleCount() }}</h2>
  <button @click="useCounter.increment">increment</button>
</template>

3. Store中的state

Pinia通过defineStore函数来创建一个store,它接收一个id用来标识store,以及store选项

3.1 在store中声明state

export const useStore = defineStore({
  id: "main",
  // 第一种
  state(){
    return {
      counter: 1,
      name: "main"
     }
  }
  // 第二种
  state: ()=>({
    counter: 45,
    name: 'zs'
  }),

3.2 在模版组件中使用

在组建中引用,创建便可访问sotre中的state。

<script setup lang="ts">
import {useStore} from "./store/counter"
let store = useStore()
</script>
<template>
  <div>{{store.counter}}</div>
</template>

3.3 解构store

<script setup lang="ts">
import {useStore} from "./store/counter"
// 直接解构数据变为非响应式
// let {counter} = useStore()
import { storeToRefs } from 'pinia'
// 通过storeToRefs转换为响应式对象解构可正常使用
let {counter} = storeToRefs(useStore())
</script>
<template>
  <div>{{counter}}</div>
</template>

4. Store 中的getters

getters 可以理解为vue中的计算属性,getters的第一个参数是state。

4.1 声明 getters

export const useStore = defineStore({
  id: "main",
  ...
  getters: {
    doubleCounter(state){
      return state.counter * 2
    },
  }
 })

使用与state相同

4.2 访问当前store中的的其他getters

export const useStore = defineStore({
  id: "main",
  ...
  getters: {
    doubleCounter(state){
      return state.counter * 2
    },
  }
 })

4.3 getters传递参数

利用高阶函数传递参数

export const useStore = defineStore({
  id: "main",
  ...
  getters: {
    doubleCounterPlus(state){
      return (num)=> {
        return state.counter * 2 + num
      } 
    },
  }
 })
 <div>{{dobuleCounterPlus(3)}}</div>

4.4 访问其他store中的getters

创建 defalut store,并将其引入使用

export const useDefaultStore = defineStore({
  id: "default",
  state: ()=>({
    time: 2
  }),
  getters: {
    calcTime(state){
      return state.time + 2
    }
  }
})
  export const useStore = defineStore({
    id: "main",
    ...
    getters: {
      addDefault(state){
        const store = useDefaultStore()
        return state.counter + store.calcTime
      }
    }
 })
  

5. Store中的Actions

5.1 同步使用

pinia 中不再区分MutaionsActions,同步异步均使用 Actions

export const useDefaultStore = defineStore({
  id: "default",
  ...
  actions: {
    addCounter(num: number){
        this.counter = this.doubleCounter + num
    }
  }
})

5.2 异步使用

export const useDefaultStore = defineStore({
  id: "default",
  ...
  actions: {
    addCounter(num: number){
      setTimeout(() => {
        this.counter = this.doubleCounter + num
      }, 3000)
    }
  }
})

5.3 使用其他 store 的Actions

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')
      }
    },
  },
})

5.4 订阅 Actions

store.$onAction()({
    name, // name of the action
    store, // store instance, same as `someStore`
    args, // array of parameters passed to the action
    after, // hook after the action returns or resolves
    onError, // hook if the action throws or rejects
  }) => {
    ...
  }
})

6. 其他

6.1 修改state

store.counter++
// 修改多个属性
store.$patch({ count: store.counter + 1 })
// 可以重新赋值整个state
store.$state = { counter: 666, name: 'Paimon' }

观察state

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // same as cartStore.$id
  mutation.storeId // 'cart'
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to cartStore.$patch()

  // persist the whole state to the local storage whenever it changes
  localStorage.setItem('cart', JSON.stringify(state))
})

6.2 对optionsapi组件支持

export default {
  computed: {
    ...mapStores(useCounterStore, useUserStore)
    ...mapState(useCounterStore, ['count', 'double']),
    // 利用mapWritableState,能够写入这些状态属性
    ...mapWritableState(useStore, ['counter'])
  },
  methods: {
    ...mapActions(useCounterStore, ['increment']),
  },
}

6.3 $reset() 👏

通过$reset()方法还原最初sotre

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

7. 插件

  • 向stores增加属性
  • 向stores中增加新的options
  • 向stores增加新的方法
  • 包装现有方法
  • 修改或取消 actions
  • 实施副作用如本地存储
  • 仅适用于特定 store
export function myPiniaPlugin(context) {
  context.pinia // the pinia created with `createPinia()`
  context.app // the current app created with `createApp()` (Vue 3 only)
  context.store // the store the plugin is augmenting
  context.options // the options object defining the store passed to `defineStore()`
  // ...
}

pinia.use(myPiniaPlugin)