vue2中使用pinia(vuex转pinia)

背景

最近项目中,想在vue2中把状态管理库从vuex转到pinia。

如何从vuex转到pinia

安装和配置

1. 安装

"@vue/composition-api": "^1.7.0",
"pinia": "^2.0.17",
import { PiniaVuePlugin, createPinia } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  router,
  store,
  render: h => h(App),
  pinia
}).$mount('#app')

2. 配置vue-cli识别mjs后缀文件

由于pinia的源码文件后缀是mjs和cjs,webpack默认不识别该后缀的文件,我们项目中是通过import的方式导入的,需要配置vue-cli匹配到mjs文件时,把文件代码识别成ES Module格式导入

configureWebpack: {
    module: {
      rules: [
        {
          test: /\.mjs$/,
          include: /node_modules/,
          type: 'javascript/auto'
        }
      ]
    }
  }

pinia与 Vuex 的比较

  1. mutations不再存在。在pinia的思想中被认为是多余的,延长了状态更改的链路,action和mutation在vuex中最初是为了和devtools插件有更好的联系而存在的,pinia中可以直接修改状态(devtools能够记录下来state的变化),或者提交action修改状态
  2. 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  3. 不再需要注入(mixin)、你只需要在想改变状态的地方,导入函数、调用函数即可
  4. 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
  5. 不再有 modules 的嵌套结构(命名空间模块)。你仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但不建议, Pinia 提倡平级的去管理各个store,同时支持 Store 之间的交叉组合方式

第5点举例:vuex的组织方式可能是这样的:嵌套

src
└── store
    ├── index.js          
    └── modules
        ├── module1.js    
        └── nested
            ├── index.js   
            ├── module2.js
            └── module3.js 

重构成pinia后:平级

src
└── stores
    ├── index.js      
    ├── module1.js      
    ├── nested-module2.js 
    ├── nested-module3.js 
    └── nested.js         

pinia在vue2中基本使用:

修改Store中的State状态(直接修改$patch\actions修改$reset)

现有store如下:

import { defineStore } from "pinia";
export const storeA = defineStore("storeA", {
  state: () => {
    return {
      msg: "hello pinia",
      name: "hmi",
    };
  },
  getters: {},
  actions: {
    setName(data) {
      this.name = data;
    },
  },
});

直接修改

<template>
  <div>{{ piniaStoreA.msg }}</div>
</template>

<script setup>
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.msg = 'hello world'
</script>

$patch

import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.$patch({
  msg: 'hello world',
  name: '111'
})
console.log(piniaStoreA.name);//111

actions修改

import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.setName('111')

重置state:使用$reset将状态重置为初始值

import { storeA } from '@/piniaStore/storeA' 
let piniaStoreA = storeA()
piniaStoreA.$reset()

Store中的State,Getters映射到组件中:mapState

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // 允许访问组件内的 this.doubleCounter
    // 与从 store.doubleCounter 中读取相同
    ...mapState(useCounterStore, ['doubleCount'])
    // 与上面相同,但将其注册为 this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'doubleCounter',
      // 您还可以编写一个访问 store 的函数
      double: store => store.doubleCount * 2,
      // 它可以正常读取“this”,但无法正常写入...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

一次性把store映射到组件中(一个个映射太麻烦了):mapStores

import { mapStores } from 'pinia'

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

export default {
  computed: {
    ...mapStores(useCartStore, useUserStore),
    }),
  },

  methods: {
    async buyStuff() {
      //默认情况下(可改),Pania 会为每个商店id 添加"Store"后缀。
      //所以需要以 user+StoreId 的命名去调用
      if (this.userStore.isAuthenticated()) {
        await this.cartStore.buy()
        this.$router.push('/purchased')
      }
    },
  },
}

Store的actions映射到组件中:mapActions

import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  methods: {
    //  this.increment()的方式去访问
    //  store.increment() 调用也可以
    ...mapActions(useCounterStore, ['increment'])
    //  给store中的doubleCounter方法在组件中改个名字去调用
    ...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
  },
}

