Vue3 composition api的技巧(1)

Vue3 composition api的技巧(1)

Ref和Reactive使用场景的问题

Ref
import { ref } from 'vue'
let foo = 0
let bar = ref(0)
foo = 1
bar = 1 // ts-error
复制代码

特点:

  • 显示调用,能显而易见的看出哪些值是被ref处理,来做响应式更新;同时,在上面的demo中,ts会做类型检查并报错,bar.value = 1这样才是正确姿势,是不是有点像react里面的.current?
  • 相比reactive局限性更少,下面讲reactive的时候会说明
  • 得通过.value来获取响应式更新的值
Reactive
import { reactive } from 'vue'
const foo = { prop: 0 }
const bar = reactive({ prop: 0})
foo.prop = 1
bar.prop = 1
复制代码

特点

  • 自动unRef(不需要.value)
  • 在类型上和一般对象没区别
  • 使用es6对象解构会使响应式丢失
  • 需要使用箭头函数包装才能使用watch?这一点AnthonyFu未展开说明,不太懂是指什么意思

综上,能使用ref的情况下,尽量使用ref。

Ref自动解包
  • watch直接接受Ref作为监听对象,并在回调函数中返回解包的值

    const counter = ref(0)
    watch(counter, count => {
        console.log(count) // same as count.value
    })
    复制代码
  • Ref在模版中自动解包

    <template>
      <button @click="counter ++">
          count is {{counter}}
      <button>
    <template>
    复制代码
  • 使用reactive解包嵌套的Ref

    import { reactive, ref } from 'vue'
    const foo = ref('bar')
    const data = reactive({ foo, id: 10 })
    data.foo // 'bar'
    复制代码
unref

unref是ref的反操作

  • 如果传入一个ref,返回其值(.value)
  • 反之,返回原值
接受Ref作为函数参数

纯函数:

function add(a: number, b: number) {
    return a + b
}

const a = 1
const b = 2

const c = add(a, b) // 3
复制代码

接收Ref作为参数,返回一个响应式的结果:

function add(a: Ref<number>, b: Ref: <number>) {
    return computed(() => a.value + b.value)
}
const a = ref(1)
const b = ref(2)
const c = add(a, b)
c.value // 3, 这样就实现了c.value是个响应式的值
复制代码
MayBeRef

type MayBeRef<T> = Ref<T> | T

MayBeRef可支持响应式的参数

export function useTimeAgo (
    time: Date | number | string | Ref<Date | number | string>
) {
    return computed(() => someFormating(unref(time)))
}
复制代码
import { computed, unref, ref } from 'vue'
export function useTimeAgo (
    time: MayBeRef<Date | number | string>
) {
    return computed(() => someFormating(unref(time)))
}
复制代码

MayBeRef可实现对响应式参数的兼容

useTitle
import { useTitle } from '@vueuse/core'
const title = useTitle() // useTitle内部会构建一个新ref
title.value = 'hello wolrd' // document.title = 'hello world'
复制代码
import { ref, computed } from 'vue'
import { useTitle } from '@vueuse/core'
const name = ref('hello')
const title = computed(() => {
    return `${name.value}, world`
})
useTitle(title) // 传入一个ref,document.title = 'hello world'
name.value = 'hi' // document.title = 'hi world'
复制代码

useTitle的实现

import { ref, watch } from 'vue'
import { MayBeRef } from '@vueuse/core'
export function useTitle (
	newTitle: MayBeRef<string, null, undefined>
) {
  const title = ref(newTitle || document.title) // 如果传了newTitle,那么不会重新ref
  watch(title, t => {
    if (t != null) {
      document.title = t
    }
  }, { immediate: true })
  return title
}
复制代码

由ref组成的对象

import { ref, reactive } from 'vue'
function useMouse () {
    return {
        x: ref(0),
        y: ref(0)
    }
}
const { x } = useMouse()
const mouse = reactive(useMouse())

mouse.x === x.value
复制代码

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

异步

const data = await fetch('https: //api.github.com/').then(r => r.json())
复制代码

组合式api

const { data } = useFetch('https: //api.github.com').json()
const user_url = computed(() => data.value.user_url)
复制代码
useFetch
export 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 = error)
	return {
            data,
            error
	}
} // 实际上vueuse里面的实现比这个复杂得多
复制代码
副作用清除

useEventListener

import { onUnmounted } from 'vue'
export function useEventListener(target: EventTarget, name: string, fn: any) {
	target.addEventListener(name, fn)
	onUnmounted(() => {
            target.removeEventListener(name, fn)
	})
}
复制代码
类型安全的Provide/Inject

使用vue提供的InjectionKey<T>类型工具来在不同的上下文中共享类型

// context.js
import { InjectionKey } from 'vue'
export interface useInfo {
    id: number
    name: string
}
export const injectKeyUser: InjectionKey<useInfo> = Symbol()
复制代码
// parent.vue
import { provide } from 'vue'
import { injectKeyUser } from './context.js'
export default {
	setup () {
            provide(injectKeyUser, {
                    name: 'xxx',
                    id: '7' // 类型错误,必须为number
            })
	}
} 
复制代码
// children.vue
import { inject } from 'vue'
import { InjectionKey } from './context.js'
export default {
    setup () {
        const user = inject(InjectionKey)
        if (user) {
          //xxxx
        }
    }
}
复制代码
状态共享

由于组合式api天然的灵活性,状态可以独立于组建被创建并使用,这就是为什么说vue3,可以完全不用vuex

// share.ts
import { reactive } from 'vue'
export const state = reactive({
    foo: 1,
    bar: 'hello'
})
复制代码
//A.vue
import { state } from './share.ts'
state.foo ++
复制代码
// B.vue
import { state } from './share.ts'
console.log(state.foo) // 2
复制代码
useVModel

一个让使用props和emit更加容易的工具

export function useVModel (props, name) {
    const emit = getCurrentInstance().emit
    return computed(() => {
        get () {
            return props[name]
        },
        set (v) {
            emit(`update: ${name}`, v)
        }
    })
}	
复制代码
export default defineComponent({
	setup(){
            const value = useVModel(props, 'value')
            return { value }
	}
})
复制代码
<template>
    <input v-model="value">
</template>
复制代码

以上主要来源于AnthonyFu在vueConf2021上的分享

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享