06Vue3-Pinia

发布时间 2023-05-30 21:40:48作者: 子不语2015831

Pinia

Pinia是西班牙语piña(西班牙语中的“菠萝”)单词的形似。

它是一个状态管理的库,用于跨组件、页面进行状态共享(这点和Vuex、Redux一样),同时兼容Vue2、Vue3,也并不要求你使用Composition API;

Pinia开始于大概2019年,最初是作为一个实验,目的为了探索 Vuex 的下一次迭代会是什么样子,所以它结合了 Vuex 5 核心团队讨论中的许多想法,为Vue重新设计状态管理,让它用起来像组合式API(Composition API)。

最终,团队意识到Pinia已经实现了Vuex5中大部分内容,所以最终决定用Pinia来替代Vuex;

与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了 Composition-API 风格的 API;

最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持;

和Vuex相比,Pinia有很多的优势:

  • 比如mutations 不再存在;
  • 更友好的TypeScript支持,Vuex之前对TS(TypeScript)的支持很不友好;
  • 不再有modules的嵌套结构,
  • 也不再有命名空间的概念。

image-20230530165717424

01. 安装Pinia

npm install pinia

或者:

yarn add pinia

02. 案例入门

目录结构。

与vuex类似,对于pinia的使用,编程习惯上一般创建“stores”文件夹。

也常见取名为“pinia”。

image-20230530184119948

在index.js文件中,创建一个全局的pinia实例。

import {createPinia} from 'pinia'

const pinia = createPinia()

export default pinia

创建好之后,在全局的main.js文件中,使用这个实例。

import {createApp} from 'vue'  // 不支持template选项
// import {createApp} from 'vue/dist/vue.esm-browser'  // 支持template选项
import App from './App.vue'
import pinia from './stores/index'

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

对于业务逻辑,可以根据模块的不同,将需要管理的state抽离,建立单独的文件,比如counter.js。

定义一个Store:

Store 是使用 defineStore() 定义的,

它需要一个唯一名称,作为第一个参数传递;第二个参数为具体的对象。

defineStore返回的是一个函数,在命名上,统一使用useX作为命名方案,这是约定的规范。

使用defineStore定义的store,默认会与创建的全局pinia关联,可以在组件中直接调用,无需其他操作。

import {defineStore} from 'pinia'

// defineStore第一个参数为name,也称为id,是必传参数。唯一标识Store
const useCounter = defineStore('counter',{
    state:()=>({
        count:99
    })
})

export default useCounter

HomeCom.vue

<template>
    <h1>Home Page</h1>

	<!-- 注意这里与vuex不同 -->
    <p>Home中的count计数:{{counterStore.count}}</p>
    <button @click="addCount">count+1</button>
</template>

<script setup>
    import useCounter from '@/stores/counter'
    import {toRefs} from "vue";
	import {storeToRefs} from "pinia";
	
    // 执行函数,拿到Store
    const counterStore = useCounter()
    
    // pinia支持直接修改state,不必像vuex那样commit一个mutation
    function addCount(){
      counterStore.count++
    }
    
    // 需要注意的是,直接对Store进行解构,会失去响应式
    // const {count} = counterStore

    // 保留响应式,可以使用vue提供的方法
    // const {count} = toRefs(counterStore)

    // 保留响应式,pinia也专门提供了一个方法
    const {count} = storeToRefs(counterStore)
</script>

<style scoped>

</style>

03. Store

在应用程序中,可以定义任意数量的Store来管理应用程序的状态,

Store有三个核心概念:state、getters、actions;

类似于组件的data、computed、methods;

Store在它被使用之前是不会创建的,使用defineStore函数定义Store时,返回的是一个函数,

调用这个函数,才会创建对应的 store 。

编程习惯:定义Store时,一般以useX的形式。

04. State

对于state,pinia支持直接修改,不同像vuex那样通过mutation。

pinia也支持一次性修改多个state,用 Store.$patch() ,传入一个对象。

对于Store的内部方法,以$开头。

pinia支持直接替换state对象和重置对象。

stores/user.js:

import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
    name: "Mark",
    age: 18,
    score: 100
  })
})

export default useUser

Home.vue

<template>
  <div class="home">
    <h2>Home View</h2>
    <h2>name: {{ name }}</h2>
    <h2>age: {{ age }}</h2>
    <h2>level: {{ score }}</h2>
    <button @click="changeState">修改state</button>
    <button @click="resetState">重置state</button>
  </div>
</template>

<script setup>
  import useUser from '@/stores/user'
  import { storeToRefs } from 'pinia';

  const userStore = useUser()
  const { name, age, score } = storeToRefs(userStore)

  function changeState() {
    // 1.一个个修改状态
    // userStore.name = "kobe"
    // userStore.age = 20
    // userStore.score = 200

    // 2.一次性修改多个状态
    // userStore.$patch({
    //   name: "james",
    //   age: 35
    // })

    // 3.替换state为新的对象
    const oldState = userStore.$state
    userStore.$state = {
      name: "curry",
      score: 200
    }
    console.log(oldState === userStore.$state)  // true。pinia这个功能并不是真的直接替换。
  }

  function resetState() {
    userStore.$reset()
  }

</script>

05. Getters

Getters相当于Store的计算属性:

  • 用 defineStore() 中的 getters 属性定义;

  • getters中可以定义接受一个state作为参数的函数;


counter.js:

// 定义关于counter的store
import { defineStore } from 'pinia'

import useUser from './user'

const useCounter = defineStore("counter", {
    state: () => ({
        count: 99,
        subjects: [
            { id: 111, name: "语文" },
            { id: 112, name: "英语" },
            { id: 113, name: "数学" },
        ]
    }),
    getters: {
        // 1.基本使用
        doubleCount(state) {
            return state.count * 2
        },
        // 2.一个getter引入另外一个getter
        doubleCountAddOne() {
            // this是store实例
            return this.doubleCount + 1
        },
        // 3.getters也支持返回一个函数
        getFriendById(state) {
            return function(id) {
                for (let i = 0; i < state.subjects.length; i++) {
                    const subject = state.friends[i]
                    if (subject.id === id) {
                        return subject
                    }
                }
            }
        },
        // 4.getters中用到别的store中的数据
        showMessage(state) {
            // 1.获取user信息
            const userStore = useUser()

            // 2.获取自己的信息

            // 3.拼接信息
            return `name:${userStore.name}-count:${state.count}`
        }
    },
})

export default useCounter

06. Actions

Actions 相当于组件中的 methods。

  • 使用 defineStore() 中的 actions 属性定义。

一般网络请求相关的state管理(异步操作),就使用action完成。

import { defineStore } from 'pinia'

const useHome = defineStore("home", {
  state: () => ({
    banners: [],
    recommends: []
  }),
  actions: {
    async fetchHomeMultidata() {
      const res = await fetch("http://123.207.32.32:8000/home/multidata")
      const data = await res.json()

      this.banners = data.data.banner.list
      this.recommends = data.data.recommend.list
      
      // return new Promise(async (resolve, reject) => {
      //   const res = await fetch("http://123.207.32.32:8000/home/multidata")
      //   const data = await res.json()

      //   this.banners = data.data.banner.list
      //   this.recommends = data.data.recommend.list

      //   resolve("bbb")
      // })
    }
  }
})

export default useHome