Appearance
Vue 2 & 3
组合式 api 技巧
- 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'
}
})
- 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
- 类型工具 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
}
}
- 副作用自动删除
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
- 类型安全的 Provide/Inject
ts
// 使用InjectionKey<T>类型工具来在不同的上下文中共享类型
interface UserInfo {
id: string
name: string
}
const injectionKeyUser: InjectionKey<UserInfo> = Symbol()
- 状态共享
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)
- 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>