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