/
CATEGORY
Vue
/
Vue3 如何用 defineModel 實作 props / emit 的父子元件傳值,讓傳值變得更方便簡單

Vue3 如何用 defineModel 實作 props / emit 的父子元件傳值,讓傳值變得更方便簡單

MUKI AI Summary

Vue.js 開發常用 propsemit 傳遞資料,但這方法複雜難維護。Vue3 引入 v-model 簡化操作,讓資料變更自動同步,提升傳遞效率。Vue3.4 推出 defineModel(),進一步簡化 props / emit,是官方推薦寫法。

defineModel() 可傳多個資料,並支持型別與預設值設定。使用 v-model 修飾符時,需解構 defineModel() 回傳值。若出現 defineModel is not defined 錯誤,更新 vue@vitejs/plugin-vue 版本,並修改 vite.config.js 設定。...

前言

使用 Vue.js 開發時,我們很常用到父子元件相互傳遞資料。以往我們會使用 propsemit 來實作這個傳遞方式,但老實說,利用他們將資料傳來傳去,會讓程式變得更加複雜也不好維護。

而我們在 Vue3 可以使用 v-model 這個大家熟悉的「雙向綁定」語法糖來簡化 props / emit 操作,可以將資料的變更自動同步到父子元件之間,讓傳遞變得更簡單。

到了近期 Vue 推出 3.4 版本,還有一個更厲害的 defineModel() 也正式發佈了,他能再大幅度簡化 props / emit,使用也非常方便,是官方目前最推薦的寫法。

如何使用 v-model 實現雙向綁定並傳值

在 vue3.4 版本推出之前,如果要簡化 props / emit 操作,我們可以使用 v-model 語法糖達成目的

▼ 父元件:在 <child /> 元件中用 v-model 綁定要傳遞的值

<template>
  <child v-model="value" />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  const value = ref('hello world')
</script>

▼ 子元件:不需要額外在 props 設定,直接使用 modelValue 接收與監聽

<template>
  接收的值:{{ props.modelValue }}
</template>

<script setup lang="ts">
  const props = defineProps(["modelValue"])
  const emits = defineEmits(["update:modelValue"])
  
  const handleChange = (newVal: string) => {
    emits('update:modelValue', newVal)
  }
</script>

v-model 會直接給元件定義 modelValue 以及 update:modelValue 事件,如果要接收資料,使用 modelValue;要監聽與傳遞,使用 update:modelValue

使用 v-model 可能會產生的問題

如果今天我們改用 input 接收這個值,你可能會這樣寫:

<template>
  接收的值:{{ props.modelValue }}
  <input v-model="props.modelValue" />
</template>

但這樣會產生的問題是:從父組件接收的 props.modelValue 是不能被修改的,所以不能使用 v-model 雙向綁定並修改數據。

▼ 你需要把它拆成單向流,改用 :value 綁定 props.modelValue,再用 update:modelValue 更新數值到父元件

<template>
  <input :value="props.modelValue" />
</template>

▼ 你可能會在 Vue 的一些 Library 看到類似的寫法,例如 elemen-plus。我用他的元件 Dialog 為例,如果今天你把 Dialog 放到子元件,然後從父元件傳遞他的開關狀態(是否顯示 Dialog),你會發現用 v-model 不會有作用,而是要改用 model-value 才能正常作用,就是這個原因。

<template>
  <el-dialog :model-value="visible">
    ...
  </el-dialog>
</template>

▼ 透過 element-plus 網站的 Dialog API 文件,可以看到他在綁定「顯示 Dialog」的功能上,有兩種屬性可選

使用 defineModel() 實現雙向綁定並傳值

defineModel()v-model 的進化版,在 Vue3.4+ 以後都可直接使用 defineModel(),如果你開發的專案可以直升 3.4+,就請直接使用它吧!

在 Vue 的設定中,只要是 define 開頭的,大都是 Vue 的巨集 (macro),不需要匯入就能直接使用,我們常用的 defineProps()defineEmits(),以及這次要介紹的 defineModel(),都是屬於 Vue 的巨集。

defineModel() 基本的用法

直接參考官方網站的語法如下:

▼ 父元件跟之前的寫法沒啥變化,可以自己定義要傳到子元件的值與型別

<template>
  <child v-model="counterValue" />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  const counterValue = ref(555)
</script>

▼ 子元件可以自由定義接收的變數,不再需要制式化的使用 v-model 定義好的 modelValue

<template>
  <div>從父元件接收的值為 {{ modelValue }}</div>
</template>

<script setup lang="ts">
  const modelValue = defineModel()
  
  const update = () => {
    // 使用 .value 就能直接與父組件 v-model 綁定的值(counterValue)同步
    modelValue.value++
  }
</script>

▼ 所以剛剛提到的 input 這種原本支援 v-model 的問題,現在也都解決了

<template>
  <!-- 可以直接使用 v-model 囉 -->
  <input v-model="modelValue" />
</template>

<script setup lang="ts">
  const modelValue = defineModel()
</script>

