您好,登錄后才能下訂單哦!
這篇文章主要講解了如何使用Vue的雙向綁定,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。
Vue 中需要輸入什么內(nèi)容的時(shí)候,自然會(huì)想到使用 <input v-model="xxx" />
的方式來(lái)實(shí)現(xiàn)雙向綁定。下面是一個(gè)最簡(jiǎn)單的示例
<div id="app"> <h3>What's your name:</h3> <input v-model="name" /> <div>Hello {{ name }}</div> </div>
new Vue({ el: "#app", data: { name: "" } });
在這個(gè)示例的輸入框中輸入的內(nèi)容,會(huì)隨后呈現(xiàn)出來(lái)。這是 Vue 原生對(duì) <input>
的良好支持,也是一個(gè)父組件和子組件之間進(jìn)行雙向數(shù)據(jù)傳遞的典型示例。不過(guò) v-model
是 Vue 2.2.0 才加入的一個(gè)新功能,在此之前,Vue 只支持單向數(shù)據(jù)流。
Vue 的單向數(shù)據(jù)流和 React 相似,父組件可以通過(guò)設(shè)置子組件的屬性(Props)來(lái)向子組件傳遞數(shù)據(jù),而父組件想獲得子組件的數(shù)據(jù),得向子組件注冊(cè)事件,在子組件高興的時(shí)候觸發(fā)這個(gè)事件把數(shù)據(jù)傳遞出來(lái)。一句話總結(jié)起來(lái)就是,Props 向下傳遞數(shù)據(jù),事件向上傳遞數(shù)據(jù)。
上面那個(gè)例子,如果不使用 v-model
,它應(yīng)該是這樣的
<input :value="name" @input="name = $event.target.value" />
由于事件處理寫成了內(nèi)聯(lián)模式,所以腳本部分不需要修改。但是多數(shù)情況下,事件一般都會(huì)定義成一個(gè)方法,代碼就會(huì)復(fù)雜得多
<input :value="name" @input="updateName" />
new Vue({ // .... methods: { updateName(e) { this.name = e.target.value; } } })
從上面的示例來(lái)看 v-model
節(jié)約了不少代碼,最重要的是可以少定義一個(gè)事件處理函數(shù)。所以 v-model
實(shí)際干的事件包括
v-bind
(即 :
)單向綁定一個(gè)屬性(示例::value="name"
)input
事件(即 @input
)到一個(gè)默認(rèn)實(shí)現(xiàn)的事件處理函數(shù)(示例:@input=updateName
this.name = e.target.value
)Vue 對(duì)原生組件進(jìn)行了封裝,所以 <input>
在輸入的時(shí)候會(huì)觸發(fā) input
事件。但是自定義組件應(yīng)該怎么呢?這里不妨借助 JsFiddle Vue 樣板的 Todo List 示例。
點(diǎn)擊 JsFilddle 的 Logo,在上面彈出面板中選擇 Vue 樣板即可
樣板代碼包含 HTML 和 Vue(js) 兩個(gè)部分,代碼如下:
<div id="app"> <h3>Todos:</h3> <ol> <li v-for="todo in todos"> <label> <input type="checkbox" v-on:change="toggle(todo)" v-bind:checked="todo.done"> <del v-if="todo.done"> {{ todo.text }} </del> <span v-else> {{ todo.text }} </span> </label> </li> </ol> </div>
new Vue({ el: "#app", data: { todos: [ { text: "Learn JavaScript", done: false }, { text: "Learn Vue", done: false }, { text: "Play around in JSFiddle", done: true }, { text: "Build something awesome", done: true } ] }, methods: { toggle: function(todo){ todo.done = !todo.done } } })
JsFiddle 的 Vue 模板默認(rèn)實(shí)現(xiàn)一個(gè) Todo 列表的展示,數(shù)據(jù)是固定的,所有內(nèi)容在一個(gè)模板中完成。我們首先要做事情是把單個(gè) Todo 改成一個(gè)子組件。因?yàn)樵?JsFiddle 中不能寫成多文件的形式,所以組件使用 Vue.component()
在腳本中定義,主要是把 <li>
內(nèi)容中的那部分拎出來(lái):
Vue.component("todo", { template: ` <label> <input type="checkbox" @change="toggle" :checked="isDone"> <del v-if="isDone"> {{ text }} </del> <span v-else> {{ text }} </span> </label> `, props: ["text", "done"], data() { return { isDone: this.done }; }, methods: { toggle() { this.isDone = !this.isDone; } } });
原來(lái)定義在 App 中的 toggle()
方法也稍作改動(dòng),定義在組件內(nèi)了。toggle()
調(diào)用的時(shí)候會(huì)修改表示是否完成的 done
的值。但由于 done
是定義在 props
中的屬性,不能直接賦值,所以采用了官方推薦的第一種方法,定義一個(gè)數(shù)據(jù) isDone
,初始化為 this.done
,并在組件內(nèi)使用 isDone
來(lái)控制是否完成這一狀態(tài)。
相應(yīng)的 App 部分的模板和代碼精減了不少:
<div id="app"> <h3>Todos:</h3> <ol> <li v-for="todo in todos"> <todo :text="todo.text" :done="todo.done"></todo> </li> </ol> </div>
new Vue({ el: "#app", data: { todos: [ { text: "Learn JavaScript", done: false }, { text: "Learn Vue", done: false }, { text: "Play around in JSFiddle", done: true }, { text: "Build something awesome", done: true } ] } });
不過(guò)到此為止,數(shù)據(jù)仍然是單向的。從效果上來(lái)看,點(diǎn)擊復(fù)選框可以反饋出刪除線線效果,但這些動(dòng)態(tài)變化都是在 todo
組件內(nèi)部完成的,不存在數(shù)據(jù)綁定的問(wèn)題。
為了讓 todo
組件內(nèi)部的狀態(tài)變化能在 Todo List 中呈現(xiàn)出來(lái),我們?cè)?Todo List 中添加計(jì)數(shù),展示已經(jīng)完成的 Todo 數(shù)量。因?yàn)檫@個(gè)數(shù)量受 todo
組件內(nèi)部狀態(tài)(數(shù)據(jù))的影響,這就需要將 todo
內(nèi)部數(shù)據(jù)變化反應(yīng)到其父組件中,這才有 v-model
的用武之地。
這個(gè)數(shù)量我們?cè)跇?biāo)題中以 n/m
的形式呈現(xiàn),比如 2/4
表示一共 4 條 Todo,已經(jīng)完成 2 條。這需要對(duì) Todo List 的模板和代碼部分進(jìn)行修改,添加 countDone
和 count
兩個(gè)計(jì)算屬性:
<div id="app"> <h3>Todos ({{ countDone }}/{{ count }}):</h3> <!-- ... --> </div>
new Vue({ // ... computed: { count() { return this.todos.length; }, countDone() { return this.todos.filter(todo => todo.done).length; } } });
現(xiàn)在計(jì)數(shù)呈現(xiàn)出來(lái)了,但是現(xiàn)在改變?nèi)蝿?wù)狀態(tài)并不會(huì)對(duì)這個(gè)計(jì)數(shù)產(chǎn)生影響。我們要讓子組件的變動(dòng)對(duì)父組件的數(shù)據(jù)產(chǎn)生影響。v-model
待會(huì)兒再說(shuō),先用最常見(jiàn)的方法,事件:
todo
在 toggle()
中觸發(fā) toggle
事件并將 isDone
作為事件參數(shù)toggle
事件定義事件處理函數(shù)Vue.component("todo", { //... methods: { toggle(e) { this.isDone = !this.isDone; this.$emit("toggle", this.isDone); } } });
<!-- #app 中其它代碼略 --> <todo :text="todo.text" :done="todo.done" @toggle="todo.done = $event"></todo>
這里為 @toggle
綁定的是一個(gè)表達(dá)式。因?yàn)檫@里的 todo
是一個(gè)臨時(shí)變量,如果在 methods
中定義專門的事件處理函數(shù)很難將這個(gè)臨時(shí)變量綁定過(guò)去(當(dāng)然定義普通方法通過(guò)調(diào)用的形式是可以實(shí)現(xiàn)的)。
事件處理函數(shù),一般直接對(duì)應(yīng)于要處理的事情,比如定義onToggle(e)
,綁定為@toggle="onToggle"
。這種情況下不能傳入todo
作為參數(shù)。普通方法,可以定義成
toggle(todo, e)
,在事件定義中以函數(shù)調(diào)用表達(dá)式的形式調(diào)用:@toggle="toggle(todo, $event)"。它和
todo.done = $event` 同屬表達(dá)式。注意二者的區(qū)別,前者是綁定的處理函數(shù)(引用),后者是綁定的表達(dá)式(調(diào)用)
現(xiàn)在通過(guò)事件方式已經(jīng)達(dá)到了預(yù)期效果
之前我們說(shuō)了要用 v-model
實(shí)現(xiàn)的,現(xiàn)在來(lái)改造一下。注意實(shí)現(xiàn) v-model
的幾個(gè)要素
value
屬性(Prop)接受輸入input
事件輸出,帶數(shù)組參數(shù)v-model
綁定Vue.component("todo", { // ... props: ["text", "value"], // <-- 注意 done 改成了 value data() { return { isDone: this.value // <-- 注意 this.done 改成了 this.value }; }, methods: { toggle(e) { this.isDone = !this.isDone; this.$emit("input", this.isDone); // <-- 注意事件名稱變了 } } });
<!-- #app 中其它代碼略 --> <todo :text="todo.text" v-model="todo.done"></todo>
前面講到了 Vue 2.2.0 引入 v-model
特性。由于某些原因,它的輸入屬性是 value
,但輸出事件叫 input
。v-model
、value
、input
這三個(gè)名稱從字面上看不到半點(diǎn)關(guān)系。雖然這看起來(lái)有點(diǎn)奇葩,但這不是重點(diǎn),重點(diǎn)是一個(gè)控件只能雙向綁定一個(gè)屬性嗎?
Vue 2.3.0 引入了 .sync
修飾語(yǔ)用于修飾 v-bind
(即 :
),使之成為雙向綁定。這同樣是語(yǔ)法糖,添加了 .sync
修飾的數(shù)據(jù)綁定會(huì)像 v-model
一樣自動(dòng)注冊(cè)事件處理函數(shù)來(lái)對(duì)被綁定的數(shù)據(jù)進(jìn)行賦值。這種方式同樣要求子組件觸發(fā)特定的事件。不過(guò)這個(gè)事件的名稱好歹和綁定屬性名有點(diǎn)關(guān)系,是在綁定屬性名前添加 update:
前綴。
比如 <sub :some.sync="any" />
將子組件的 some
屬性與父組件的 any
數(shù)據(jù)綁定起來(lái),子組件中需要通過(guò) $emit("update:some", value)
來(lái)觸發(fā)變更。
上面的示例中,使用 v-model
綁定始終感覺(jué)有點(diǎn)別扭,因?yàn)?v-model
的字面意義是雙向綁定一個(gè)數(shù)值,而表示是否未完成的 done
其實(shí)是一個(gè)狀態(tài),而不是一個(gè)數(shù)值。所以我們?cè)俅螌?duì)其進(jìn)行修改,仍然使用 done
這個(gè)屬性名稱(而不是 value
),通過(guò) .sync
來(lái)實(shí)現(xiàn)雙向綁定。
Vue.component("todo", { // ... props: ["text", "done"], // <-- 恢復(fù)成 done data() { return { isDone: this.done // <-- 恢復(fù)成 done }; }, methods: { toggle(e) { this.isDone = !this.isDone; this.$emit("update:done", this.isDone); // <-- 事件名稱:update:done } } });
<!-- #app 中其它代碼略 --> <!-- 注意 v-model 變成了 :done.sync,別忘了冒號(hào)喲 --> <todo :text="todo.text" :done.sync="todo.done"></todo>
通過(guò)上面的講述,我想大家應(yīng)該已經(jīng)明白了 Vue 的雙向綁定其實(shí)就是普通單向綁定和事件組合來(lái)完成的,只不過(guò)通過(guò) v-model
和 .sync
注冊(cè)了默認(rèn)的處理函數(shù)來(lái)更新數(shù)據(jù)。Vue 源碼中有這么一段
// @file: src/compiler/parser/index.js if (modifiers.sync) { addHandler( el, `update:${camelize(name)}`, genAssignmentCode(value, `$event`) ) }
從這段代碼可以看出來(lái),.sync
雙向綁定的時(shí)候,編譯器會(huì)添加一個(gè) update:${camelize(name)}
的事件處理函數(shù)來(lái)對(duì)數(shù)據(jù)進(jìn)行賦值(genAssignmentCode
的字面意思是生成賦值的代碼)。
目前 Vue 的雙向綁定還需要通過(guò)觸發(fā)事件來(lái)實(shí)現(xiàn)數(shù)據(jù)回傳。這和很多所的期望的賦值回傳還是有一定的差距。造成這一差距的主要原因有兩個(gè)
在現(xiàn)在的 Vue 版本中,可以通過(guò)定義計(jì)算屬性來(lái)實(shí)現(xiàn)簡(jiǎn)化,比如
computed: { isDone: { get() { return this.done; }, set(value) { this.$emit("update:done", value); } } }
說(shuō)實(shí)在的,要多定義一個(gè)意義相同名稱不同的變量名也是挺費(fèi)腦筋的。希望 Vue 在將來(lái)的版本中可以通過(guò)一定的技術(shù)手段減化這一過(guò)程,比如為屬性(Prop)聲明添加 sync
選項(xiàng),只要聲明 sync: true
的都可以直接賦值并自動(dòng)觸發(fā) update:xxx
事件。
當(dāng)然作為一個(gè)框架,在解決一個(gè)問(wèn)題的時(shí)候,還要考慮對(duì)其它特性的影響,以及框架的擴(kuò)展性等問(wèn)題,所以最終雙向綁定會(huì)演進(jìn)成什么樣子,我們對(duì) Vue 3.0 拭目以待。
看完上述內(nèi)容,是不是對(duì)如何使用Vue的雙向綁定有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。