来自学习视频:VueUse 作者 Anthony Fu 分享可组合的 Vue
ref & reactive
| ref | reactive | |
|---|---|---|
| PROS | 显式调⽤,类型检查 相⽐ Reactive 局限更少 | ⾃动 Unwrap (即不需要 .value ) |
| CONS | .value | 在类型上和⼀般对象没有区别 使⽤ ES6 解构会使响应性丢失 需要使⽤箭头函数包装才能使⽤ watch |
需要 watch reactive 对象的某个属性,需要使用箭头函数:
const bar = reactive({props: 0})
watch(()=>bar.props, newVal=>{
console.log('newVal', newVal) // make sense
})
watch(bar.props, newVal=>{
console.log('props', newVal)
})
ref 自动解包
在众多情况下,我们可以减少 .value 的使⽤:
-watch 直接接受 ref 作为监听对象,并在回调函数中返回解包后的值
const counter = ref(0)
watch(counter, count => {
console.log(count)
})
- ref 在模板中自动解包
<template>
<button @click="counter += 1">Counter is {{ counter }}</button>
</template>
- 使用 reactive 解包嵌套的 ref
import { ref, reactive } from 'vue'
const foo = ref('bar')
const data = reactive({foo, id: 10})
data.foo // 'bar'
模式:接受 Ref 作为函数参数
使用 Ref 类型作为函数参数,可以返回一个响应式的结果,例子 useTitle
// useTitle.ts
import { ref, watch, Ref } from 'vue'
type MaybeRef<T> = Ref<T> | T
export function useTitle(newTitle: MaybeRef<string | null | undefined>){
const title = ref(newTitle || document.title)
watch(title, (t)=>{
if(t!=null){
document.title = t
}
}, {immediate: true})
return title
}
useTitle 函数可以接受一个普通变量作为参数,也可以接受 Ref 类型的变量作为参数。
传递一个 Ref,当源 ref 更改时,标题将被更新:
const messages = ref(0)
const title = computed(() => {
return !messages.value ? 'No message' : `${messages.value} new messages`
})
useTitle(title) // document title will match with the ref "title"
setTimeout(()=>{
messages.value = 2
}, 2000)
2 秒后标题从 No message 变为 2 new messages。
重复使用已有的 Ref
如果将⼀个 ref 传递给 ref() 构造函数,它将会原样将其返回。
const foo = ref(1)
const bar = ref(foo)
foo === bar // true
在上面的 useTitle 例子中也有用到这一点,如果 newTitle 是 ref 类型的,那么会重复利用已有的。
模式:返回由 Ref 组成的对象
以在使⽤可组合的函数式,同时获得 ref 和 reactive 的好处。
import { ref, reactive } from 'vue'
export function useMouse(){
return{
x: ref(0),
y: ref(0)
}
}
// 直接使用 ES6 解构其中的 Ref 使用
const { x, y } = useMouse()
// 使用 reactive 自动解包功能
const mouse = reactive(useMouse())
mouse.x === x.value // true
例子 useFetch
// useFetch.ts
import { Ref, shallowRef, unref, } from 'vue'
type MaybeRef<T> = Ref<T> | T
export function useFetch<T>(url: MaybeRef<string>){
const data = shallowRef<T | undefined>()
const error = shallowRef<Error | undefined>()
fetch(unref(url))
.then(r => r.json())
.then(r => data.value = r)
.catch(e => error.value = e)
return {
data,
error
}
}
shallowRef只会监听.value这一层变化,非深度监听数据的变化,提高性能。unref是 Ref 的反操作:如果传入的是一个 Ref,返回其值;否则原样返回。 使用:
const { data } = useFetch('https://api.github.com/')
const user_url = computed(()=>data.value?.user_url)
先建⽴数据间的“连结” ,然后再等待异步请求返回将数据填充。概念和 React 中的 SWR (stale-while-revalidate) 类似。
模式:副作用自动清除
Vue 中原⽣的 watch 和 computed API 会在组件销毁时⾃动解除其内部的依赖监听。 我们可以编写我们的函数时,遵循同样的模式。
import { onUnmounted } from 'vue'
export function useEventListener(target: EventTarget, name: string, fn: any) {
target.addEventListener(name, fn)
onUnmounted(() => {
target.removeEventListener(name, fn) // <--
})
}
模式:状态共享
由于组合式 API 天然提供的灵活性,状态可以独⽴于组件被创建并使⽤。
// shared.ts
import { reactive } from 'vue'
export const state = reactive({
foo: 1,
bar: 'hello'
})
使用:
// A.vue
import { state } from './shared.ts'
state.foo += 1
// B.vue
import { state } from './shared.ts'
console.log(state.foo) // 2
此⽅案不兼容 SSR!
原因:cn.vuejs.org/guide/scali…
模式:兼容 SSR 的状态共享
使⽤ provide 和 inject 来共享应⽤层⾯的状态。
// useState.ts
import { InjectionKey, App, inject, reactive } from "@vue/runtime-core";
export interface MyState{
id: number
name: string
}
export const myStateKey: InjectionKey<MyState> = Symbol()
export function createMyState(){
const state = reactive({
id: 1,
name: 'vue3'
})
return {
install(app: App){
app.provide(myStateKey, state)
}
}
}
export function useMyState(): MyState{
return inject(myStateKey)!
}
- Vue 提供了
InjectionKey类型⼯具来在不同的上下⽂中共享类型。juejin.cn/post/700033… - 在 TS 中
!用于告诉编译器变量不存在undefined或null的情况。
使用:
// main.ts
const app = createApp(App)
app.use(createMyState())
// A.vue
// 在任何组件中使⽤这个函数来获得状态对象
const state = useMyState()
Vue Router v4 也使用的类似的方式:
createRouter()useRouter()