持续创作,加速成长!这是我参与「掘金日新计划 · 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个好处:
- 你只初始化了store一次。
- 你可以直接引入store后使用,不再需要初始化。
- 可以一次性对应多个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 😢
以上代码我都是直接在网页上敲的,如有错误,欢迎评论指正,谢谢