初识 Pinia.js

468 阅读1分钟

初识 Pinia.js

前言

什么是 Pinia.js ? Pinia 是一个用于 Vue 的状态管理库,类似 Vuex, 是 Vue 的另一种状态管理方案。记得之前在面试某公司的时候,面试官问我一个问题,你会在每个 Vue 项目中都引入 Vuex 嘛,当时的我以为能用好用就可以了,没有考虑代码繁琐冗余等问题,如果在有同样的问题,我可能会回答,体积不是很庞大的项目,我会更倾向采用轻量级的 Pinia.js


选择 Pinia 的理由

  • 未来趋势 Pinia 本质上是由 Vuex 核心成员在 Vue 3.x/4.x 的基础上进行改进开发。据官网描述, Pinia 已实现了在 Vuex5.x 中想要的大部分内容。
  • 体积方面 Pinia 在重约 1kb前提下,实现了 vuex 中的 state,getters,actions,这里 Pinia,把 mutations 去掉,可通过 $patch 来批量修改 state 中的属性也可以直接字面量赋值修改
  • 开发工具方面 Pinia 与 Vue devtools 挂钩,为您提供增强的 Vue 2 和 Vue 3 开发体验。
  • 使用方面,可直接引入即用,方便简单快捷,没有模块概念。某个页面/组件需要这个库可直接引入使用,库与库之间也可以引入使用。
  • 完美支持 TS ,与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

对比 Vue 3.x/4.x

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

安装

  • yarn add pinia
    
    # or with npm
    
    npm install pinia
    

挂载全局实例

  • main.js 中挂载, 我这里使用的是 vue3.2 ,vue 2的写法也是 app.use 即可。

  •  import { createPinia } from 'pinia'
     import { createApp } from 'vue'
     import App from './App.vue'
    
     createApp(App).use(createPinia()).mount('#app')
    

示例

  • 这边我将用 Pinia 完成简单的一个登录登出需求,具体实现为, vue 父子组件。
  • 父组件展示一个用户当前登录状态字段。 'isLogin' ,该字段存储将从我们的 userStore 当中获取。
  • 子组件展示一个 userName && passWord 输入框来进行数据录入,存储在我们的 userStore 当中。
  • store 方面我们使用 store 中的 action 进行逻辑处理,与 state 操作。

1. 创建一个 userStore

  • 创建 userStore 完成用户登录登出逻辑的攥写。
user.js
  • 这里给到 store actions 中两个方法 login / logout, 并简单写一个用户校验方法。

  • 这里可以发现 Piniaactions 是支持同步 && 异步的。

  •   import { defineStore } from 'pinia'
      /**
       * Simulate a login
       * @param {string} a
       * @param {string} p
       */
       // 简单校验方法
      function apiLogin(a, p) {
          if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true })
          if (p === 'ed') return Promise.resolve({ isAdmin: false })
          return Promise.resolve({ isAdmin: false })
      }
      // 导出 user 库
      export const useUserStore = defineStore({
          id: 'user',
          state: () => ({
              name: 'Eduardo',
              isAdmin: false,
          }),
    
          actions: {
              // 退出
              logout() {
                  this.$patch({
                      name: '',
                      isAdmin: false,
                  })
              },
              /**
               * @param {string} username
               * @param {string} password
               */
              async login(username, password) {
                  const userData = await apiLogin(username, password)
                  this.$patch({
                      name: username,
                      ...userData,
                  })
                  return userData
              },
          },
      })
    

2. 父组件 vue 代码

  • 这里只展示一个字段,并且附上监听 store 字段变化的方式,可用用作其他逻辑处理
user.vue
  •  <template>
       <div>
         <h1>父组件</h1>
         <h2>当前用户登录状态 {{ userStore.isLogin ? "在线" : "离线" }}</h2>
         <Demo> </Demo>
       </div>
     </template>
     <script>
     import { watch, } from "vue"
     import Demo from './components/Demo'
     import { useUserStore } from './stores/user'
     export default {
       name: 'User',
       setup() {
         // 引入
         const userStore = useUserStore()
         watch(() => userStore.isLogin, (newValue, oldValue) => {
           console.log(newValue, oldValue)
           console.log("当前用户登录状态")
         })
         return {
           userStore
         }
       },
       components: {
         Demo,
       },
     }
     </script>
    

3. 子组件 vue 代码

  • 这里展示两个输入框,用户名和密码的输入。
  • 登录登出两个按钮事件。
  • 完成通过操作 userStore 对父组件之间的通信。
login.vue
  •  <template>
       <div>
         <h1>子组件</h1>
         <label>用户名:</label>
         <input type="text" v-model="userName" />
         <label>密码:</label>
         <input type="text" v-model="passWord" />
         <button style="margin-left: 30px" @click="pinaiEvent">登录</button>
         <div style="margin-top: 30px">
           <button @click="userStoreReSetEvent">退出登录</button>
         </div>
         <h2>msg:{{ logMsg }}</h2>
       </div>
     </template>
     
     <script>
     import { useUserStore } from '../stores/user'
     import { reactive, toRefs, } from 'vue'
     export default {
       name: 'Login',
       setup() {
         const state = reactive({
           modalOpen: false,
           userName: '',
           passWord: '',
           logMsg: '',
         })
         const userStore = useUserStore()
         const pinaiEvent = async () => {
           let temp = await userStore.login(state.userName, state.passWord)
           console.log(temp)
           if (temp.isLogin) {
             state.logMsg = '登录成功'
             return
           }
           state.logMsg = '登录失败'
         }
         // user仓库制空
         const userStoreReSetEvent = () => {
           // userStore.$reset() 
           userStore.logout()
           state.logMsg = '退出登录'
         }
         return {
           ...toRefs(state),
           pinaiEvent,
           userStore,
           userStoreReSetEvent
         }
       },
     }
     </script>
    

4. 效果展示

  • image.png

5. 功能简述

  • Pinia 也是有很多实用的 API 比如可以在 vue 组件中直接操作 store 清空 store.$reset()
  • 也可以直接在 vue 组件中操作 store 中的 state实现双向绑定,但是这边我个人使用还是倾向于通过 store 内部 actions 去操作 state, 不然项目迭代过久,会变的难以维护。
  • 操作 state 可以在 action 中去字面量赋值 ,也可以用 $patch来修改多个 state 中属性。
  • 在 Pinia 当中只是移除了大家印象里的 mutionse, getters 还是存在的,大家可以根据按需使用。

总结

  • 至此完成了一个 Pinia 简单的小功能的实现,大家可以根据自己项目按需使用,感觉 Pinia 这种轻量化仓库会是近段时间的一个趋势。

  • 大家有什么疑惑和更实用的 Pinia 小技巧欢迎在评论区沟通,共同探讨。

  • 以下为官方文档,方便同学们进一步学习Pinia


参考文献