- 沒有
this
了吠架,我要怎么獲取組件實例锋喜? - 沒有
this
了凡涩,怎么派發(fā)自定義事件棒搜? - 我該如何在
reactive
和ref
之間做選擇? - setup函數(shù)太長了怎么辦活箕?
- 我的屬性怎么就不響應(yīng)了
-
watchEffect
和watch
有啥不同力麸? - 生命周期鉤子能不能寫多個?順序是怎樣的育韩?
我要怎么獲取組件實例克蚂?
我們都知道composition api是可以和options api一起使用、友好相處的筋讨,比如下面的示例:
const { createApp } = Vue
createApp({
data() {
return {
foo: 'foo'
}
},
setup() {
// 沒有this埃叭,我該如何獲取data中的foo和methods中的bar哪?
return { }
},
methods: {
bar() {
console.log('我是bar方法');
}
},
}).mount('#app')
但是setup里面this指向window悉罕,composition的文檔中也沒有提到怎么獲取組件實例呀游盲,這著實難住了不少小伙伴,方法自然是有的:咱們可以通過getCurrentInstance()這個接口獲取組件實例:
setup() {
// getCurrentInstance()可以獲取組件實例
const instance = getCurrentInstance()
console.log(instance);
onMounted(()=>{
// 組件實例的上下文才是我們熟悉的this
console.log(instance.ctx.foo); // 'foo'
console.log(instance.ctx.bar()); // '我是bar方法'
})
return {}
},
但是很快我們又蒙圈了蛮粮,這個組件實例和我們以前熟悉的this
不一樣益缎,直接訪問this.foo
還是找不到數(shù)據(jù)。
vue3中組件實例結(jié)構(gòu)如下然想,各個選項中的this
實際上是ctx
或proxy
當(dāng)然坑還是有的莺奔,你仔細(xì)觀察這個ctx,發(fā)現(xiàn)它不是一個Proxy對象,也就是這位兄臺只有值卻沒有響應(yīng)性令哟,所以如果要利用響應(yīng)特性恼琼,還得用proxy這個屬性返回上下文對象,如果只是想要數(shù)據(jù)屏富,上圖中不是有個data也是Proxy類型的嘛晴竞。
setup() {
const { proxy, data } = getCurrentInstance()
// 想要利用響應(yīng)能力,就要使用proxy這個上下文
watchEffect(() => {
console.log(proxy.foo) // foo變化會有輸出
console.log(data.foo) // foo變化會有輸出
})
},
最后大家還要注意狠半,setup()執(zhí)行的時間點是很早的噩死,甚至早于created,因此foo和bar的訪問如果沒有特意放到onMounted里面還真沒有神年。
setup() {
const instance = getCurrentInstance()
console.log(instance.ctx.foo); // undefined
console.log(instance.ctx.bar()); // undefined
},
怎么派發(fā)自定義事件已维?
突然之間沒有this了之后,好像突然生活不能自理了已日,再也不能用this.$emit()派發(fā)事件了垛耳。
其實通過組件實例是可以派發(fā)事件的,比如:
setup() {
getCurrentInstance().emit('ooxx')
}
但是這樣比較麻煩飘千,所以我們要用到setup函數(shù)的第二個參數(shù):
setup(props, ctx) {
ctx.emit('ooxx')
}
當(dāng)然堂鲜,還能把emit直接結(jié)構(gòu)出來使用更方便:
setup(props, { emit }) {
emit('ooxx')
}
我該如何在reactive和ref之間做選擇?
composition-api引入了獨立的數(shù)據(jù)響應(yīng)式方法reactive护奈,它可以將傳入的對象做響應(yīng)式處理:
const state = reactive({
foo: 'foo',
})
watchEffect(() => {
console.log(state.foo)
})
state.foo = 'foooooo' // 輸出 'foooooo'
這個方式類似于我們設(shè)置的data選項缔莲,能夠解決我們大部分需求。但是也有以下問題:
- 當(dāng)我們直接導(dǎo)出這個state時逆济,我們在模板中就要帶上state這個前綴
setup() {
const state = reactive({})
return { state }
}
<div>{{ state.foo }}</div>
為了解決這個問題又要引入toRefs
setup() {
const state = reactive({})
return { ...toRefs(state) }
}
<div>{{ foo }}</div>
小伙伴們又懵了,toRefs是個啥磺箕?為啥不直接展開奖慌?
- 單個值時用reactive()顯得比較多余
于是就有了Ref
的概念,通過包裝單值為Ref
對象松靡,這樣就可以對其做響應(yīng)式代理
setup() {
const foo = ref('foo')
return { foo }
}
模板中使用還可以省掉前綴简僧,toRefs就是利用這一點將reactive()返回代理對象的每個key對應(yīng)的值都轉(zhuǎn)換為Ref
<div>{{ foo }}</div>
但是Ref對象也有副作用:
- 在JS中修改這個值要額外加上value
setup() {
const foo = ref('foo')
setTimeout(() => {
// 額外的value讓人惱火
foo.value++
}, 1000)
return { foo }
}
- 額外增加的心智負(fù)擔(dān):一個值到底是不是Ref,我要不要加.value雕欺?
setup(props) {
const foo = props.foo
// foo是Proxy還是Ref岛马?
// 編寫`watch`方法的時候?qū)懛ㄍ耆煌? // Ref可以被直接watch
watch(foo, () => {})
// Proxy則需要寫成函數(shù)形式
watch(() => foo.bar, () => {})
}
對比之后發(fā)現(xiàn)都有一些問題,我們來討論一下兩者選擇問題:
- 如果是單值屠列,建議ref啦逆,哪怕是個單值的對象也可以
const counterRef = ref(1)
const usersRef = ref(['tom', 'jerry'])
一個業(yè)務(wù)關(guān)注點有多個值,建議reactive
const mouse = reactive({
x: 0,
y: 0
})
- 降低Ref心智負(fù)擔(dān)的方法:利用unref笛洛、isRef夏志、isProxy等工具方法,利用一些命名約定苛让。
setup(props) {
const foo = unref(props.foo) // foo是我們要的值
// 等效于
const foo = isRef(props.foo) ? props.foo.value : props.foo
}
setup函數(shù)太長了怎么辦沟蔑?
雖然很好的將關(guān)注點集中起來湿诊,就像下面這樣:
但是難免還是太長了(怎么太長還成了困擾?)瘦材,此時就可以開始函數(shù)拆分
setup(){
let { val, todos, addTodo } = useTodo()
let {count,double,add} = useCounter()
let {x, double:doubleX} = useMouse()
return {
val, todos, addTodo,
count,double,add,
x,doubleX
}
}
return的上下文太長了厅须,我們可以使用vue3的setup script功能,把setup這個配置也優(yōu)化掉食棕,一個功能export一次
<script setup>
import useCounter from './counter'
import useMouse from './mouse'
import useTodo from './todos'
let { val, todos, addTodo } = useTodo()
export {val, todos, addTodo}
let {count,double,add} = useCounter()
export {count,double,add}
let {x, double:doubleX} = useMouse()
export {x,doubleX}
</script>
我的屬性怎么就不響應(yīng)了
下面的代碼是小伙伴們可能會寫出來的:
setup({ foo, bar }) {
watchEffect(() => {
console.log(foo, bar) // foo朗和,bar發(fā)生變化,也不會有輸出
})
}
props是一個Proxy對象宣蠕,直接結(jié)構(gòu)就失去了響應(yīng)能力例隆,所以對待props要溫柔,不能動不動就劈開了
setup(props) {
watchEffect(() => {
console.log(props.foo, props.bar) // foo抢蚀,bar發(fā)生變化镀层,會有輸出
})
}
真想劈開也行,看你喜歡什么姿勢了
setup(props) {
const { foo, bar } = toRefs(props)
watchEffect(() => {
console.log(foo, bar) // foo皿曲,bar發(fā)生變化唱逢,會有輸出
})
}
watchEffect和watch有啥不同?
這倆方法很相似屋休,都是觀察響應(yīng)式數(shù)據(jù)坞古,變化執(zhí)行副作用函數(shù),但有如下不同:
watch需要明確指定監(jiān)視目標(biāo),
watch(() => state.counter, (val, prevVal) => {})
watchEffect不需要
watchEffect(() => {
console.log(state.counter)
})
watch可以獲取變化前后的值
watch是懶執(zhí)行的劫樟,它等效于vue2中的this.$watch()痪枫,watchEffect為了收集依賴,要立即執(zhí)行一次
現(xiàn)在知道怎么選擇它們了吧叠艳?
生命周期鉤子能不能寫多個奶陈?
當(dāng)然可以寫多個,最后它們會按注冊順序依次執(zhí)行:
setup() {
onMounted(() => {})
onMounted(() => {})
onMounted(() => {})
}
甚至還能拆分出多個相同生命周期鉤子到獨立函數(shù)中呢附较,相當(dāng)帥氣
function fun1() {
// 這里可以用onMounted執(zhí)行代碼
onMounted(() => {})
}
function fun2() {
// 這里還可以用onMounted執(zhí)行代碼
onMounted(() => {})
}
setup() {
fun1()
fun2()
onMounted(() => {})
}
抄襲自掘金羊村楊老師