可以發現改用 defineModel() 的寫法真是好處多多:

  1. 首先,我們不需要再處理 v-model 的問題,現在子元件的值有調整,也會自動更新到父元件
  2. 再來,不知道大家有沒有發現,我們連 defineProps() 以及 defineEmits() 也不用寫了,完全意義上的取代了 props / emit 操作

如何使用 defineModel() 綁定多個資料

我們傳值一定不只會傳一個資料,所以 defineModel() 當然可以傳多個資料囉!

▼ 父元件的寫法,是使用 v-mode: 後面串接多個參數

<template>
  <child 
    v-model:couterValue="counterValue"
    v-model:stringValue="stringValue"
  />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  const counterValue = ref(555)
  const stringValue = ref('hello world')
</script>

▼ 子元件

<template>
  <p>從父元件接收的值:</p>
  <p>{{ childCounterValue }}</p>
  <p>{{ childStringValue }}</p>
</template>

<script setup lang="ts">
  const childCounterValue = defineModel('couterValue')
  const childStringValue = defineModel('stringValue')
</script>

如何定義 defineModel() 的型別與預設值

如同我們在接收 props 資料時,會定義型別與預設值,defineModel() 也可以做到同樣的功能。

▼ 父元件只傳遞一個資料,也就是只用 v-model="modelValue" 這樣寫法的情況下

<!--
父元件只傳遞一個資料
<child v-model="modelValue" />
-->

<template>
  <p>從父元件接收的值:</p>
  <p>{{ modelValue }}</p>
</template>

<script setup lang="ts">
  // 定義型別與預設值
  const modelValue = defineModel({ type: String, default: 'MUKI space' })
</script>

▼ 父元件傳遞多組資料

<!--
父元件傳遞多組資料
<child 
  v-model:couterValue="counterValue"
  v-model:stringValue="stringValue"
/>
-->

<template>
  <p>從父元件接收的值:</p>
  <p>{{ childCounterValue }}</p>
  <p>{{ childStringValue }}</p>
</template>

<script setup lang="ts">
  // 定義型別與預設值
  const childCounterValue = defineModel('couterValue', {
    type: Number,
    default: 100
  })
  const childStringValue = defineModel('stringValue', {
    type: String,
    default: 'MUKI space'
  })
</script>

defineModel() 使用 v-model 的修飾符

v-model 有三個內建的修飾符:.trim.number 以及 .lazy,關於這些修飾符的功能可以參考官方的 Modifiers 文章介紹

如果今天要用 defineModel() 去取得 v-model 上的修飾符,我們需要解構 defineModel() 的回傳值

▼ 在 v-model 使用了 .trim 修飾符,他會在輸入文字時,自動除去前後多餘的空格

<template>
  <child v-model.trim="title" />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  const title = ref('MUKI space')
</script>

▼ 在子元件解構 defineModel() 的回傳值

<script setup lang="ts">
  // 從 defineModel 的回傳值中解構 modelValue 和 modelModifiers
  const [modelValue, modelModifiers] = defineModel({
  	set(value) {
      // 如果有使用 .trim 修飾符,就回傳 value.trim()
      if (modelModifiers.trim) {
        return value.trim()
      }
    }
    return value
  })
</script>

相關的使用方法與介紹,也可以參考官網的文件說明

4.25 更新:部署後出現 defineModel is not defined 的錯誤

如果部署之後有使用 defineModel 的元件一片空白,而且 console 面板出現 defineModel is not defined 的錯誤,可以嘗試以下修改:

更新套件版本

我自己有使用,而且確定會影響的套件有兩個:vue 以及 @vitejs/plugin-vue

  1. 我的 vue 的版本:v3.3.8
  2. 我的 @vitejs/plugin-vue 版本:v5.0.4
    我更新之前的版本是 v3.2.0,但這個版本會報錯。
    但要更新至幾版才安全?我沒有詳細研究,因為我是直接更新到最近的穩定版。

修改 vite.config.js 設定

▼ 加入以下語法

plugins: [
  vue({
    script: {
      defineModel: true
    }
  })
]

我自己是用這兩個方法,再重新部署就正常了。如果你有碰到以上問題但還是沒辦法解決,可以再留言互相交流討論。

結語

文章跟大家分享了如何使用 v-model 實作父子元件之間的資料傳遞,以及在 Vue 3.4 版後,正式發布的 defineModel() 更進一步簡化了這個過程。

如果文章內有任何錯誤需要指正,或有任何問題,都可以在文章底下留言聯繫我唷,感謝。

歡迎給我點鼓勵,讓我知道你來過 :)

44
17
1
MUKI says:

如果文章有幫助到你,歡迎分享給更多人知道。文章內容皆為 MUKI 本人的原創文章,我會經常更新文章以及修正錯誤內容,因此轉載時建議保留原出處,避免出現版本不一致或已過時的資訊。

本文地址:https://muki.tw/vmodel-definemodel-props-emit/ 已複製

Subscribe
Notify of
guest

2 則留言
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
alex
alex
6 months ago

子元件可以直接改父元件的值,這樣方便好多

Copyright © since 2008 MUKI space* / omegaSS theme All Rights Reserved.