pinia 5 大顶级技巧解析
前言
pinia的作者最近在博客上放出了一篇pinia的使用文章:《My Top 5 Tips for using Pinia》,里面讲述了5个一般不怎么知道的使用技巧,但是由于讲的很浅,我这边自己理解了一下,做了一些解释,顺带记个笔记。
不要创建无用的 getter
这点我感同身受,在最初学习vuex的时候,前辈们总是会说,getter有缓存,性能会更好,于是,在大多数的教程中,我们总是能看到这种示例写法:
export default Vuex.Store({
state: () => ({
counter: 0
}),
getters: {
// Completely useless getter
getCount: state => state.counter,
},
})
为此我也很长一段时间去模仿这种写法,给每个state属性都增加了getter,然后取值的时候总是通过getter取。
但是当项目的内容增加时,你会发现你的vuex仓库文件有着巨长巨长的属性,而且由于options的原因,常常state的属性在文件上部分,getter、Mutation、Action对应state中属性的操作,被分散在文件上中下各个部分,你不得不来回滚动文件来查看具体的代码内容。
这一度让我非常痛苦,但是好像没有什么更好的办法,特别是有些人不通过getter直接取用state中的属性时,仿佛被人打了一巴掌一样难受(我优化了什么啊~~~)。
但是事实就是,这些getter都是无用的,在pinia中,也是不推崇这种写法,如果你需要的值是一个依赖多个数据源的响应式数据,那么你可以使用getter,这非常正确,如果仅是将某一个属性getter化了,那实在没有必要了。
const counterStore = useCounterStore()
counterStore.counter // 0 ✅
作者还表示,如果没有必要,不需要使用storeToRefs()
或 toRef()
来对pinia仓库中的数据进行响应式处理,直接使用数据才是正确的。
在选项库中使用可组合项
这个标题有点拗口,其实意思就是除了使用vue提供的reactive
、ref
的方式声明一个响应式对象,还可以使用其他可以响应式的方式,这点在option写法和setup写法中应该都是适用的。
比如第三方库:@vueuse/core
里面会有一个方法也能创建响应式对象,这些也能被pinia使用。
import { useLocalStorage } from '@vueuse/core'
const useAuthStore = defineStore('auth', {
state: () => ({
user: useLocalStorage('pinia/user/login', 'alice'),
}),
})
import { refDebounced } from '@vueuse/core'
const useSearchStore = defineStore('search', {
state: () => ({
user: {
text: refDebounced(/* ... */),
},
}),
})
作者只提供了options的示例代码,setup的不太确定,但是我认为是可以的,因为useLocalStorage的类型是基于vue的Ref
类型做的二次声明,显然底层是相同的。
具有设置存储的复杂可组合项
这点和第二点差不多太多,都是在pinia的仓库中可以使用一些第三方插件提供的响应式对象,官方的示例是setup语法,使用的也是@vueuse/core。
import { useWebSocket } from '@vueuse/core'
export const useServerInfoStore = defineStore('server-info', () => {
const { status, data, send, open, close } = useWebSocket('ws://websocketurl')
return {
status,
data,
send,
open,
close,
}
})
我们简单看一下类型定义:
/**
* Reactive WebSocket client.
*
* @see https://vueuse.org/useWebSocket
* @param url
*/
export function useWebSocket<Data = any>(
url: MaybeRefOrGetter<string | URL | undefined>,
options: UseWebSocketOptions = {},
): UseWebSocketReturn<Data> {
const {
onConnected,
onDisconnected,
onError,
onMessage,
immediate = true,
autoClose = true,
protocols = [],
} = options
可以看到函数返回了一个UseWebSocketReturn<Data>
类型对象,我们再看这个类型:
export interface UseWebSocketReturn<T> {
/**
* Reference to the latest data received via the websocket,
* can be watched to respond to incoming messages
*/
data: Ref<T | null>
/**
* The current websocket status, can be only one of:
* 'OPEN', 'CONNECTING', 'CLOSED'
*/
status: Ref<WebSocketStatus>
/**
* Closes the websocket connection gracefully.
*/
close: WebSocket['close']
/**
* Reopen the websocket connection.
* If there the current one is active, will close it before opening a new one.
*/
open: Fn
/**
* Sends data through the websocket connection.
*
* @param data
* @param useBuffer when the socket is not yet open, store the data into the buffer and sent them one connected. Default to true.
*/
send: (data: string | ArrayBuffer | Blob, useBuffer?: boolean) => boolean
/**
* Reference to the WebSocket instance.
*/
ws: Ref<WebSocket | undefined>
}
可以看到他的数据对象也是Ref
类型,所以pinia自然而然可以使用它。
在pinia setup语法中使用 inject() 接收变量
标题原名是:inject()
within setup stores,机翻过来非常拗口,为此我自己翻译了一下。
我们先看示例:
import { useRouter } from 'vue-router'
export const useAuthStore('auth', () => {
const router = useRouter()
function logout() {
// logout the user
return router.push('/login')
}
return {
logout
}
})
作者使用了vue-router的hooks函数,但是并没有inject
这个函数代码,这是因为useRouter
函数内部使用了inject。
查看hooks源码:
import { inject } from 'vue'
import { routerKey, routeLocationKey } from './injectionSymbols'
import { Router } from './router'
import { RouteLocationNormalizedLoaded } from './types'
/**
* Returns the router instance. Equivalent to using `$router` inside
* templates.
*/
export function useRouter(): Router {
return inject(routerKey)!
}
/**
* Returns the current route location. Equivalent to using `$route` inside
* templates.
*/
export function useRoute(): RouteLocationNormalizedLoaded {
return inject(routeLocationKey)!
}
可以看到,它引入了routerKey, routeLocationKey
这两个值,这两个其实就是个Symbol
唯一值。
/**
* Allows overriding the router instance returned by `useRouter` in tests. r
* stands for router
*
* @internal
*/
export const routerKey = Symbol(__DEV__ ? 'router' : '') as InjectionKey<Router>
/**
* Allows overriding the current route returned by `useRoute` in tests. rl
* stands for route location
*
* @internal
*/
export const routeLocationKey = Symbol(
__DEV__ ? 'route location' : ''
) as InjectionKey<RouteLocationNormalizedLoaded>
由于没怎么看vue对于provide
和inject
源码,我大体猜测一下,provide方法会将传入的内容以key value的形式,存放在对应的组件实例的provides
对象属性上,通过inject获取时,需要传入对应的key,然后返回provides
对象上对应的内容,如果没有则会一层一层往上查找,直到最顶层的app组件,还没有则返回undefined
。
而pinia中能通过inject拿取到router,我认为是他在use安装的时候,将app实例保留了下来,然后做了一些特殊处理,我们可以理解为只能拿取app上provide挂载的内容,因为在vue-router的源码中,在install中确实通过app.provide
挂载了router和route。
官方提供了这个我感觉更适合搞多语言,路由跳转有点代码耦合了,而且什么情况需要跳转,大多数都是请求接口满足什么条件的情况下才会,如果在pinia仓库中调用接口,我是不推荐的,因为你会碰到循环引入的问题,这些有机会再说吧。
私有存储
一般情况下我们在pinia中声明的属性都是公开的,任何人获取到仓库的实例对象都能从中读取所有数据,但是有时候我们可能不希望有些数据被公开,那么如何隐藏这部分数据呢?
export const usePrivateAuthState('auth-private', () => {
const token = ref<string | null>(null)
return {
token,
}
})
export const useAuthStore('auth', () => {
const user = ref<User | null>(null)
const privateState = usePrivateAuthState()
privateState.token // accessible only within this store
return {
user,
}
})
官方做法是单独建立一个存储,在另一个存储中使用,你甚至都可以不用导出这个私有仓库,这样在外部我们就是一个隐藏的内容,通过公共仓库对外的api方法来实现增删改。
客户端和SSR一起使用
这个是作者特别提供的,告知了一些在SSR情况下使用的方法。
服务器端渲染 (SSR) 是提高应用程序性能的好方法。但是,与仅限客户端的应用程序相比,它带来了一些额外的困难。例如,您无权访问window
、 或document
或任何其他特定于浏览器的 API,例如本地存储。
在选项存储中,这要求你使用一个hydrate
选项来告诉 Pinia 某些状态不应该在客户端上被冻结:
import { useLocalStorage } from '@vueuse/core'
const useAuthStore = defineStore('auth', {
state: () => ({
user: useLocalStorage('pinia/user/login', 'alice'),
}),
hydrate(state, initialState) {
state.user = useLocalStorage('pinia/user/login', 'alice')
},
})
在安装程序存储区中,可以使用 skipHydrate
帮助程序将某些状态标记为仅限客户端:
import { defineStore, skipHydrate } from 'pinia'
const useAuthStore = defineStore('auth', () => {
const user = skipHydrate(useLocalStorage('pinia/user/login', 'alice'))
return { user }
})
这两个方法官方文档都有具体的说明,我们可以作为新知识扩展,等有机会用到SSR了就能用上了。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据