# 父子組件的溝通

# 父傳子

# 父組件

<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 來實現這一機制。

更新於 閱讀次數 次