# 父子組件的溝通
# 父傳子
# 父組件
<script setup> | |
import { ref } from 'vue' | |
import Child from './components/Child.vue' | |
const testText = ref('') | |
</script> | |
<template> | |
<div> | |
<input type="text" v-model="testText" /> | |
<Child :data="testText" /> | |
</div> | |
</template> |
# 子組件
<template> | |
<div> | |
<p> 字數:</p> | |
</div> | |
</template> | |
<script setup> | |
import { computed } from 'vue'; | |
const props = defineProps({ | |
data: String | |
}) | |
const msgCount = computed(() => { | |
return props.data.length | |
}) | |
</script> |
# 說明
這邊的範例是一個 input 輸入字串,下列輸出字數的程式
傳入 Child 組件在 v-bind 的地方名稱不用跟父組件原來的名稱一樣
可是在子組件裡必須跟 v-bind 的名稱一樣不然會找不到
當我們要使用
data我們必須有一個變數props來去接defineProps回傳的東西才能做使用
可是我們即使沒有去接defineProps回傳的東西在template的地方仍然可以做使用 這是為什麼?
這是因為 Vue 3 的 <script setup> 內部會在編譯階段自動將通過 defineProps 定義的 props 暴露到模板作用域中。也就是說:
-
如果你這樣寫:
defineProps({
data: String
})
那麼 Vue 會自動把
data注入到模板中,讓你在模板裡可以直接使用data。 -
但是在
<script setup>裡面的 JavaScript 邏輯中,如果你要使用這個 prop,你就必須先將defineProps的返回值賦值給一個變數(例如props),然後透過props.data來存取。
這樣設計的目的就是為了讓模板的使用更簡潔,而在腳本部分能夠清楚地知道哪個變量是 props 。
# 子傳父
# 父組件
<script setup> | |
import { ref } from 'vue' | |
import Child from './components/Child.vue' | |
const testText = ref('') | |
const updateHandle = (newVal, oldVal) => { | |
console.log('newVal:', newVal) | |
console.log('oldVal:', oldVal) | |
} | |
</script> | |
<template> | |
<div> | |
<input type="text" v-model="testText" /> | |
<Child :data="testText" @update:data="updateHandle"/> | |
</div> | |
</template> |
# 子組件
<template> | |
<div> | |
<p> 字數:</p> | |
</div> | |
</template> | |
<script setup> | |
import { computed } from 'vue'; | |
const props = defineProps({ | |
data: String | |
}) | |
const msgCount = computed(() => { | |
return props.data.length | |
}) | |
const emits = defineEmits(['update:data']) | |
watch(msgCount, (newVal, oldVal) => { | |
emits('update:data', newVal, oldVal) | |
}, { immediate: false }) | |
</script> |
# 說明
defineEmits 裡面放的東西是定義要發出去給父組件的東西
watch 這邊的作用是監聽 msgCount,後面 function 有兩個參數一個是新資料,一個是舊資料
監聽到變更發出 update:data 事件夾帶 newVal 與 oldVal
@update:data="updateHandle" 這邊的意思是
- 當子組件發出名稱為 update:data 的事件時,
- 父組件會呼叫 updateHandle 方法,並將事件的參數傳入這個方法。
注意這邊的 update:data 要與子組件defineEmits(['update:data'])裡面的名稱要一致
{ immediate: false }
這邊如果改成 true 救世會在組件掛載時立即執行一次
# 官方文檔
https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
# nextTick
# 基本概念
Vue 3 採用異步更新機制。當你更改響應式狀態時,Vue 不會立即更新 DOM,而是將更新任務排入異步隊列,等待當前執行堆疊中的所有資料變更完成後,才一次性進行 DOM 更新。 nextTick 方法讓我們可以等待這個 DOM 更新完成後再執行特定代碼。
# 為什麼需要 nextTick
當你更改資料後,想要立即操作更新後的 DOM 元素時,就需要使用 nextTick 。因為 Vue 的響應式更新是異步的,如果不使用 nextTick ,可能會操作到尚未更新的 DOM。
# 使用方式
在 Vue 3 中, nextTick 可以通過以下方式使用:
import { nextTick } from 'vue' | |
// 作為全局 API 使用 | |
nextTick(() => { | |
// 這裡的代碼會在 DOM 更新後執行 | |
}) | |
// 也可以使用 async/await 語法 | |
async function example() { | |
await nextTick() | |
// 這裡的代碼會在 DOM 更新後執行 | |
} |
# 實際範例
# 範例 1:更新後獲取 DOM 元素尺寸(Composition API)
<template> | |
<div> | |
<div ref="textBox"><!--swig2--></div> | |
<button @click="updateMessage">更新訊息</button> | |
</div> | |
</template> | |
<script setup> | |
import { ref, nextTick } from 'vue' | |
const message = ref('原始訊息') | |
const textBox = ref(null) | |
const updateMessage = async () => { | |
message.value = '訊息已經更新,且內容變得非常長...' | |
// 如果不使用 nextTick,這裡獲取的可能是更新前的高度 | |
console.log('立即獲取高度:', textBox.value.offsetHeight) | |
// 使用 nextTick 等待 DOM 更新完成 | |
await nextTick() | |
// 這時獲取的才是更新後的實際高度 | |
console.log('nextTick後獲取高度:', textBox.value.offsetHeight) | |
} | |
</script> |
# 範例 2:基於更新後的 DOM 進行操作(Composition API)
<template> | |
<div> | |
<div v-if="showList"> | |
<ul ref="list"> | |
<li v-for="item in items" :key="item"><!--swig3--></li> | |
</ul> | |
</div> | |
<button @click="addItemAndScroll">添加項目並滾動到底部</button> | |
</div> | |
</template> | |
<script setup> | |
import { ref, nextTick } from 'vue' | |
const items = ref(['項目1', '項目2', '項目3']) | |
const list = ref(null) | |
const showList = ref(true) | |
const addItemAndScroll = async () => { | |
// 添加新項目 | |
items.value.push(`項目${items.value.length + 1}`) | |
// 等待 DOM 更新完成 | |
await nextTick() | |
// 現在可以安全地滾動到底部 | |
if (list.value) { | |
list.value.scrollTop = list.value.scrollHeight | |
} | |
} | |
</script> |
# 範例 3:條件渲染後聚焦元素(Composition API)
<template> | |
<div> | |
<button @click="focusInput">顯示輸入框</button> | |
<input v-if="showInput" ref="inputField" placeholder="請輸入..." /> | |
</div> | |
</template> | |
<script setup> | |
import { ref, nextTick } from 'vue' | |
const showInput = ref(false) | |
const inputField = ref(null) | |
const focusInput = async () => { | |
showInput.value = true | |
// 需要等待 DOM 更新後,input 元素才存在 | |
await nextTick() | |
// 現在可以安全地聚焦 input 元素 | |
inputField.value.focus() | |
} | |
</script> |
# 範例 4:基本用法 - 獲取 DOM 元素(Composition API)
<template> | |
<div> | |
<p ref="paragraph">這是一段文字</p> | |
<button @click="highlightText">高亮文字</button> | |
</div> | |
</template> | |
<script setup> | |
import { ref } from 'vue' | |
const paragraph = ref(null) | |
const highlightText = () => { | |
// 直接操作 DOM 元素 | |
if (paragraph.value) { | |
paragraph.value.style.color = 'red' | |
paragraph.value.style.fontWeight = 'bold' | |
} | |
} | |
</script> |
# 範例 5:獲取多個元素的引用(Composition API)
<template> | |
<div> | |
<div v-for="(item, index) in items" :key="index"> | |
<input :ref="el => { if (el) inputRefs[index] = el }"> | |
</div> | |
<button @click="focusLast">聚焦最後一個輸入框</button> | |
</div> | |
</template> | |
<script setup> | |
import { ref, reactive } from 'vue' | |
const items = ref([1, 2, 3, 4, 5]) | |
const inputRefs = reactive([]) | |
const focusLast = () => { | |
const lastIndex = items.value.length - 1 | |
if (inputRefs[lastIndex]) { | |
inputRefs[lastIndex].focus() | |
} | |
} | |
</script> |
# 範例 6:結合 nextTick 使用(Composition API)
<template> | |
<div> | |
<div v-if="show"> | |
<ul ref="list"> | |
<li v-for="item in items" :key="item"><!--swig4--></li> | |
</ul> | |
</div> | |
<button @click="toggleAndLog">切換顯示並記錄元素</button> | |
</div> | |
</template> | |
<script setup> | |
import { ref, nextTick } from 'vue' | |
const show = ref(false) | |
const list = ref(null) | |
const items = ref(['蘋果', '香蕉', '橙子']) | |
const toggleAndLog = async () => { | |
show.value = !show.value | |
// 如果是顯示元素,需要等待 DOM 更新後才能訪問 | |
if (show.value) { | |
await nextTick() | |
console.log('列表元素:', list.value) | |
console.log('列表項目數量:', list.value.children.length) | |
} | |
} | |
</script> |
# nextTick 的實現原理
Vue 3 的 nextTick 基於 JavaScript 的微任務 (microtask) 機制實現,它會利用 Promise.then、MutationObserver 或 setTimeout 等方法來確保回調函數在 DOM 更新後執行。在大多數現代瀏覽器中,Vue 優先使用 Promise 來實現這一機制。