下一代vue状态管理工具 pinia,快来看看这个可爱的菠萝吧

1,559 阅读4分钟

尝试过vuex @4.x的同学都知道,对ts的支持不是很好,写起来不是很方便。不了解的同学请看这篇文章

pinia官网

pinia简单介绍

Pinia是新一代的状态管理器,由 Vue.js团队中成员Phan An所开发的,因此也被认为是下一代的 Vuex,即 Vuex5.x。并被加入官方账户下

Pinia 有如下特点:

  • 支持options api和composition api。
  • 完整的 typescript 的支持。
  • 去除 mutations,只有 state,getters,actions。
  • actions 支持同步和异步。
  • 没有模块嵌套,扁平式的模块组织方式,极大的简化了代码书写过程。
  • 自动代码分割。
  • 支持vue devtools。

安装

    npm install pinia
    // yarn add pinia

在vue中注册该库。

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

const app = createApp(App)

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

核心概念

pinia从使用角度和vuex几乎一样。通过defineStore来创建一个容器对象。

state

就是以前vuex中的state。类似于组件的data,保存该模块下的数据。

    import { defineStore } from 'pinia'

    const useUserStore = defineStore('user', {
      state: () => {
        return {
          name "zh",
          age: 20,
          friends: []
        }
      }
    })

如何获取和修改该state中的数据呢?下面就来介绍一下。

获取state属性

直接导入该模块

    <ul>
      <li>姓名: {{ userStore.name }}</li>
      <li>年龄: {{ userStore.age }}</li>
      <li>朋友: {{ userStore.friends }}</li>
    </ul>
    
   import useUserStore from '../store/userStore'

    const userStore = useUserStore()

看到上面取值这么麻烦,那我来解构看看吧。

    <ul>
      <li>姓名: {{ name }}</li>
      <li>年龄: {{ age }}</li>
      <li>朋友: {{ friends }}</li>
    </ul>

    // 不能直接解构,结构后数据将不再是响应式。
    const { name, age, friends } = userStore

貌似上面也能展示,没啥问题,那么我们来尝试改变一下数据,看看界面的变化。

    <h1>响应式state数据</h1>
    <ul>
      <li>姓名: {{ userStore.name }}</li>
      <li>年龄: {{ userStore.age }}</li>
      <li>朋友: {{ userStore.friends }}</li>
    </ul>

    <h1>非响应式state数据,通过解构</h1>
    <ul>
      <li>姓名: {{ name }}</li>
      <li>年龄: {{ age }}</li>
      <li>朋友: {{ friends }}</li>
    </ul>

    <button @click="handleUserState">修改数据:handleUserState</button>
    const handleUserState = () => {
      userStore.$patch((state) => {
        state.name = 'llm'
        state.age = 30
        state.friends.push('llm', 'zh')
      })
    }

btn2.gif 从上面的gif可以看出,基本数据类型是不会做到响应式的,但是引用数据类型可以。(这一问题,通过不同方式修改state,会有不同的效果。因为state返回数据的引用问题。直接修改后,返回的仍旧是原来的对象, 通过$patch和action派发返回的是一个对于原来对象的拷贝。)尽管如此,如果我们想要解构state,那么我们需要使用pinia提供的storeToRefs来为我们服务。

     <h1>响应式state数据,通过解构</h1>
    <ul>
      <li>姓名: {{ reactiveName }}</li>
      <li>年龄: {{ reactiveAge }}</li>
      <li>朋友: {{ reactiveFriends }}</li>
    </ul>
    // 将其变为响应式
    const {
      name: reactiveName,
      age: reactiveAge,
      friends: reactiveFriends,
    } = storeToRefs(userStore)

reactive.gif

修改state属性

  • 直接修改
  userStore.name = 'llm'
  • 通过$patch进行批量修改。
    • 可以直接传入一个对象。导入模块获取state值进行修改。
    • 也可以传入一个函数。该函数接收state作为参数。
// 这种方式只有基本数据类型不能响应式
 userStore.$patch((state) => {
    state.name = 'llm'
    state.age = 30
    state.friends.push('llm', 'zh')
  })
  
  // 这种方式任何数据都不能作为响应式
    userStore.$patch({
        name: 'llm',
        age: 30,
        friends: [...userStore.friends, 'zh', 'llm'],
    })
  • 通过action派发。
    // useUserStore.js
    import { defineStore } from 'pinia'
    const useUserStore = defineStore('user', {
      state: () => {
        return {
          name: 'zh',
          age: 20,
          friends: []
        }
      },
      actions: {
        changeState() {
          this.name = 'llm'
          this.age = 30
          this.friends.push('zj', 'llm')
        }
      }
    })
  // vue
  userStore.changeState()