特别注意:

1. pinia需要在用到的时候再用

错误用法:导入立马使用,会报错没有注册pinia,至少给个延迟打印才行

<script>
import storeId from './pinia1.js' 
console.log(storeId())  //报错
</script>

正确用法:data函数不是马上调用的

<script>
import storeId from './pinia1.js' 
export default {
data () {
    return {
      storeId: storeId()
    }
  },
}
</script>

2. pinia的store不能被解构

错误用法:count2没有响应性

<template>
    {{count2}}
</template>

<script>
import storeId from './pinia1.js' 
export default {
data () {
    return {
      count2: storeId().count
    }
  },
}
</script>

正确用法:count2具有响应性

<template>
    {{count2}}
</template>

<script>
import storeId from './pinia1.js' 
import { storeToRefs } from 'pinia'
export default {
data () {
    return {
       count2: storeToRefs(storeId()).counter
    }
  },
}
</script>

3. pinia中定义的每个store,id必须不同

Vuex转pinia的一个较完整示例:

vuex:

import { Module } from 'vuex'
import { api } from '@/api'
import { RootState } from '@/types' // if using a Vuex type definition

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

const storeModule: Module<State, RootState> = {
  namespaced: true,
  state: {
    firstName: '',
    lastName: '',
    userId: null
  },
  getters: {
    firstName: (state) => state.firstName,
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // combine with some state from other modules
    fullUserDetails: (state, getters, rootState, rootGetters) => {
      return {
        ...state,
        fullName: getters.fullName,
        // read the state from another module named `auth`
        ...rootState.auth.preferences,
        // read a getter from a namespaced module called `email` nested under `auth`
        ...rootGetters['auth/email'].details
      }
    }
  },
  actions: {
    async loadUser ({ state, commit }, id: number) {
      if (state.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      commit('updateUser', res)
    }
  },
  mutations: {
    updateUser (state, payload) {
      state.firstName = payload.firstName
      state.lastName = payload.lastName
      state.userId = payload.userId
    },
    clearUser (state) {
      state.firstName = ''
      state.lastName = ''
      state.userId = null
    }
  }
}

export default storeModule

pinia:

import { defineStore } from 'pinia'
import { useAuthPreferencesStore } from './auth-preferences'
import { useAuthEmailStore } from './auth-email'
import vuexStore from '@/store' // for gradual conversion, see fullUserDetails

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

export const useAuthUserStore = defineStore('auth/user', {
  // convert to a function
  state: (): State => ({
    firstName: '',
    lastName: '',
    userId: null
  }),
  getters: {
    // firstName getter removed, no longer needed
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // must define return type because of using `this`
    fullUserDetails (state): FullUserDetails {
      // import from other stores
      const authPreferencesStore = useAuthPreferencesStore()
      const authEmailStore = useAuthEmailStore()
      return {
        ...state,
        // other getters now on `this`
        fullName: this.fullName,
        ...authPreferencesStore.$state,
        ...authEmailStore.details
      }

      // alternative if other modules are still in Vuex
      // return {
      //   ...state,
      //   fullName: this.fullName,
      //   ...vuexStore.state.auth.preferences,
      //   ...vuexStore.getters['auth/email'].details
      // }
    }
  },
  actions: {
    // no context as first argument, use `this` instead
    async loadUser (id: number) {
      if (this.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      this.updateUser(res)
    },
    // mutations can now become actions, instead of `state` as first argument use `this`
    updateUser (payload) {
      this.firstName = payload.firstName
      this.lastName = payload.lastName
      this.userId = payload.userId
    },
    // easily reset state using `$reset`
    clearUser () {
      this.$reset()
    }
  }
})

参考:

  1. https://pinia.web3doc.top/
  2. https://pinia.vuejs.org/

你可能感兴趣的:(前端,Vue,javascript,前端,vue.js,pinia,vuex)