Vue.js中的v-model
指令大家都非常熟悉,它實作了元件間的雙向資料綁定。但當為自訂元件手動實作v-model
時,通常會遇到一些問題。
通常的做法如下:
<code class="language-javascript">const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); <template></template></code>
請注意,我們不會在元件內部修改modelValue
prop的值。相反,我們將更新後的值透過emit
方法傳遞回父元件,由父元件進行實際的修改。這是因為:子元件不應該影響父元件的狀態,這會使資料流複雜化,並增加除錯難度。
如Vue文件所述,不應在子元件內部修改prop。如果這樣做,Vue會在控制台中發出警告。
物件的情況如何?
JavaScript中的物件和陣列是一種特殊情況,因為它們是按引用傳遞的。這意味著元件可以直接修改物件prop的嵌套屬性。但是,Vue不會對嵌套物件屬性中的修改發出警告(追蹤這些修改會帶來效能損失)。因此,這種意外的更改可能會導致應用程式中出現難以察覺和難以調試的問題。
大多數情況下,我們使用基本值作為v-model
。但是,在某些情況下,例如在建立表單元件時,我們可能需要一個可以處理物件的自訂v-model
。這就引出一個重要的問題:
如何實作一個自訂的
v-model
來處理對象,同時避免上述陷阱?
一種方法是使用可寫入的計算屬性或defineModel
輔助函數。但是,這兩種解決方案都有一個顯著的缺點:它們直接修改了原始對象,這違背了保持清晰資料流的目的。
為了說明這個問題,讓我們來看一個「表單」元件的範例。這個元件旨在在表單中的值發生變更時,將物件的更新副本發射回父元件。我們將嘗試使用可寫的計算屬性來實現這一點。
在這個例子中,可寫的計算屬性仍然會修改原始物件。
<code class="language-javascript">import { computed } from 'vue'; import { cloneDeep } from 'lodash-es'; type Props = { modelValue: { name: string; email: string; }; }; const props = withDefaults(defineProps<Props>(), { modelValue: () => ({ name: '', email: '' }), }); const emit = defineEmits<{ 'update:modelValue': [value: Props['modelValue']]; }>(); const formData = computed({ // 返回的getter对象仍然是可变的 get() { return props.modelValue; }, // 注释掉setter仍然会修改prop set(newValue) { emit('update:modelValue', cloneDeep(newValue)); }, });</code>
這不起作用,因為從getter回傳的物件仍然是可變的,導致原始物件被意外修改。
defineModel
也是一樣。由於update:modelValue
沒有從組件中發出,並且物件屬性在沒有任何警告的情況下被修改。
處理這種情況的「Vue方式」是使用內部響應式值來表示對象,並實現兩個觀察者:
modelValue
prop的更改,並更新內部值。這確保了內部狀態反映了父元件傳遞的最新prop值。 為了防止這兩個觀察者之間發生無休止的回饋循環,我們需要確保對modelValue
prop的更新不會意外地重新觸發內部值的觀察者。
<code class="language-javascript">const props = defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']); <template></template></code>
我知道你在想什麼:「這太多了!」讓我們看看如何進一步簡化它。
將此邏輯提取到可重複使用的組合式函數中是簡化流程的好方法。但好消息是:我們甚至不需要這樣做! VueUse中的useVModel
組合式函數可以幫我們處理這個問題!
VueUse是一個功能強大的Vue實用程式庫,通常被稱為組合式實用程式的「瑞士軍刀」。它是完全可樹狀搖動的,因此我們可以只使用所需的部分,而無需擔心會增加套件的大小。
以下是使用useVModel
重構之前的範例:
<code class="language-javascript">import { computed } from 'vue'; import { cloneDeep } from 'lodash-es'; type Props = { modelValue: { name: string; email: string; }; }; const props = withDefaults(defineProps<Props>(), { modelValue: () => ({ name: '', email: '' }), }); const emit = defineEmits<{ 'update:modelValue': [value: Props['modelValue']]; }>(); const formData = computed({ // 返回的getter对象仍然是可变的 get() { return props.modelValue; }, // 注释掉setter仍然会修改prop set(newValue) { emit('update:modelValue', cloneDeep(newValue)); }, });</code>
簡潔許多!
就是這樣!我們已經探討瞭如何在Vue中正確使用帶有v-model
的對象,而不會直接從子組件中修改它。透過使用觀察者或利用VueUse的useVModel
等組合式函數,我們可以在應用程式中保持清晰且可預測的狀態管理。
這裡有一個包含本文所有範例的Stackblitz連結。隨意探索和實驗。
感謝您的閱讀,祝您編碼愉快!
以上是如何在 Vue 中使用帶有 v-model 的對象的詳細內容。更多資訊請關注PHP中文網其他相關文章!