主页

Pinia

官网:https://pinia.vuejs.org/zh/

一个全新的用于 Vue 的状态管理库

介绍

pinia 最初是 一个实验,目的是在2019年11月左右重新设计 Vue 状态管理在 Composite API 上的样子,也就是下一代 Vuex

  • 之前的 Vuex 主要服务于 Vue2,选项式 API
  • 如果想要在 Vue3 中使用 Vuex,需要使用它的版本4

    • 只是一个过渡的选择,还有很大缺陷
  • 所以在 Vue3 伴随着组合式 API 诞生之后,也设计了全新的 Vuex:Pinia,也就是 Vuex5
提案链接:https://github.com/vuejs/rfcs/pull/271
  • Vue2 和 Vue3 都支持

    • 处理初始化安装和 SSR 配置之外,两者的 API 都是相同的
    • 官方文档注意针对 Vue3 进行说明,必要的时候会提供 Vue2 的注释
  • 支持 Vue DevTools

    • 跟踪 actions、mutations 的时间线
    • 在使用容器的组件中就可以观察到容器本身
    • 支持 time-travel 更容易的调试功能
    • 在 Vue2 中 Pinia 使用 Vuex 的现有接口,所以不能与 Vuex 一起使用
    • 但是针对于 Vue3 中的调试工具支持还不够完美,比如还没有 time-travel 调试功能
  • 模块热更新

    • 无需重新加载页面即可修改您的容器
    • 热更新的时候保持任何现有状态
  • 支持使用插件扩展 Pinia 功能
  • 相比 Vuex 有更好完美的 TypeScript 支持
  • 支持服务端渲染

核心概念

Pinia 从使用者角度和之前 Vuex 几乎是一样的

Store(如 Pinia)是一个保存状态和业务逻辑的实体,它不绑定到您的组件树。换句话说,它承载全局 state。它有点像一个始终存在的组件,每个人都可以读取和写入。它有三个核心概念

在 Vuex 中有四个核心概念:

  • State
  • Getters
  • Mutations
  • Actions

state:类似组件的 data,用来存储全局状态

{
  todos: [
    { id: 1, title: '吃饭', done: false },
    { id: 2, title: '睡觉', done: false },
    { id: 3, title: '打豆豆', done: true }
  ]
}

getters:类似组件的 computed,根据已有的 State 封装派生数据,也具有缓存的特性

doneCount() {
  return todos.filter(item => item.done).length
}

actions:类似组件的 menthods,用来封装业务逻辑,同步异步都可以

  • 在 Vuex 中同步操作用 mutations,异步操作用 actions,太麻烦。Pinia 中没有 mutations

基本示例

首先创建一个 store:

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // 可以这样写
  // state: () => ({ count: 0 })

  actions: {
    increment() {
      // 在 Vuex 中需要搞两步:1. 定义 mutations 2. 提交 mutations
      this.count++
    }
  }
})

在组件中使用它:

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

export default {
  setuo() {
    const counter = useCounterStore()

    counter.count++
    // with autocompletion
    counter.$patch({ count: counter + 1 })
    // or using an action instead
    counter.increment()
  }
}

甚至可以使用函数(类似于组件 setup())为更高级的用例定义 Store:

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

  return { count, increment }
})

如果仍不熟悉 setup() Composition API,Pania 也支持类似 Vuex 的一组 地图助手。可以以相同的方式定义商店,但随后使用 mapStores()mapActions():

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

const useUserStore = defineStore('user', () => {
  // ...
})

export default {
  computed: {
    // other computed properties
    // ...
    // gives access to this.counterStore and this.userStore
    ...mapStores(useCounterStore, useUserStore),
    // gives read access to this.count and this.double
    ...mapState(useCounterStore, ['count', 'double'])
  },
  methods: {
    // gives access to this.increment()
    ...mapActions(useCounterStore, ['increment'])
  }
}

Pinia vs Vuex

Pinia 试图尽可能接近 Vuex 的理念。它只在测试 Vuex 下一次迭代的提案,并取得了成功,因为目前有一个针对 Vuex5 的开放式 RFC,其 API 与 Piniia 使用的 API 非常相似。Pinia 的作者(Eduardo)是 Vue.js 核心团队的一员,并积极参与 Router 和 Vuex 等 API 的设计。他对这个项目的意图是重新设计使用全局 Store 的体验,同时保存 Vue 平易近人的哲学。让 Pinia 的 API 与 Vuex 一样接近,因为它不断向前发展,使人们可以轻松地迁移到 Vuex,甚至在未来融合这两个项目(在 Vuex 下)。

关于版本问题:

  • Vuex 当前最新版本是 4.x

    • VueX4 用于 Vue3
    • Vuex3 用于 Vue2
  • Pinia 当前最新版本是 2.x

    • 即支持 Vue2 也支持 Vue3

可以认为就是 Vuex5,因为它的作者是官方的开发人员,并且已被官方接管了

Pinia API 与 Vuex ≤4 有很大不同,即:

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

关于名字

