父子元件的資料傳遞

note-img

簡介

  • 在 Vue 當中,常常會遇到要從外部元件傳遞資料到內部元件
  • 例如一些客製化表單元件,如果我們想重複使用,必須封裝成 UI 元件,再從外部傳入資料
  • 以下整理幾種常用寫法提供我自己參考

方法 1:使用 props 和 emit

一般寫法

  • 在父元件中,傳遞 propsMyInput
  • 在子元件中,接收 props,顯示在畫面上
  • 建立emit,當 Input 內的值變動時,把更新的資料傳出到父元件
// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const msg = ref('')
const updateMsg = (newMsg: string) =. {
    msg.value = newMsg
}
</script>
<template>
  <MyInput :input-value="msg" @update-value="updateMsg" />
</template>
// MyInput.vue
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'

defineProps<{
  inputValue: String
}>()

const emit = defineEmits(['update-value'])

const onInput = (event: Event) => {
  const value = (event.target as HTMLInputElement).value
  emit('update-value', value)
}
</script>

<template>
  <input type="text" :value="inputValue" @input="onInput" />
</template>

Vue3 寫法

  • 使用modelValue, update:modelValue
// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const msg = ref('')
</script>

<template>
  <MyInput :modelValue="msg" @update:modelValue="msg = $event" />
</template>
  • 自訂props的名稱,替代modelValue
<MyInput :modelValue:customMsg="msg" @update:modelValue="msg = $event" />
// MyInput.vue
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'

defineProps<{
  modelValue: String
}>()

const emit = defineEmits(['update:modelValue'])
</script>
<template>
  <input
    type="text"
    :value="modelValue"
    @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
  />
</template>

使用 v-model

  • 可以在父元件,直接使用 v-model 簡化程式碼
  • MyInput則維持不變
// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const msg = ref('')
</script>

<template>
  <MyInput v-model="msg" />
</template>
// MyInput.vue
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'

defineProps<{
  modelValue: String
}>()

const emit = defineEmits(['update:modelValue'])
</script>
<template>
  <input
    type="text"
    :value="modelValue"
    @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
  />
</template>

方法 2: Writable computed

  • MyInput內新增 computed 變數 myMsg
  • 設定這個變數的 gettersetter
  • 和 Input 標籤使用 v-model 雙向綁定
  • 當 Input 內的值變化時,myMsg 的值跟著改變,觸發setter
  • setter觸發時,執行 emit,將更新的值傳出到父元件
// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const msg = ref('')
</script>

<template>
  <MyInput v-model="msg" />
</template>
// MyInput.vue
<script setup lang="ts">
import { defineProps, defineEmits, computed } from 'vue'

const props = defineProps<{
  modelValue: String
}>()

const emit = defineEmits(['update:modelValue'])

const myMsg = computed({
  get() {
    return props.modelValue
  },
  set(newVal) {
    emit('update:modelValue', newVal)
  }
})
</script>
<template>
  <input type="text" v-model="myMsg" />
</template>

方法 3:使用 VueUse 的 useVModel

  • 直接引入useVModel
  • 不用自己寫computedgettersetter
// MyInput.vue
<script lang="ts" setup>
import { useVModel } from '@vueuse/core'

const props = defineProps<{
  modelValue: string
}>()
const emit = defineEmits(['update:modelValue'])

const myMsg = useVModel(props, 'modelValue', emit)
</script>
<template>
  <input type="text" v-model="myMsg" />
</template>

方法 4:使用 defineModel

  • 在 Vue 3.4 中,新加入的 API(Stable)
  • 簡化了以上這些寫法

範例:傳入一個字串

// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const msg = ref('Hello world')
</script>

<template>
  <MyInput v-model="msg" />
</template>
// MyInput.vue
<script setup lang="ts">
const model = defineModel()
</script>

<template>
  <input v-model="model" />
</template>

範例:傳入一個物件

// 父元件
<script setup lang="ts">
import MyInput from '@/components/MyInput.vue'

const searchData = ref({
  keyword: 'Hello',
  page: 1
})
</script>

<template>
  <MyInput v-model="searchData" />
</template>
// MyInput.vue
<script setup lang="ts">
interface SearchData {
  keyword: string
  page: number
}
const model = defineModel<SearchData>()
</script>

<template>
  <input v-model="model.keyword" />
  {{ model.page }}
</template>

參考資料

# Vue # V-model # Computed