【Pinia】最新探索出的的几个小技巧<6 月更文第一天>

765 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

最近迷上了vue3,当然也用了pinia,发现了一些新的技巧和思路,与诸君分享

PS. 介绍pinia的文章太多了,如果你还不会使用pinia,可以随便在掘金上搜搜

自动初始化pinia Store

一般情况下,我们会这样使用pinia.第一步是声明store

import { defineStore } from 'pinia'

export const useCounter = defineStore('counter', {
  state(){
   return {
     count:0
   }
  }
})

然后在组件中使用的时候

<script setup>
import {storeToRefs} from 'pinia'
import {useCounter} from '@/store/counter'

const store = useCounter()
//或者直接使用store.count
const {count} = storeToRefs(store)
</script>

但这样有个问题:每次使用前都要重复引入并初始化store.

在我参考了一些其他写法之后,我研究出了这种写法.定义store那块暂时不改,我们在store目录创建index.js,导出一个执行registerStore函数。

在main.js中,注册pinia之后,执行registerStore函数.

//main.js
import { createPinia } from 'pinia'
import {registerStore} from '@/store'
const app = createApp(App)
app.use(createPinia())
registerStore()

现在看一下store目录这个index.js如何写。

import CounterStore from './counter'
export const storeInstance = {
  counter: null
}
const Store = new Proxy({},{
  get(target, p): any {
    const counter = storeInstance.counter
    if(['_counter'].includes(p)){
      return counter
    }
    if(app){
      return counter[p]
    }
    return counter?.[p]
  }
})

export const registerStore = () => {
  storeInstance.counter = CounterStore()
  Store._counter = storeInstance.counter
}

export default Store

首先我们引入你之前定义好的store,然后创建一个对象,这个对象里存放的就是初始化过的store实例。 之后,通过proxy保证你每次访问Store的时候,都会使用初始化过的CounterStore,避免了出现store没有初始化的错误. 使用起来也是十分简单

<script setup>
import {storeToRefs} from 'pinia'
import store from '@/store'

store.count++ 
// 或者
const {count} = storeToRefs(store)
count.value++
//也可以直接返回整个store
store._counter
</script>

这样写有3个好处:

  1. 你只初始化了store一次。
  2. 你可以直接引入store后使用,不再需要初始化。
  3. 可以一次性对应多个store

这里说一下第三点,假设我们还有一个appStore,这时候修改一下@/store

import CounterStore from './counter'
import AppStore from './app'
export const storeInstance = {
  counter: null,
  app:null
}
const Store = new Proxy({},{
  get(target, p): any {
    const pStringArr =  p.split('_')
    const keyName = pStringArr.slice(2).join('_')
    const storeName =  pStringArr[1]
    if(pStringArr.length === 2){
      return storeInstance[storeName]
    }
    
    if(storeInstance[storeName] && keyName){
      return storeInstance[storeName][keyName]
    } 
    return undefined
  }
})

export const registerStore = () => {
  storeInstance.counter = CounterStore()
  Store._counter = storeInstance.counter
  storeInstance.app = AppStore()
  Store._app = storeInstance.app
}

export default Store

我们指定下划线_作为分隔符,第一个字段是store的名字,对应storeInstance.xx,第二个值是store内部的值,因此我们可以这样使用

import {storeToRefs} from 'pinia'
import store from '@/store'

store._counter_count++ 
store._app_count2++ 

// 或者
const {count} = storeToRefs(store._counter)
count.value++

const {count2} = storeToRefs(store._app)
count2.value++

</script>

美中不足的是,这样做可能没有代码提示。。。

如果你有很多store,你可以再研究一下vite中import.glob或者webpack的require.context来实现自动引入,岂不美哉!!

无需action

pinia内部取消了Mutation,保留了Action.

现在我想说的是,Action也一定需要吗?

vue3中新增了Composition API,这个难道不可以替代Action?

我这里举个例子

import axios from 'axios'
import store from '@/store'
const api = async ()=>await axios.get('xxxx')
const getUserInfo = async ()=>{
    const data = await api()
    store.$patch(data)
}
export default getUserInfo

这难道不是一个action,我们一样可以在任何地方引用这个文件,可以把相同的逻辑封装在一起,同时自由组合,我觉得这样比你写一个action更好。

onlystate 只需状态

这时候有人说了,如果去掉action,那么pinia其实只剩下全局的状态,这时候我们只需要一个简单的reactive就可以了,为什么还要用pinia呢?

没错!我也是这样想的。于是我自己写了一个包,onlystate,用起来大概是这样的。为了避免pinia的初始化错误,我要求先初始化,因此需要先定义,然后在Vue.use

//store.js
import {defineState} from 'onlystate';
const install = defineState({
    a: 1,
    b: 2
}, {
    c: state => 'a=' + state.a,
    d: state => 'b=' + state.b
});
export default install;

使用的时候,格式上参考了react的useState,返回值默认使用toRefs处理,避免丢失响应性。

<script setup>
import {useState} from 'onlystate';
const [a, b] = useState('a', 'b');
//这样写也可以
const [c, d] = useState(['c', 'd']);
a.value = 2;
</script>

这个包仍处于初级阶段,仍有许多问题,虽然我用ts重写了,但是类型推断那块我还是不太会,写的typescript也慢慢变成了anyscript,但是我会继续学习,继续完善这个npm包,希望未来能让它取代pinia.

但是现在,我仍旧推荐pinia 😢

以上代码我都是直接在网页上敲的,如有错误,欢迎评论指正,谢谢