这些方式,我依旧是推荐使用action派发,从而做到统一管理。解构state对象时,一定要使用storeToRefsapi。

action

上面已经提到了,就是对state做更新的。和vuex中action一样。但是用法有所改变。 如果管理数据,基本上都是用过action派发函数,利于管理。 下面来看看吧。

基本使用

获取state中的数据直接通过this即可。

     // useUserStore.js
    import { defineStore } from 'pinia'
    const useUserStore = defineStore('user', {
      state: () => {
        return {
          name: 'zh',
          age: 20,
          friends: []
        }
      },
      actions: {
        changeState() {
          this.name = 'llm'
          this.age = 30
          this.friends.push('zj', 'llm')
        }
      }
    })

组件中使用直接通过该模块调用action函数即可。

  userStore.changeState()

处理异步数据

    // 更新异步数据
    getSong() {
      axios({
        url: 'http://123.207.32.32:9001/song/detail?ids=1441758494'
      }).then((res) => {
        console.log('res', res.data.songs[0])
      })
    }

和其他store模块中的action函数使用

其实就是导入其他模块的store,然后直接调用对应的action即可。

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

    export const useSettingsStore = defineStore('settings', {
      state: () => ({
        preferences: null,
        // ...
      }),
      actions: {
        async fetchUserPreferences() {
          const auth = useAuthStore()
          if (auth.isAuthenticated) {
            this.preferences = await fetchPreferences()
          } else {
            throw new Error('User must be authenticated')
          }
        },
      },
    })

getter

用法同vuex中的getter。类似于计算属性,具有缓存功能。利于性能优化。

基本使用

  • getter函数函数中可以使用其他getter。 直接通过this获取即可。
import { defineStore } from 'pinia'

const useUserStore = defineStore('user', {
  // arrow function recommended for full type inference
  state: () => {
    return {
      name: 'zh',
      age: 20,
      friends: [],
      song: {},
      discount: 0.6,
      books: [
        { name: '深入Vuejs', price: 200, count: 3 },
        { name: '深入Webpack', price: 240, count: 5 },
        { name: '深入React', price: 130, count: 1 },
        { name: '深入Node', price: 220, count: 2 }
      ]
    }
  },
  getters: {
    currentDiscount(state) {
      return state.discount * 0.9
    },
    totalPrice(state) {
      let _totalPrice = 0
      for (const book of state.books) {
        _totalPrice += book.count * book.price
      }
      return _totalPrice * this.currentDiscount
    }
  }
})
  • 如果想向getter函数传递参数,我们可以让getter返回一个函数,在外部调用,并传入参数即可。
  getters: {
    currentDiscount(state) {
      return state.discount * 0.9
    },
    totalPrice(state) {
      let _totalPrice = 0
      for (const book of state.books) {
        _totalPrice += book.count * book.price
      }
      return _totalPrice * this.currentDiscount
    },
    totalPriceCountGreaterN(state) {
      return function (n) {
        let totalPrice = 0
        for (const book of state.books) {
          if (book.count > n) {
            totalPrice += book.count * book.price
          }
        }
        return totalPrice * this.currentDiscount
      }
    }
  }
// vue
<h1>getters展示</h1>
<div>{{ userStore.totalPrice }}</div>
<div>{{ userStore.totalPriceCountGreaterN(2) }}</div>

image.png

使用其他store模块的getter

    import { useOtherStore } from './other-store'

    export const useStore = defineStore('main', {
      state: () => ({
        // ...
      }),
      getters: {
        otherGetter(state) {
          const otherStore = useOtherStore()
          return state.localData + otherStore.data
        },
      },
    })

注意事项

我们定义getter函数时,可以接受state参数。

  • 如果未接受,并且使用了ts,那么我们需要指定返回值类型。
  • 如果接受了,可以不指定返回值类型。

兄弟们,pinia真的很香,没有了模块的嵌套,对ts支持更是优秀,没学习的抓紧时间啊。学习过vuex的人,上手很容易。

有帮助的就点一个star吧。感谢你们。