一、通過 ref 獲取子組件實例中的DOM結構數據及方法
setup 怎么獲取子組件的 ref ?在 Vue3 中,如果要在父組件拿到子組件(子組件的DOM結構、數據、方法),可以通過 ref。即在父組件中定義響應式數據 ref(null) ,並綁定給子組件,在需要的時候,通過定義的響應式變量即可獲取。獲取后,即獲取了子組件的一切,可以看到子組件的DOM結構,也可以看到子組件中對外暴露的的數據和方法,並且可以直接調用。
<ExpertFormVue ref="ExpertRef"></ExpertFormVue>
const ExpertRef = ref() // 通過 ref 綁定子組件
function getSonComponent () { // 通過 ref 獲取子組件 // 獲取子組件的數據
console.log(ExpertRef.value) console.log(ExpertRef.value.msg) }
這里面涉及 2 個問題:
1、如何通過 ref 獲取子組件實例
ref 用法1:響應式數據。ref 用法2:模板ref,獲取dom元素節點(重點)
1、const a = ref(null); 2、在template中定義ref <div ref='a'>*********</div>
3、setup中獲取對應節點【在onMounted里】; 4、將a return出去;
但是這時候你可能會發現,你無法獲取這個節點,這是什么原因呢?其實還是生命周期的問題,結合官方文檔可知:原來的created沒有了,setup充當了原來的created。
所以在setup的時候,dom元素還沒有被創建,一切都處於混沌狀態,只有setup完畢了HTML才能完整構建,才能真正訪問到value值,所以自然無法獲取到dom節點,要想解決這個問題,就要配合鈎子函數 onMounted ,在dom掛載完畢后再進行獲取。
2、為什么獲取子組件實例中的數據都是空? —— defineExpose API 的使用
問題:開始的時候,我像上面寫的,怎么都拿不到子組件實例的數據和方法等。
原因:其實寫法是對的,只不過拿到的引用是空的。子組件需要明確使用 expose
方法暴露出接口之后,才能在父組件獲取到接口引用。未暴露接口的情況下,引用的始終是一個空對象。
// 子組件導出方法,才能在父組件拿到子組件的實例里使用
defineExpose({ initExp })
在 vue3.x 的 setup 語法糖中定義的變量默認不會暴露出去,這時使用 definExpose({ })
來暴露組件內部屬性給父組件使用,在父組件中直接修改子組件的屬性,子組件也會相應更新。
// 子組件
<script setup> let aaa = ref("aaa") defineExpose({ aaa }); </script>
// 父組件
<Chlid ref="child"></Chlid>
<script setup> let child = ref(null) child.value.aaa //獲取子組件的aaa
</script>
二、官網:模板引用
盡管存在 prop 和事件,但有時你可能仍然需要直接訪問 JavaScript 中的子組件。為此,可以使用 ref
attribute 為子組件或 HTML 元素指定引用 ID。例如:
<input ref="input" />
例如,你希望在組件掛載時,以編程的方式 focus 到這個 input 上,這可能有用(可以看到直接使用 this.$refs.input 就獲取到了 input Dom)
const app = Vue.createApp({}) app.component('base-input', { template: ` <input ref="input" /> `, methods: { focusInput() { this.$refs.input.focus() } }, mounted() { this.focusInput() } })
此外,還可以向組件本身添加另一個 ref
,並使用它從父組件觸發 focusInput
事件:
<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput.focusInput()
需要注意的是:$refs
只會在組件渲染完成之后生效(也就是生命周期 onMounted 之后)。這僅作為一個用於直接操作子元素的“逃生艙”——你應該避免在模板或計算屬性中訪問 $refs
。
三、官網:在組合式 API 中使用 template refs
在使用組合式 API 時,響應式引用和模板引用的概念是統一的。為了獲得對模板內元素或組件實例的引用,我們可以像往常一樣聲明 ref 並從 setup() 返回:
<template>
<div ref="root">This is a root element</div>
</template>
<script> import { ref, onMounted } from 'vue' export default { setup() { const root = ref(null) onMounted(() => { // DOM 元素將在初始渲染后分配給 ref
console.log(root.value) // <div>This is a root element</div>
}) return { root } } } </script>
這里我們在渲染上下文中暴露 root
,並通過 ref="root"
,將其綁定到 div 作為其 ref。在虛擬 DOM 補丁算法中,如果 VNode 的 ref
鍵對應於渲染上下文中的 ref,則 VNode 的相應元素或組件實例將被分配給該 ref 的值。這是在虛擬 DOM 掛載/打補丁過程中執行的,因此模板引用只會在初始渲染之后獲得賦值。
作為模板使用的 ref 的行為與任何其他 ref 一樣:它們是響應式的,可以傳遞到 (或從中返回) 復合函數中
1、v-for 中的用法(for循環中如何獲取及重置refs)
組合式 API 模板引用在 v-for
內部使用時沒有特殊處理。相反,請使用函數引用執行自定義處理:
<template>
<div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }"> {{ item }} </div>
</template>
<script> import { ref, reactive, onBeforeUpdate } from 'vue' export default { setup() { const list = reactive([1, 2, 3]) const divs = ref([]) // 確保在每次更新之前重置ref
onBeforeUpdate(() => { divs.value = [] }) return { list, divs } } } </script>
2、偵聽模板引用:偵聽模板引用的變更可以替代前面例子中演示使用的生命周期鈎子。
但與生命周期鈎子的一個關鍵區別是,watch()
和 watchEffect()
在 DOM 掛載或更新之前運行副作用,所以當偵聽器運行時,模板引用還未被更新。
const root = ref(null) watchEffect(() => { // 這個副作用在 DOM 更新之前運行,因此,模板引用還沒有持有對元素的引用。
console.log(root.value) // => null
})
因此,使用模板引用的偵聽器應該用 flush: 'post'
選項來定義,這將在 DOM 更新后運行副作用,確保模板引用與 DOM 保持同步,並引用正確的元素。
watchEffect(() => { console.log(root.value) // => <div>This is a root element</div>
}, { flush: 'post' })