Pinia (发音为 /piːnjʌ/,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 pineapple,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。

关于作者

vuejs 核心团队成员 https://cn.vuejs.org/about/team.html

快速入门

安装

yarn add pinia
# or with npm
npm install pinia
如果应用程序使用 Vue2,还需要安装组合式 api 包:@vue/composition-api

初始化配置

Vue3:

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

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

import { createPinia, PiniaVuePlugin } from 'pinia'

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

new Vue({
  el: '#app',
  // 其他配置...
  // ...
  // 请注意,同一个`pinia'实例
  // 可以在同一个页面的多个 Vue 应用中使用。 
  pinia,
})
这样才能提供 devtools 的支持。在 Vue 3 中,一些功能仍然不被支持,如 time traveling 和编辑,这是因为 vue-devtools 还没有相关的 API,但 devtools 也有很多针对 Vue 3 的专属功能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口(因此不能与 Vuex 一起使用)。

定义和使用 Store

使用 Store

使用 storeToRefs 解构 Store 数据

状态

定义状态

访问 State

重置状态

与 Options API 一起使用

创建项目

yarn create vite

安装 Pinia

yarn add pinia

基本使用

1. 创建 pinia 示例并挂载

// src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

import { createPinia } from 'pinia'
// 1.创建 Pinia 实例
const pinia = createPinia()

const app = createApp(App)

// 2.挂载到 Vue 根实例
app.use(pinia)

app.mount('#app')

2. 基本使用

// src/store/index.ts
import { defineStore } from 'pinia'

// 1.定义并导出容器
// 参数1:容器 ID,必须唯一,将来 Pinia 会把所有的容器挂载到根容器
// 参数2:选项对象
// 返回值:一个函数,调用得到容器实例
export const useMainStore = defineStore('main', {
  /**
   * 类似于组件的 data,用来存储全局状态的
   * 1.必须是函数:是为了在服务端渲染的时候避免交叉请求导致的状态数据污染
   * 2.必须是箭头函数:为了更好的 TS 类型推导
   */
  state: () => {
    return {
      count: 100,
      name: 'Joe',
      arr: [1, 2, 3]
    }
  },

  /**
   * 类似于组件的 computed,用来封装计算属性,有缓存功能
   */
  getters: {
    // 函数接收一个可选参数: state 状态对象
    count10(state) {
      return state.count + 10
    }
    // 如果在 getters 中使用了 this 则必须手动指定返回值的类型,否则类型推导不出来
    // count10():number {
    //   return this.count + 10
    // }
  },

  /**
   * 类似于组件的 methods,封装业务逻辑,修改 state
   */
  actions: {
    // 不能使用箭头函数定义 actions,箭头函数绑定外部 this
    changeState() {
      // this.count++
      // this.name = 'qiaofugui' + this.count
      // this.arr.push(4)

      this.$patch((state) => {
        state.count++
        state.name = 'qiaofugui' + state.count
        state.arr.push(4)
      })
    }
  }
})

// 2.使用容器中的 state

// 3. 修改 state

// 4.容器中的 action 使用

组件使用

<template>
  <p>{{ mainStore.count }}</p>
  <p>{{ mainStore.name }}</p>
  <p> {{ mainStore.arr }}</p>
  <p> {{ mainStore.count10 }}</p>
  <hr>
  <p>{{ count }}</p>
  <p>{{ name }}</p>
  <p> {{ arr }}</p>
  <hr>
  <p>
    <button @click="handelChangeState">修改数据</button>
  </p>
</template>
<script lang='ts' setup>
import { storeToRefs } from 'pinia'
import { useMainStore } from '../store/index'

const mainStore = useMainStore()
console.log(mainStore)

// 这是有问题的,因为这样拿到的数据不是响应式的
// pinia 其实就是把 state 数据做了 reactive 处理了
// const { count, name, arr } = mainStore
// 解决办法就是使用 storeToRefs 把解构出来的数据做 ref 响应式代理
const { count, name, arr } = storeToRefs(mainStore)

// 修改数据
const handelChangeState = () => {
  // 方式一:最简单的方式
  // mainStore.count++
  // mainStore.name = 'Joe'

  // 方式二:如果需要修改多个数据,建议使用 $patch 批量更新
  // mainStore.$patch({
  //   count: count.value + 1,
  //   name: 'qiaofugui' + count.value + 1,
  //   arr: [...mainStore.arr, 4]
  // })

  // 方式三:更好的批量更新方式 $patch 一个函数
  // mainStore.$patch(state => {
  //   state.count++
  //   state.name = 'qiaofugui' + state.count
  //   state.arr.push(4)
  // })

  // 方式四:逻辑比较多的时候封装到 action 处理
  mainStore.changeState()
}
</script>
<style lang="less" scoped></style>

Pinia 添加到 vue 项目中

  1. 使用 create-vue 创建空的新项目 npm init vue@latest
  2. 按照官方文档安装 pinia 到项目 https://pinia.vuejs.org/zh/getting-started.html

简单使用

getters

Pinia 中的 getters 直接使用 computed 函数进行模拟

import { ref, computed } from 'vue'
// 数据(state)
const count = ref(0)

// getter
const doubleCount = computed(() => count.value * 2)

action 实现异步

action 中实现异步和组件中定义数据、方法的风格完全一致

const API_URL = 'http://127.0.0.1/list'
// 准备数据(state)
const list = ref([])

// 异步 action
const loadList = async () => {
  count { data: res } = await axios.get(API_URL)
  list.value = res
}

storeToRefs

在组件中使用 storeToRefs 函数可以辅助保持数据(state + getter)的响应式解构

import { useCounterStore } from '@/stores/counter.js'
import { storeToRefs } from 'pinia'

// 保持数据响应式
const { count, doubleCount } = storeToRefs(counterStore)
属性使用 storeToRefs 函数进行解构,方法直接进行解构

Pinia

版权属于:Joe
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
0

目录

来自 《Pinia》
评论

qiaofugui

博主很懒,啥都没有
188 文章数
14 评论量
3 分类数
191 页面数
已在风雨中度过 2年146天0小时17分