Advertisement

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/

全部评论 (0)

还没有任何评论哟~