Pinia 核心概念详解

715 阅读3分钟

1. Pinia介绍和Vuex的区别

  • Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近piña(西班牙语中的菠萝)的词;

    • Pinia开始于大概2019年,最初是作为一个实验为Vue重新设计状态管理,让它用起来像组合式API(Composition API)

    • 从那时到现在,最初的设计原则依然是相同的,并且目前同时兼容Vue2、Vue3,也并不要求使用Composition API

    • Pinia本质上依然是一个状态管理的库,用于跨组件、页面进行状态共享(这点和Vuex、Redux一样)

  • 已经有Vuex了吗?为什么还要用Pinia呢?

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

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

  • Pinia 的优势:

    • mutations 不再存在:

      • 经常被认为是 非常 冗长

      • 最初带来了 devtools 集成

    • 更友好的TypeScript支持,Vuex之前对TS的支持很不友好

    • 不再有modules的嵌套结构

      • 可以灵活使用每一个store,它们是通过扁平化的方式来相互使用的
    • 也不再有命名空间的概念,不需要记住它们的复杂关系

image.png

2. Pinia的安装和基本使用

  • 安装(本文使用版本为:2.0.16)
    • npm install pinia
  • createPinia
    import { createPinia } from 'pinia'
    
    const pinia = createPinia()
    
    export default pinia
    
  • store的理解
    • 一个 Store (如 Pinia)是一个实体,它会持有为绑定到组件树的状态和业务逻辑,也就是保存了全局的状态

    • 它有点像始终存在,并且每个人都可以读取和写入的组件

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

  • 定义store
    • store 是使用 defineStore() 定义的

    • 它需要一个唯一名称,作为第一个参数传递

    • 这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools

    • 返回的函数统一使用useX作为命名方案,这是约定的规范

    // 定义关于counter的store
    import { defineStore } from 'pinia'
    
    const useCounter = defineStore('counter', {
      state: () => ({
        count: 99
      })
    })
    
    export default useCounter
    
  • 使用

    • store在它被使用之前是不会创建的,可以通过调用use函数来使用store

    • 注意store获取到后不能被解构,那么会失去响应式:

      • 为了从 Store 中提取属性同时保持其响应式,需要使用storeToRefs()
    <template>
      <div class="home">
        <h2>Home Page</h2>
        <h2>count: {{ counterStore.count }}</h2>
        <h2>count: {{ count }}</h2>
        <h2>count: {{ pCount }}</h2>
      </div>
    </template>
    
    <script setup>
    import { toRefs } from 'vue'
    import { storeToRefs } from 'pinia'
    import useCounter from '../store/counter'
    
    const counterStore = useCounter()
    const { count } = toRefs(counterStore)
    
    const { count: pCount } = storeToRefs(counterStore)
    
    </script>
    
  • Store有三个核心概念:

    • state、getters、actions

    • 等同于组件的data、computed、methods

    • 一旦 store 被实例化,就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性

3. Pinia核心state

  • 使用和修改 store.xxx
  • 了解:
    • store.$reset
    • store.$patch
    • store.$state = {}, 赋值一个新的对象
    <template>
      <div class="home">
        <h2>Home Page</h2>
        <h2>name: {{ name }}</h2>
        <h2>age: {{ age }}</h2>
        <h2>score: {{ score }}</h2>
        <button @click="changeUser">修改user</button>
        <button @click="resetUser">重置user</button>
      </div>
    </template>
    
    <script setup>
    import { storeToRefs } from 'pinia'
    import useUser from '../store/user'
    
    const userStore = useUser()
    
    const { name, age, score } = storeToRefs(userStore)
    
    function changeUser() {
      // 一个一个修改
      // userStore.name = '韩梅梅'
      // userStore.age = 17
      // userStore.score = 100
    
      // 一次性修改多个状态
      // userStore.$patch({
      //   name: 'Jim',
      //   age: 19
      // })
    
      // 替换state为新的对象
      userStore.$state = {
        name: '王老师',
        age: 30,
        // score: 'max'
      }
    }
    
    function resetUser() {
      userStore.$reset()
    }
    
    </script>
    

4. Pinia核心getters

  • 基本使用

  • 引入其他的getters

  • getters返回函数

  • getters引入其他store数据

    // 定义关于counter的store
    import { defineStore } from 'pinia'
    
    // 引入其他store
    import useUser from './user'
    
    const useCounter = defineStore('counter', {
      state: () => ({
        count: 99,
        friends: [
          { id: 111, name: "kobe", age: 24 },
          { id: 112, name: "james", age: 22 },
          { id: 113, name: "curry", age: 21 }
        ],
      }),
      getters: {
        // 基本使用
        doubleCount(state) {
          return state.count * 2
        },
        // 引入另外的getter
        doubleCountAddOne() {
          // this指向store实例
          return this.doubleCount + 1
        },
        // 返回一个函数
        getFriendById(state) {
          return function(id) {
            return state.friends.find(friend => friend.id === id)
          }
        },
        // 使用别的store中的数据
        showMessage(state) {
          // 1. 获取user信息
          const userStore = useUser()
          // 2.获取自己的信息
    
          // 3.拼接信息
          return `name: ${userStore.name}-count: ${state.count}`
        }
      }
    })
    
    export default useCounter
    

5. Pinia核心Actions

  • 基本使用

  • 传入参数

    // 定义关于counter的store
    import { defineStore } from 'pinia'
    
    import useUser from './user'
    
    const useCounter = defineStore('counter', {
      state: () => ({
        count: 99,
        friends: [
          { id: 111, name: "kobe", age: 24 },
          { id: 112, name: "james", age: 22 },
          { id: 113, name: "curry", age: 21 }
        ],
      }),
      getters: {...},
      actions: {
        increment(state) {
          // 此处通过 this 访问实例,不传递state
          console.log(state) // undefined
          this.count++
        },
        incrementWithN(n) {
          this.count += n
        }
      }
    })
    
    export default useCounter
    
  • 发送异步请求

  • 返回Promise回调

    import { defineStore } from 'pinia'
    
    const useHome = defineStore('home', {
      state: () => ({
        banners: [],
        recommends: []
      }),
      actions: {
        async fetchHomeMultiData() {
          const res = await fetch('http://xxx.207.xx.32:8000/home/multidata')
          const { data } = await res.json()
    
          this.banners = data.banner.list
          this.recommends = data.recommend.list
          return data
        }
      }
    })
    
    export default useHome
    
    <template>
      <div class="home">
        <h2>Home Page</h2>
        <h2>count: {{ counterStore.count }}</h2>
        <h2>doubleCount: {{ counterStore.doubleCount }}</h2>
        <button @click="changeState">count+1</button>
        <button @click="changeCountWithN">count+10</button>
        <hr>
        <h2>banners:</h2>
        <ul>
          <template v-for="item in homeStore.banners" :key="item.acm">
            <li>{{ item.title }}</li>
          </template>
        </ul>
      </div>
    </template>
    
    <script setup>
    import useCounter from '../store/counter'
    import useHome from '../store/home'
    
    const counterStore = useCounter()
    
    function changeState() {
      counterStore.increment()
    }
    
    function changeCountWithN() {
      counterStore.incrementWithN(10)
    }
    
    const homeStore = useHome()
    
    homeStore.fetchHomeMultiData().then(res => {
      console.log('fetchHomeMultiData的action已完成: ', res)
    })
    
    </script>