轻量级状态管理库Pinia试吃

  最近连续看了几个GitHub上的开源项目,里面都用到了 Pinia 这个状态管理库,于是研究了一下,发现确实是个好东西!那么,Pinia 的特点:

  • 轻量化 —— Pinia 体积约1KB,十分轻巧,加载运行都很快速,相对来说Vuex明显块头更大一些
  • Pinia 的 API 设计并非追求另辟蹊径,事实上它十分接近 Vuex 5的提案,并且对composition API非常友好(作者本身是Vue.js的核心成员,并且积极参与Vue Router以及Vuex的API设计)
  • 模块化设计,支持创建数个store(有点类似Vuex中的module),在打包时会被自动拆分
  • 跨模块调用十分直观方便,你可以在任意的 store 之间交叉组合使用
  • 完美支持Typescript,不得不说这个是Vuex的最大劣势之一,深度使用过Vuex + Ts的童鞋应该都懂
  • 可脱离组件使用,比如在编写router时调用
  • Vue devtools兼容(暂不支持个别功能点)
  • 同时支持Vue2/Vue3

  具体的使用细节就不照搬文档了,官网链接奉上 => Pinia
  值得一提的是,虽然文档使用英文编写,但是文档结构、语言组织等方面都属上乘,通俗易懂

  下面介绍几个亮点特性。

一、结构设计与Vuex API非常相似

这使得它学习成本很低,如果之前使用过Vuex的话,可以非常方便的上手

import { defineStore } from 'pinia'

interface AppState {
  name: string;
}

export const useUserStore = defineStore({
  id: 'app',
  state: (): AppState =>({
    name: 'Eduardo'
  }),
  getters: {},
  actions: {}
})

二、取消mutation,使用三种方式更改state

<script lang="ts" setup>
import { useStore } from '@/store/modules/app';
import { ref } from 'vue';

const store = useStore();

function raise() {
  // 方式一:直接修改 -> 'direct'
  store.salary += 10000;

  // 方式二:patch对象 -> 'patch object',填入打算更改的state字段即可
  store.$patch({
    salary: store.salary + 10000,
  });

  // 方式三:patch函数 -> 'patch function',可键入语句,执行复杂逻辑
  store.$patch((state) => {
    state.salary += 10000;
  });
}
</script>

三、store使用reactive包裹,自带响应性

无需二次包裹reactive、computed,即可在模板中直接使用,同时具有响应性

<template>
  <div>{{ store.name }}, 文章阅读量:{{ store.article }}</div>
  <hr />
  <button @click="jump">点击跳转阅读新文章</button>
</template>

四、跨模块调用直观方便

直接引入其他store的hook函数,调用即可

import { useSomeOtherStore } from './auth-store'

export const useAppStore = defineStore('app', {
  state: () => ({
    // ...
  }),
  actions: {
    async someAction() {
      const someOtherStore = useSomeOtherStore()
      // ...
    },
  },
})

五、组件外调用

用户创建的类似useStore这样的Hook函数默认会自动注入在单页应用入口处创建并传递给app实例的Pinia实例(有点拗口…),在组建内使用时,Pinia实例肯定已经传递给app实例了,这时候直接调用hook函数使用就完事了。但在组件外使用时,很可能出现在调用hook函数时,Pinia实例还未传递给app实例,这时候就需要用户手动将Pinia实例传入hook函数,并暴露出来以供调用。具体可参见官方文档的对应章节 -> Using a store outside of a component

// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { pinia } from '@/store'

const app = createApp(App)
// 将pinia实例传递给app实例,在此之后使用useStore钩子,会自动注入pinia实例,否则需要在useStore中手动注入
app.use(pinia)

app.mount('#app')

// store/index.ts
import { createPinia } from 'pinia'

const pinia = createPinia()
export { pinia }

// store/modules/app.ts
import { defineStore } from 'pinia'
import { pinia } from '@/store'

interface AppState {
  name: string;
}

export const useUserStore = defineStore({
  id: 'app',
  state: (): AppState =>({
    name: 'Eduardo'
  }),
  getters: {},
  actions: {}
})

// 这里的pinia即为通过createPinia()生成并在入口处传递给app实例的pinia实例,在此手动注入,并暴露
export function useAppStoreHook() {
  return useAppStore(store)
}

最后,推荐大家尝试,真的好用~