Skip to content
On this page

Vue 2 & 3

组合式 api 技巧

  1. useDark
ts
const isDark = computed({
  get() {
    return store.value === 'auto' ? preferredDark.value : store.value === 'dark'
  },
  set(v) {
    if (v === preferredDark.value) store.value = 'auto'
    else store.value = v ? 'dark' : 'light'
  }
})
  1. ref & reactive (尽量使用 ref)
ts
// watch方法接受ref作为监听对象,并在回调函数中返回解包后的值
const counter = ref(0)
watch(counter, count => {
  console.log(count) // same as counter.value
})

2.1 使用 reactive 解包嵌套的 ref

ts
import { ref, reactive } from 'vue'
const foo = ref('foo')
const bar = reactive({ foo, id: 10 })
console.log(bar.foo) // same as foo.value

2.2 unref 解包

ts
function unref<T>(r: Ref<T> | T): T {
  return isRef(r) ? r.value : r
}
const bar = ref('bar')
const foo = 'foo'
console.log(unref(bar)) // 'bar'
console.log(unref(foo)) // 'foo'

2.3 接受 ref 作为函数的参数

ts
function add(a, b) {
  return a + b
}
const a = add(1, 2) // a=3

function addRef(a: Ref<number>, b: Ref<number>) {
  return computed(() => a.value + b.value)
}
const a = addRef(ref(1), ref(2)) // a.value = 3

function addRef(a: Ref<number> | number, b: Ref<number> | number) {
  return computed(() => unref(a) + unref(b))
}
const a = addRef(ref(1), 2) // a.value = 3
  1. 类型工具 MaybeRef
ts
type MaybeRef = Ref<T> | T

3.1 构造特殊的 ref

ts
const title = useTitle()
title.value = 'new title'

const name = 'title'
const title = computed(() => name + ' new')
useTitle(title) // title new
name.value = 'hi' // hi new

function useTitle(newTitle: MaybeRef<string | null | undefined>) {
  const title = ref(newTitle || document.title)
  watch(
    title,
    t => {
      t !== null && (document.title = t)
    },
    { immediate: true }
  )
  return title
}

3.2 ref

ts
const foo = ref(1)
const bar = ref(foo)
foo === bar // true
function useRef<T>(r: Ref<T> | T): T {
  return isRef(r) ? r : ref(r)
}

3.3 由 ref 组成的对象

ts
// 可以直接使用ES6解构其中的ref使用
// 当想要自动解包时,可以使用reactive将其转换为对象
function useMouse() {
  return {
    x: ref(0),
    y: ref(0)
  }
}
const { x, y } = useMouse()
const mouse = reactive(useMouse())
mouse.x === x.value // true

3.4 将异步操作转换为”同步“

ts
// 异步
const data = await fetch('http://xxx.com').then(r => r.json)
// 组合式
const { data } = useFetch('http://xxx.com').json()
const userUrl = computed(() => data.value?.userUrl)
// 这里先建立数据”连接“,等待数据返回之后将数据填充,概念和react的SWR(stale-while-revalidate)
function useFetch<R>(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
  }
}
  1. 副作用自动删除
ts
// vue中的watch和computed API在组件销毁时自动解除其内部的依赖监听
function useEventListener(target: EventTarget, name: string, fn: any) {
  target.addEventListener(name, fn)
  onUnmounted(() => {
    target.removeEventListener(name, fn)
  })
}

4.1 effectScope 一个新的用于依赖收集的 API

ts
// 在函数scope内创建的effect、computed、watch、watchEffect等会被自动收集
const scope = effectScope(() => {
  const doubled = computed(() => counter.value * 2)
  watch(doubled, () => console.log(doubled.value))
  watchEffect(() => console.log('Count: ', doubled.value))
})
stop(scope) // 清除scope内的所有effect
  1. 类型安全的 Provide/Inject
ts
// 使用InjectionKey<T>类型工具来在不同的上下文中共享类型
interface UserInfo {
  id: string
  name: string
}
const injectionKeyUser: InjectionKey<UserInfo> = Symbol()
  1. 状态共享

6.1 不兼容 ssr 的

ts
// shared.ts
export const state = reactive({
  name: 'test'
})
// a.vue
import { state } from 'shared.ts'
state.name = 'test1'
// b.vue
import { state } from 'shared.ts'
console.log(state.name) // test1

6.2 兼容 SSR 的方式,使用 provide/inject 来共享应用层面的状态(vue-router 用的也是这种方式)

ts
const stateKey: InjectionKey<State> = Symbol()
export function createState() {
  const state = {}
  return {
    install(app: App) {
      app.provide(stateKey, state)
    }
  }
}
export function useState(): State {
  return inject(stateKey)!
}
// main.ts
app.use(createState)
  1. useVModel 一个让 props 和 emit 更加容易的工具
ts
function useVModel(props, name) {
  const emit = getCurrentInstance().emit
  return computed({
    get() {
      return props.name
    },
    set(v) {
      emit(`update:${name}`, v)
    }
  })
}
vue
// a.vue
<script lang="ts">
export default defineComponent({
  setup(props) {
    const data = useVModel(props, 'data')
    console.log(data.value) // props.data
    data.value = 'foo' // emit('update:data', 'foo')
    return { value }
  }
})
</script>
<template>
  <input v-model="value" />
</template>

与 vue2.0 的区别