MUKI AI Summary
Vue.js 開發常用 props
和 emit
傳遞資料,但這方法複雜難維護。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 開發時,我們很常用到父子元件相互傳遞資料。以往我們會使用 props
和 emit
來實作這個傳遞方式,但老實說,利用他們將資料傳來傳去,會讓程式變得更加複雜也不好維護。
而我們在 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.modelValu
e,再用 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()
的寫法真是好處多多:
- 首先,我們不需要再處理 v-model 的問題,現在子元件的值有調整,也會自動更新到父元件
- 再來,不知道大家有沒有發現,我們連
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
- 我的
vue
的版本:v3.3.8
- 我的
@vitejs/plugin-vue
版本:v5.0.4
我更新之前的版本是v3.2.0
,但這個版本會報錯。
但要更新至幾版才安全?我沒有詳細研究,因為我是直接更新到最近的穩定版。
修改 vite.config.js 設定
▼ 加入以下語法
plugins: [ vue({ script: { defineModel: true } }) ]
我自己是用這兩個方法,再重新部署就正常了。如果你有碰到以上問題但還是沒辦法解決,可以再留言互相交流討論。
結語
文章跟大家分享了如何使用 v-model
實作父子元件之間的資料傳遞,以及在 Vue 3.4 版後,正式發布的 defineModel()
更進一步簡化了這個過程。
如果文章內有任何錯誤需要指正,或有任何問題,都可以在文章底下留言聯繫我唷,感謝。
子元件可以直接改父元件的值,這樣方便好多
對呀,相對來說簡單許多,超級好用 QwQ