Vue.js使用帶有對(duì)象的 v-model 來創(chuàng)建自定義組件的詳細(xì)操作
翻譯原文:Vue.js: Using v-model with objects for custom components - Simon Kollross
組件是 Vue.js 最強(qiáng)大的特性,它允許你把應(yīng)用拆成小的、可復(fù)用的單元,再組合成新的功能。
在構(gòu)建 SimpleSell 的過程中,我需要一些自定義組件,它們內(nèi)部可能包括多個(gè) input 字段。我們希望它們像“可復(fù)用的輸入控件”一樣使用,同時(shí)它們的狀態(tài)由一個(gè)對(duì)象提供。
v-model 的基礎(chǔ)
你當(dāng)然知道 v-model —— 它是讓你的大多數(shù)輸入元素能夠工作的重要屬性。但你知道嗎,你也可以把 v-model 作為你自己組件的接口。
例如,對(duì)普通 HTML <input>:
<input type="text" v-model="value">
其實(shí) v-model 不過是下列寫法的語法糖:
<input type="text" :value="value" @input="e => value = e.target.value">
也就是說:首先有 :value 綁定,它提供 input 的值;其次,當(dāng) input 觸發(fā) input 事件時(shí),我們更新數(shù)據(jù)屬性 value。你也可以簡(jiǎn)寫為 @input="value = $event.target.value"。
在自定義組件中包裹一個(gè) input
接下來,我們創(chuàng)建一個(gè)叫 CreateCustomer.vue 的小組件,它封裝了一個(gè) input 元素:
<template>
<div>
<label>Name</label>
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: ['value'],
}
</script>也就是:我們把父組件傳來的 value 綁定給內(nèi)部的 text 輸入框,當(dāng) input 值改變時(shí),觸發(fā) input 事件并把當(dāng)前值發(fā)回父組件。使用方式如下:
<template>
<CreateCustomer v-model="name"></CreateCustomer>
</template>
<script>
import CreateCustomer from './CreateCustomer'
export default {
components: { CreateCustomer },
data() {
return {
name: 'John Doe',
}
},
}
</script>這樣就實(shí)現(xiàn)了自定義組件上的雙向綁定。
對(duì)象 + v-model
但如果你加入多個(gè)輸入字段,并希望把整個(gè)對(duì)象作為 v-model 的值 —— 情況就復(fù)雜一些。我在一個(gè) GitHub issue 中看到一個(gè)例子,說如果你對(duì)對(duì)象使用 v-model,每次數(shù)據(jù)更新都必須 $emit 一個(gè)“全新的對(duì)象實(shí)例”(new object),然后替換掉父組件中存儲(chǔ)的對(duì)象。否則,由于 JavaScript 對(duì)象是以引用方式傳遞,會(huì)出現(xiàn)奇怪且難以 debug 的行為(例如 watcher 無法正確觸發(fā))。。
乍一看好像很復(fù)雜,但其實(shí)并不。你只需要對(duì)對(duì)象做一次(深/淺)克隆,對(duì)克隆后的對(duì)象修改值,然后 $emit 那個(gè)新對(duì)象即可。
舉例,我們擴(kuò)展 CreateCustomer 組件,加多一個(gè) select 用于 contact type:
<template>
<div>
<div>
<label>Name</label>
<input type="text" :value="value.name" @input="update('name', $event.target.value)">
</div>
<div>
<label>Type</label>
<select :value="value.type" @input="update('type', $event.target.value)">
<option value="Person">Person</option>
<option value="Company">Company</option>
</select>
</div>
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
update(key, value) {
this.$emit('input', { ...this.value, [key]: value })
}
}
}
</script>這里我們將 v-model 的值設(shè)為一個(gè)對(duì)象(例如 { name: 'John Doe', type: 'Person' })。在 template 里,對(duì)象的屬性可以直接用于 :value 綁定。當(dāng)某個(gè)字段變更時(shí),我們用 object spread({ ...this.value, [key]: value })創(chuàng)建一個(gè)新的對(duì)象,把修改后的值設(shè)置給相應(yīng) key,然后通過 input 事件發(fā)回父組件。
使用null作為v-model值
如果父組件把 v-model 的初始值設(shè)為 null —— 因?yàn)闀簳r(shí)不知道會(huì)不會(huì)使用這個(gè)表單 —— 那么直接訪問對(duì)象屬性會(huì)導(dǎo)致錯(cuò)誤(因?yàn)?null 沒有屬性)。同時(shí),如果沒有為 select 提供默認(rèn)值,select 會(huì)顯示一個(gè)無效的空選項(xiàng)。這個(gè)情況曾讓我頭疼。幸運(yùn)的是,有人給了解決建議:使用 computed 屬性,設(shè)定一個(gè) local 變量 —— 如果 value 存在就用它,否則返回一個(gè)帶默認(rèn)字段的對(duì)象。這樣可以防止空指針錯(cuò)誤。
示例簡(jiǎn)化如下:
computed: {
local() {
return this.value ? this.value : { type: 'Person' }
}
},
methods: {
update(key, value) {
this.$emit('input', { ...this.local, [key]: value })
}
}然后在 template 中,都用 local 而不是 value。這樣一來,當(dāng) value 為 null 時(shí),也能優(yōu)雅地使用默認(rèn)值。
嵌套對(duì)象的情況
假設(shè)我們的數(shù)據(jù)模型更復(fù)雜,有 nested 對(duì)象,例如:
{
name: 'John Doe',
type: 'Person',
address: {
street: 'Example Street 42',
zip: '12345',
city: 'Example City'
}
}
我們可以繼續(xù)用同樣的方法 —— 要么為每層嵌套創(chuàng)建一個(gè)子組件(比較推薦的方式),要么在一個(gè)組件里處理所有層級(jí)(較復(fù)雜但也可行)。
為了支持嵌套字段(例如 address.street),需要:
- 在 computed 的
local中,給出一個(gè)含默認(rèn)值的對(duì)象結(jié)構(gòu),比如{ type: 'Person', address: {} },這樣就算value是 null,也不會(huì)導(dǎo)致錯(cuò)誤。 - 在更新的時(shí)候,使用深拷貝(deep clone) + 按路徑設(shè)置(path setting),然后
$emit這個(gè)新對(duì)象。因?yàn)?JavaScript 本身沒有深拷貝 + 路徑設(shè)置,我們可以借助像 Lodash 那樣的庫(kù)來實(shí)現(xiàn) —— 用它的cloneDeep()和set()方法。
例如,在更新 address.street 時(shí):
update(key, value) {
this.$emit('input',
tap(cloneDeep(this.local), v => set(v, key, value))
)
}
其中 tap(a, fn) 接受一個(gè)值 a 和一個(gè)函數(shù) fn,對(duì) a 執(zhí)行 fn(修改 a),然后返回 a。這樣你只需一行就能創(chuàng)建深拷貝、修改它、并發(fā)出新的對(duì)象。
處理包含數(shù)組的情況(如聯(lián)系人列表 contacts)
如果你的數(shù)據(jù)模型中還包含數(shù)組,比如聯(lián)系人列表 contacts: [ { type: 'Email', value: '...' }, ... ],你也可以用類似方式擴(kuò)展組件。
- 在 computed
local屬性中,為contacts字段提供一個(gè)默認(rèn)值([]); - 當(dāng)用戶”添加“或”刪除“聯(lián)系人時(shí),用深拷貝 + 修改數(shù)組(push / splice) +
$emit。這樣父組件收到的是一個(gè)新的對(duì)象,其中包含更新后的數(shù)組。
需要注意的是,對(duì)于僅僅是組件內(nèi)部狀態(tài)(例如“當(dāng)前正在輸入的新聯(lián)系人 type/value”),你可以繼續(xù)使用本地 data + v-model,而不需要每次輸入就發(fā)出 input 事件。只有當(dāng)用戶點(diǎn)擊 “Add” / “Remove” 時(shí)才發(fā)出。
在 created 鉤子里發(fā)出初始狀態(tài)
有時(shí)候你可能希望在組件創(chuàng)建時(shí)就將默認(rèn)狀態(tài)(例如帶默認(rèn)字段、默認(rèn)數(shù)組、默認(rèn)空聯(lián)系人)發(fā)回給父組件。你可以在 created() 鉤子里調(diào)用一次 update 方法(或直接 $emit)。但要注意,只發(fā)出一次,并在同一次調(diào)用中設(shè)置所有默認(rèn)值,以免觸發(fā)多次 watcher,導(dǎo)致不必要的副作用或時(shí)序問題。
總結(jié)
恭喜你讀到這里!這篇文章比我預(yù)期的要長(zhǎng)得多 ??
當(dāng)然,并不是你構(gòu)建的每個(gè)組件都要用上這些模式。有時(shí)把復(fù)雜組件分解成更小、更好理解、更易維護(hù)的子組件反而更好。
但如果你確實(shí)需要“可復(fù)用的、帶簡(jiǎn)單 v-model 接口的復(fù)雜輸入控件”(例如表單子組件、多字段 + 嵌套對(duì)象 + 數(shù)組……這種場(chǎng)景),考慮這些技巧是很有幫助的 —— 深拷貝 + 每次 emit 新對(duì)象 + default 值 / fallback + 避免 null,就能讓一切更穩(wěn)健、更可預(yù)測(cè)。
到此這篇關(guān)于Vuejs使用帶有對(duì)象的 v-model 來創(chuàng)建自定義組件的文章就介紹到這了,更多相關(guān)vuejs v-model創(chuàng)建自定義組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue項(xiàng)目前端錯(cuò)誤收集之sentry教程詳解
Sentry 是一個(gè)開源的錯(cuò)誤追蹤工具,可以幫助開發(fā)人員實(shí)時(shí)監(jiān)控和修復(fù)系統(tǒng)中的錯(cuò)誤。這篇文章主要介紹了vue項(xiàng)目前端錯(cuò)誤收集之sentry,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
SpringBoot實(shí)現(xiàn)全局和局部跨域的兩種方式
本文主要介紹了SpringBoot實(shí)現(xiàn)全局和局部跨域的兩種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
vue如何解決數(shù)據(jù)加載時(shí),插值表達(dá)式閃爍問題
這篇文章主要介紹了vue如何解決數(shù)據(jù)加載時(shí),插值表達(dá)式閃爍問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Vue3組合式函數(shù)Composable實(shí)戰(zhàn)ref和unref使用
這篇文章主要為大家介紹了Vue3組合式函數(shù)Composable實(shí)戰(zhàn)ref和unref使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Element-UI?el-table對(duì)循環(huán)產(chǎn)生的空白列賦默認(rèn)值方式
這篇文章主要介紹了Element-UI?el-table對(duì)循環(huán)產(chǎn)生的空白列賦默認(rèn)值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue中的@click.native 原生點(diǎn)擊事件
這篇文章主要介紹了vue中的@click.native 原生點(diǎn)擊事件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
vue3+vite項(xiàng)目中按需引入vant報(bào)錯(cuò):Failed?to?resolve?import的解決方案
最近在vue項(xiàng)目中引入vant的時(shí)候發(fā)現(xiàn)報(bào)錯(cuò)了,經(jīng)過嘗試發(fā)現(xiàn)了問題,現(xiàn)將完整引入流程提供給大家參考,下面這篇文章主要給大家介紹了關(guān)于vue3+vite項(xiàng)目中按需引入vant報(bào)錯(cuò):Failed?to?resolve?import的解決方案,需要的朋友可以參考下2022-12-12

