Pinia是什么

Pinia官网:Pinia | The intuitive store for Vue.js (vuejs.org)

Pinia读音:/piːnjʌ/,是Vue官方团队推荐代替Vuex的一款轻量级状态管理库。 它最初的设计理念是让Vue Store拥有一款Composition API方式的状态管理库,并同时能支持 Vue2.x版本的Option API 和 Vue3版本的setup Composition API开发模式,并完整兼容Typescript写法(这也是优于Vuex的重要因素之一),适用于所有的vue项目。尤雨溪大佬也说过Pinia其实就是Vuex5,其诞生是更好服务于Vue3的。

为什么要用Pinia

pinia的优点

  • 完整的 TypeScript 支持:与在 Vuex 中添加 TypeScript 相比,添加 TypeScript 更容易
  • 极其轻巧(体积约 1KB)
  • store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数,这在 Vuex 中很常见
  • 支持多个Store
  • 支持 Vue devtools、SSR 和 webpack 代码拆分

与Vuex的不同

image.png
image.png

基本使用

安装pinia

用你喜欢的包管理器安装 pinia

yarn add pinia
# 或者使用 npm
npm install pinia

TIP
如果你的应用使用的 Vue 版本低于 2.7,你还需要安装组合式 API 包:@vue/composition-api。如果你使用的是 Nuxt,你应该参考这篇指南

如果你正在使用 Vue CLI,你可以试试这个非官方插件

创建一个 pinia 实例 (根 store) 并将其传递给应用:

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

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

如果你使用的是 Vue 2,你还需要安装一个插件,并在应用的根部注入创建的 pinia

import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  // 其他配置...
  // ...
  // 请注意,同一个`pinia'实例
  // 可以在同一个页面的多个 Vue 应用中使用。 
  pinia,
})

定义Store

定义一个Store

import { defineStore } from 'pinia'

// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
  // 其他配置...
})

Option写法

export const useCounterStore = defineStore('counter', {
  state: () => ({ 
    count: 0
 }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

Setup写法

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

  return { 
	  count, 
	  doubleCount, 
	  increment 
  }
})

使用Store

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

State

option写法

// 示例文件路径:
// ./src/stores/counter.js

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
})

setup语法

// 示例文件路径:
// ./src/stores/counter.js

import { ref } from 'vue'
import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', {
  const count = ref(0)
  return {
	  count
  }

Getter

option写法

// 示例文件路径:
// ./src/stores/counter.js

import { defineStore } from 'pinia'

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

setup写法

import { useCounterStore } from '../stores/counter'

export default defineComponent({
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    quadrupleCounter() {
      return this.counterStore.doubleCount * 2
    },
  },
})

Action

option写法

// 示例文件路径:
// ./src/stores/counter.js

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++
    }
  }
})

setup写法

import { useCounterStore } from '../stores/counter'
export default defineComponent({
  setup() {
    const counterStore = useCounterStore()
    return { counterStore }
  },
  methods: {
    incrementAndPrint() {
      this.counterStore.increment()
      console.log('New Count:', this.counterStore.count)
    },
  },
})

StoreToRefs的使用

<script setup>
import { storeToRefs } from 'pinia'
import useCounterStore from './store/counter'

const counter = useCounterStore()
// 如果直接从pinia中解构数据,会丢失响应式
const { count, double } = counter

// 使用storeToRefs可以保证解构出来的数据也是响应式的
const { count, double } = storeToRefs(counter)
</script>

模块化

在复杂项目中,不可能把多个模块的数据都定义到一个store中,一般来说会一个模块对应一个store,最后通过一个根store进行整合
(1)新建store/user.js文件

import { defineStore } from 'pinia' 

const useUserStore = defineStore('user', {   
	state: () => {     
		return {       
			name: 'zs',       
			age: 100,     
		}   
	}, 
}) 

export default useUserStore

(2)新建store/index.js

import useUserStore from './user' 
import useCounterStore from './counter' // 统一导出useStore方法 
export default function useStore() {   
	return {     
		user: useUserStore(),     
		counter: useCounterStore(),   
	} 
}

(3)在组件中使用

<script setup> 
import { storeToRefs } from 'pinia' 
import useStore from './store' 
const { counter } = useStore() // 使用storeToRefs可以保证解构出来的数据也是响应式的 
const { count, double } = storeToRefs(counter) </script>

持久化

持久化插件:pinia-plugin-persistedstate - npm (npmjs.com)

安装

yarn add pinia-plugin-persistedstate
or
npm i  pinia-plugin-persistedstate

引入

在main.ts中

import { createApp } from "vue";
import App from "./App.vue";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

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

使用

const useHomeStore = defineStore("home",{
  // 开启数据持久化
  persist: true
  // ...省略
});

进阶用法

import { defineStore } from 'pinia'

export const useStore = defineStore('main', s{
  state: () => {
    return {
      someState: 'hello pinia',
      nested: {
        data: 'nested pinia',
      },
    }
  },
  // 所有数据持久化
  // persist: true,
  // 持久化存储插件其他配置
  persist: {
    // 修改存储中使用的键名称,默认为当前 Store的 id
    key: 'storekey',
    // 修改为 sessionStorage,默认为 localStorage
    storage: window.sessionStorage,
    // 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态)
    paths: ['nested.data'],
  },
})

总结

  • pinia是一个轻量的状态管理工具
  • 相较于Vuex,更适用于中小型项目
  • 没有mutation,action支持同步、异步
  • 有完备的TS支持
  • 支持热更新
  • 提供setup写法