可组合的 Vue 的最佳实践和技巧

429 阅读3分钟

来自学习视频:VueUse 作者 Anthony Fu 分享可组合的 Vue

ref & reactive

refreactive
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 例子中也有用到这一点,如果 newTitleref 类型的,那么会重复利用已有的。

模式:返回由 Ref 组成的对象

以在使⽤可组合的函数式,同时获得 refreactive 的好处。

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 中原⽣的 watchcomputed 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()

视频 PDF 下载
可组合的 VueUse