響應(yīng)式API
- ref
- unref
- toRef
- toRefs
- isRef
- customRef
- shallowRef
- triggerRef
- computed
- watch
- watchEffect
1赫模、解構(gòu)帶來的響應(yīng)式陷阱
我們習(xí)慣了ES6的對象解構(gòu)風(fēng)格聘萨,但這在composition- api
里可能會有陷阱绎速。因為結(jié)構(gòu)可能會讓你的響應(yīng)式對象失去預(yù)期中的響應(yīng)特性讯柔。
<template>
<div id="app">
{{count}}
<button @click="addCount"></button>
</div>
</template>
<script>
import { reactive } from '@vue/runtime-dom'
export default {
setup() {
const data = reactive({
count: 0
})
function addCount() {
data.count += 1
}
return {
data,
count: data.count,
addCount
}
}
};
</script>
比如這里师骗,button的click時候屁擅,不會得到預(yù)期的count
的增加仲义。因為setup執(zhí)行返回的count
并不是響應(yīng)式的插佛。
也就是說杠巡,雖然你的click事件確實的改變了data.count
的值,但是這個值并沒有響應(yīng)式的去改變其他引用這個值的地方雇寇。怎么去驗證我們這個解釋呢氢拥?
我們可以開著chrome的vue插件,定位到你的組件锨侯。然后你可以點擊一下button嫩海,可以看到count
并沒有變化,data
呢囚痴?看起來好像也沒有變化叁怪?這不是不符合邏輯嗎?甚至我們在click的回調(diào)函數(shù)里打印一下發(fā)現(xiàn)是有執(zhí)行data.count+1 這個操作的深滚。
事實是奕谭,data.count
確實是執(zhí)行了的,但是因為不是reactive
的痴荐,所以插件里沒有及時更新這個新數(shù)據(jù)血柳。如果你把插件先切到別的組件上去,再切回來生兆。你就會發(fā)現(xiàn)难捌,data.count是符合預(yù)期的!
click操作前的插件看到的數(shù)據(jù):
我做了2次click后鸦难,現(xiàn)在插件里把光標(biāo)切到別的組件上根吁,再切回來。就可以看到data的變化:
只是引用了data.count
的地方?jīng)]有被更新合蔽,這就說明引用data.count
的地方是非reactive
的婴栽。
我們要要做的,就是改造一下引用data.count
的地方辈末。我們在setup
里的返回愚争,可以用computed
和toRefs
來改造一下返回值。
再看一下toRefs改造后的demo:
<template>
<div id="app">
{{count}}
<button @click="addCount"></button>
</div>
</template>
<script>
import { reactive } from '@vue/runtime-dom'
export default {
setup() {
const data = reactive({
count: 0
})
function addCount() {
data.count += 1
}
return {
...toRefs(data),
addCount
}
}
};
</script>
第二個問題來了:toRefs
一定是安全應(yīng)對解構(gòu)的方案么挤聘?
不是的轰枝,因為toRefs
的結(jié)構(gòu)是淺解構(gòu)的,對于我們demo里的這種簡單的對象是work的组去。但是如果是一個嵌套很深的復(fù)雜Object鞍陨,還是會有解構(gòu)后響應(yīng)式斷裂的問題。如果數(shù)據(jù)的層級比較復(fù)雜,建議使用computed
诚撵。
2缭裆、watch 和 watchEffect
watchEffect
很像React里的useEffect
,是一個副作用函數(shù)寿烟。用法也基本一致澈驼。
watch
的話,接收2個參數(shù)筛武。第一個參數(shù)是watch
的target缝其,看ts結(jié)構(gòu),必須是一個ref
對象或者computedRef
對象徘六;第二個參數(shù)是watch的回調(diào)函數(shù)内边。
踩坑:
目前@vue/composition-api里的watch有bug。在watch一個數(shù)組的時候待锈,觸發(fā)不了回調(diào)函數(shù),從v1.1版本開始就有這個問題漠其。已經(jīng)有人提了issue,暫未解決(no longer works for multiple sources after v1.1)竿音。
3和屎、composition-api模仿React的useContext
React的hooks出來之后,有個很好用的東西就是Context谍失,很像一個微型Redux眶俩,可以很好的跨組件傳值莹汤,尤其是在組件粒度很細(xì)的時候快鱼,我們的組件間通信頻率也會升高。
之前vue2的時代纲岭,其實一直有provide
和inject
可以用抹竹。但是provide
和inject
的對象一般是非響應(yīng)式的。官網(wǎng)是這么記載的:
vue2的時候止潮,我們一般不太注重如何把一個數(shù)據(jù)變成響應(yīng)式的(也不是沒有辦法窃判,比如Vue.observable(obj)
可以把一個對象變成響應(yīng)式的。如果我們把這個對象provide出去喇闸,那么傳遞的數(shù)據(jù)也就一直是響應(yīng)式的了)袄琳。
vue3(或者vue2 + @vue/composition-api
)后,我們更多的關(guān)注到了數(shù)據(jù)的reactive
特性燃乍。比如用ref
或者reactive
關(guān)鍵字來構(gòu)造一個響應(yīng)式的對象唆樊。我們?nèi)绻儆?code>provide直接傳遞一個reactive
的對象,豈不是可以模擬出類似React的useContext
這樣的結(jié)構(gòu)刻蟹?
外層Context層構(gòu)造:
import { createApp, defineComponent, provider, inject, reactive, readonly, toRefs } from 'vue';
// Provider 包裝組件
const MyConfigProvider = defineComponent({
name: 'MyConfigProvider',
props: ['prefixCls', 'title'],
setup (props, { slots }: SetupContext) {
const { prefixCls, title } = toRefs(props);
const context = reactive({
prefixCls,
title
});
provide('myConfig', readonly(context));
return () => slots.default?.();
}
});
// 測試用子組件
const ChildComp = defineComponent({
name: 'ChildComp',
setup () {
const myConfig = inject('myConfig', {});
return () => (
<>
<p>{myConfig.prefixCls}</p>
<p>{myConfig.title}</p>
</>
)
}
});
// 調(diào)用Context層和子組件
const App = defineComponent({
name: 'App',
setup () {
const state = reactive({
prefixCls: 'myui',
title: 'MyApp',
i18n: (key: string) => key
});
return () => (
<div id="#app">
<MyConfigProvider {...state}>
<ChildComp />
</MyConfigProvider>
</div>
)
}
});
本身vue3(or vue2
+ @vue/composition-api
)也是支持hooks的逗旁。
這樣我們就可以按照React hooks的開發(fā)習(xí)慣去給vue抽hooks了。