Vue 中須要輸入什麼內容的時候,天然會想到使用 <input v-model="xxx" />
的方式來實現雙向綁定。下面是一個最簡單的示例segmentfault
<div id="app"> <h2>What's your name:</h2> <input v-model="name" /> <div>Hello {{ name }}</div> </div>
new Vue({ el: "#app", data: { name: "" } });
JsFiddle 演示
在這個示例的輸入框中輸入的內容,會隨後呈現出來。這是 Vue 原生對 <input>
的良好支持,也是一個父組件和子組件之間進行雙向數據傳遞的典型示例。不過 v-model
是 Vue 2.2.0 才加入的一個新功能,在此以前,Vue 只支持單向數據流。app
Vue 的單向數據流和 React 類似,父組件能夠經過設置子組件的屬性(Props)來向子組件傳遞數據,而父組件想得到子組件的數據,得向子組件註冊事件,在子組件高興的時候觸發這個事件把數據傳遞出來。一句話總結起來就是,Props 向下傳遞數據,事件向上傳遞數據。框架
上面那個例子,若是不使用 v-model
,它應該是這樣的函數
<input :value="name" @input="name = $event.target.value" />
因爲事件處理寫成了內聯模式,因此腳本部分不須要修改。可是多數狀況下,事件通常都會定義成一個方法,代碼就會複雜得多ui
<input :value="name" @input="updateName" />
new Vue({ // .... methods: { updateName(e) { this.name = e.target.value; } } })
從上面的示例來看 v-model
節約了很多代碼,最重要的是能夠少定義一個事件處理函數。因此 v-model
實際乾的事件包括this
v-bind
(即 :
)單向綁定一個屬性(示例::value="name"
)input
事件(即 @input
)到一個默認實現的事件處理函數(示例:@input=updateName
this.name = e.target.value
)v-model
Vue 對原生組件進行了封裝,因此 <input>
在輸入的時候會觸發 input
事件。可是自定義組件應該怎麼呢?這裏不妨藉助 JsFiddle Vue 樣板的 Todo List 示例。spa
點擊 JsFilddle 的 Logo,在上面彈出面板中選擇 Vue 樣板便可
樣板代碼包含 HTML 和 Vue(js) 兩個部分,代碼以下:.net
<div id="app"> <h2>Todos:</h2> <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 模板默認實現一個 Todo 列表的展現,數據是固定的,全部內容在一個模板中完成。咱們首先要作事情是把單個 Todo 改爲一個子組件。由於在 JsFiddle 中不能寫成多文件的形式,因此組件使用 Vue.component()
在腳本中定義,主要是把 <li>
內容中的那部分拎出來:雙向綁定
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; } } });
原來定義在 App 中的 toggle()
方法也稍做改動,定義在組件內了。toggle()
調用的時候會修改表示是否完成的 done
的值。但因爲 done
是定義在 props
中的屬性,不能直接賦值,因此採用了官方推薦的第一種方法,定義一個數據 isDone
,初始化爲 this.done
,並在組件內使用 isDone
來控制是否完成這一狀態。
相應的 App 部分的模板和代碼精減了很多:
<div id="app"> <h2>Todos:</h2> <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 } ] } });
JsFiddle 演示
不過到此爲止,數據仍然是單向的。從效果上來看,點擊複選框能夠反饋出刪除線線效果,但這些動態變化都是在 todo
組件內部完成的,不存在數據綁定的問題。
爲了讓 todo
組件內部的狀態變化能在 Todo List 中呈現出來,咱們在 Todo List 中添加計數,展現已經完成的 Todo 數量。由於這個數量受 todo
組件內部狀態(數據)的影響,這就須要將 todo
內部數據變化反應到其父組件中,這纔有 v-model
的用武之地。
這個數量咱們在標題中以 n/m
的形式呈現,好比 2/4
表示一共 4 條 Todo,已經完成 2 條。這須要對 Todo List 的模板和代碼部分進行修改,添加 countDone
和 count
兩個計算屬性:
<div id="app"> <h2>Todos ({{ countDone }}/{{ count }}):</h2> <!-- ... --> </div>
new Vue({ // ... computed: { count() { return this.todos.length; }, countDone() { return this.todos.filter(todo => todo.done).length; } } });
如今計數呈現出來了,可是如今改變任務狀態並不會對這個計數產生影響。咱們要讓子組件的變更對父組件的數據產生影響。v-model
待會兒再說,先用最多見的方法,事件:
todo
在 toggle()
中觸發 toggle
事件並將 isDone
做爲事件參數toggle
事件定義事件處理函數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
綁定的是一個表達式。由於這裏的 todo
是一個臨時變量,若是在 methods
中定義專門的事件處理函數很難將這個臨時變量綁定過去(固然定義普通方法經過調用的形式是能夠實現的)。
事件處理函數,通常直接對應於要處理的事情,好比定義onToggle(e)
,綁定爲@toggle="onToggle"
。這種狀況下不能傳入todo
做爲參數。普通方法,能夠定義成
toggle(todo, e)
,在事件定義中以函數調用表達式的形式調用:@toggle="toggle(todo, $event)"。它和
todo.done = $event` 同屬表達式。注意兩者的區別,前者是綁定的處理函數(引用),後者是綁定的表達式(調用)
如今經過事件方式已經達到了預期效果
Js Fiddle 演示
v-model
以前咱們說了要用 v-model
實現的,如今來改造一下。注意實現 v-model
的幾個要素
value
屬性(Prop)接受輸入input
事件輸出,帶數組參數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>
.sync
實現其它數據綁定前面講到了 Vue 2.2.0 引入 v-model
特性。因爲某些緣由,它的輸入屬性是 value
,但輸出事件叫 input
。v-model
、value
、input
這三個名稱從字面上看不到半點關係。雖然這看起來有點奇葩,但這不是重點,重點是一個控件只能雙向綁定一個屬性嗎?
Vue 2.3.0 引入了 .sync
修飾語用於修飾 v-bind
(即 :
),使之成爲雙向綁定。這一樣是語法糖,添加了 .sync
修飾的數據綁定會像 v-model
同樣自動註冊事件處理函數來對被綁定的數據進行賦值。這種方式一樣要求子組件觸發特定的事件。不過這個事件的名稱好歹和綁定屬性名有點關係,是在綁定屬性名前添加 update:
前綴。
好比 <sub :some.sync="any" />
將子組件的 some
屬性與父組件的 any
數據綁定起來,子組件中須要經過 $emit("update:some", value)
來觸發變動。
上面的示例中,使用 v-model
綁定始終感受有點彆扭,由於 v-model
的字面意義是雙向綁定一個數值,而表示是否未完成的 done
實際上是一個狀態,而不是一個數值。因此咱們再次對其進行修改,仍然使用 done
這個屬性名稱(而不是 value
),經過 .sync
來實現雙向綁定。
Vue.component("todo", { // ... props: ["text", "done"], // <-- 恢復成 done data() { return { isDone: this.done // <-- 恢復成 done }; }, methods: { toggle(e) { this.isDone = !this.isDone; this.$emit("update:done", this.isDone); // <-- 事件名稱:update:done } } });
<!-- #app 中其它代碼略 --> <!-- 注意 v-model 變成了 :done.sync,別忘了冒號喲 --> <todo :text="todo.text" :done.sync="todo.done"></todo>
Js Fiddle 演示
經過上面的講述,我想你們應該已經明白了 Vue 的雙向綁定其實就是普通單向綁定和事件組合來完成的,只不過經過 v-model
和 .sync
註冊了默認的處理函數來更新數據。Vue 源碼中有這麼一段
// @file: src/compiler/parser/index.js if (modifiers.sync) { addHandler( el, `update:${camelize(name)}`, genAssignmentCode(value, `$event`) ) }
從這段代碼能夠看出來,.sync
雙向綁定的時候,編譯器會添加一個 update:${camelize(name)}
的事件處理函數來對數據進行賦值(genAssignmentCode
的字面意思是生成賦值的代碼)。
目前 Vue 的雙向綁定還須要經過觸發事件來實現數據回傳。這和不少所的指望的賦值回傳仍是有必定的差距。形成這一差距的主要緣由有兩個
在如今的 Vue 版本中,能夠經過定義計算屬性來實現簡化,好比
computed: { isDone: { get() { return this.done; }, set(value) { this.$emit("update:done", value); } } }
說實在的,要多定義一個意義相同名稱不一樣的變量名也是挺費腦筋的。但願 Vue 在未來的版本中能夠經過必定的技術手段減化這一過程,好比爲屬性(Prop)聲明添加 sync
選項,只要聲明 sync: true
的均可以直接賦值並自動觸發 update:xxx
事件。
固然做爲一個框架,在解決一個問題的時候,還要考慮對其它特性的影響,以及框架的擴展性等問題,因此最終雙向綁定會演進成什麼樣子,咱們對 Vue 3.0 拭目以待。
來源:
Vue 中須要輸入什麼內容的時候,天然會想到使用 <input v-model="xxx" />
的方式來實現雙向綁定。下面是一個最簡單的示例
<div id="app"> <h2>What's your name:</h2> <input v-model="name" /> <div>Hello {{ name }}</div> </div>
new Vue({ el: "#app", data: { name: "" } });
JsFiddle 演示
在這個示例的輸入框中輸入的內容,會隨後呈現出來。這是 Vue 原生對 <input>
的良好支持,也是一個父組件和子組件之間進行雙向數據傳遞的典型示例。不過 v-model
是 Vue 2.2.0 才加入的一個新功能,在此以前,Vue 只支持單向數據流。
Vue 的單向數據流和 React 類似,父組件能夠經過設置子組件的屬性(Props)來向子組件傳遞數據,而父組件想得到子組件的數據,得向子組件註冊事件,在子組件高興的時候觸發這個事件把數據傳遞出來。一句話總結起來就是,Props 向下傳遞數據,事件向上傳遞數據。
上面那個例子,若是不使用 v-model
,它應該是這樣的
<input :value="name" @input="name = $event.target.value" />
因爲事件處理寫成了內聯模式,因此腳本部分不須要修改。可是多數狀況下,事件通常都會定義成一個方法,代碼就會複雜得多
<input :value="name" @input="updateName" />
new Vue({ // .... methods: { updateName(e) { this.name = e.target.value; } } })
從上面的示例來看 v-model
節約了很多代碼,最重要的是能夠少定義一個事件處理函數。因此 v-model
實際乾的事件包括
v-bind
(即 :
)單向綁定一個屬性(示例::value="name"
)input
事件(即 @input
)到一個默認實現的事件處理函數(示例:@input=updateName
this.name = e.target.value
)v-model
Vue 對原生組件進行了封裝,因此 <input>
在輸入的時候會觸發 input
事件。可是自定義組件應該怎麼呢?這裏不妨藉助 JsFiddle Vue 樣板的 Todo List 示例。
點擊 JsFilddle 的 Logo,在上面彈出面板中選擇 Vue 樣板便可
樣板代碼包含 HTML 和 Vue(js) 兩個部分,代碼以下:
<div id="app"> <h2>Todos:</h2> <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 模板默認實現一個 Todo 列表的展現,數據是固定的,全部內容在一個模板中完成。咱們首先要作事情是把單個 Todo 改爲一個子組件。由於在 JsFiddle 中不能寫成多文件的形式,因此組件使用 Vue.component()
在腳本中定義,主要是把 <li>
內容中的那部分拎出來:
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; } } });
原來定義在 App 中的 toggle()
方法也稍做改動,定義在組件內了。toggle()
調用的時候會修改表示是否完成的 done
的值。但因爲 done
是定義在 props
中的屬性,不能直接賦值,因此採用了官方推薦的第一種方法,定義一個數據 isDone
,初始化爲 this.done
,並在組件內使用 isDone
來控制是否完成這一狀態。
相應的 App 部分的模板和代碼精減了很多:
<div id="app"> <h2>Todos:</h2> <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 } ] } });
JsFiddle 演示
不過到此爲止,數據仍然是單向的。從效果上來看,點擊複選框能夠反饋出刪除線線效果,但這些動態變化都是在 todo
組件內部完成的,不存在數據綁定的問題。
爲了讓 todo
組件內部的狀態變化能在 Todo List 中呈現出來,咱們在 Todo List 中添加計數,展現已經完成的 Todo 數量。由於這個數量受 todo
組件內部狀態(數據)的影響,這就須要將 todo
內部數據變化反應到其父組件中,這纔有 v-model
的用武之地。
這個數量咱們在標題中以 n/m
的形式呈現,好比 2/4
表示一共 4 條 Todo,已經完成 2 條。這須要對 Todo List 的模板和代碼部分進行修改,添加 countDone
和 count
兩個計算屬性:
<div id="app"> <h2>Todos ({{ countDone }}/{{ count }}):</h2> <!-- ... --> </div>
new Vue({ // ... computed: { count() { return this.todos.length; }, countDone() { return this.todos.filter(todo => todo.done).length; } } });
如今計數呈現出來了,可是如今改變任務狀態並不會對這個計數產生影響。咱們要讓子組件的變更對父組件的數據產生影響。v-model
待會兒再說,先用最多見的方法,事件:
todo
在 toggle()
中觸發 toggle
事件並將 isDone
做爲事件參數toggle
事件定義事件處理函數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
綁定的是一個表達式。由於這裏的 todo
是一個臨時變量,若是在 methods
中定義專門的事件處理函數很難將這個臨時變量綁定過去(固然定義普通方法經過調用的形式是能夠實現的)。
事件處理函數,通常直接對應於要處理的事情,好比定義onToggle(e)
,綁定爲@toggle="onToggle"
。這種狀況下不能傳入todo
做爲參數。普通方法,能夠定義成
toggle(todo, e)
,在事件定義中以函數調用表達式的形式調用:@toggle="toggle(todo, $event)"。它和
todo.done = $event` 同屬表達式。注意兩者的區別,前者是綁定的處理函數(引用),後者是綁定的表達式(調用)
如今經過事件方式已經達到了預期效果
Js Fiddle 演示
v-model
以前咱們說了要用 v-model
實現的,如今來改造一下。注意實現 v-model
的幾個要素
value
屬性(Prop)接受輸入input
事件輸出,帶數組參數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>
.sync
實現其它數據綁定前面講到了 Vue 2.2.0 引入 v-model
特性。因爲某些緣由,它的輸入屬性是 value
,但輸出事件叫 input
。v-model
、value
、input
這三個名稱從字面上看不到半點關係。雖然這看起來有點奇葩,但這不是重點,重點是一個控件只能雙向綁定一個屬性嗎?
Vue 2.3.0 引入了 .sync
修飾語用於修飾 v-bind
(即 :
),使之成爲雙向綁定。這一樣是語法糖,添加了 .sync
修飾的數據綁定會像 v-model
同樣自動註冊事件處理函數來對被綁定的數據進行賦值。這種方式一樣要求子組件觸發特定的事件。不過這個事件的名稱好歹和綁定屬性名有點關係,是在綁定屬性名前添加 update:
前綴。
好比 <sub :some.sync="any" />
將子組件的 some
屬性與父組件的 any
數據綁定起來,子組件中須要經過 $emit("update:some", value)
來觸發變動。
上面的示例中,使用 v-model
綁定始終感受有點彆扭,由於 v-model
的字面意義是雙向綁定一個數值,而表示是否未完成的 done
實際上是一個狀態,而不是一個數值。因此咱們再次對其進行修改,仍然使用 done
這個屬性名稱(而不是 value
),經過 .sync
來實現雙向綁定。
Vue.component("todo", { // ... props: ["text", "done"], // <-- 恢復成 done data() { return { isDone: this.done // <-- 恢復成 done }; }, methods: { toggle(e) { this.isDone = !this.isDone; this.$emit("update:done", this.isDone); // <-- 事件名稱:update:done } } });
<!-- #app 中其它代碼略 --> <!-- 注意 v-model 變成了 :done.sync,別忘了冒號喲 --> <todo :text="todo.text" :done.sync="todo.done"></todo>
Js Fiddle 演示
經過上面的講述,我想你們應該已經明白了 Vue 的雙向綁定其實就是普通單向綁定和事件組合來完成的,只不過經過 v-model
和 .sync
註冊了默認的處理函數來更新數據。Vue 源碼中有這麼一段
// @file: src/compiler/parser/index.js if (modifiers.sync) { addHandler( el, `update:${camelize(name)}`, genAssignmentCode(value, `$event`) ) }
從這段代碼能夠看出來,.sync
雙向綁定的時候,編譯器會添加一個 update:${camelize(name)}
的事件處理函數來對數據進行賦值(genAssignmentCode
的字面意思是生成賦值的代碼)。
目前 Vue 的雙向綁定還須要經過觸發事件來實現數據回傳。這和不少所的指望的賦值回傳仍是有必定的差距。形成這一差距的主要緣由有兩個
在如今的 Vue 版本中,能夠經過定義計算屬性來實現簡化,好比
computed: { isDone: { get() { return this.done; }, set(value) { this.$emit("update:done", value); } } }
說實在的,要多定義一個意義相同名稱不一樣的變量名也是挺費腦筋的。但願 Vue 在未來的版本中能夠經過必定的技術手段減化這一過程,好比爲屬性(Prop)聲明添加 sync
選項,只要聲明 sync: true
的均可以直接賦值並自動觸發 update:xxx
事件。
固然做爲一個框架,在解決一個問題的時候,還要考慮對其它特性的影響,以及框架的擴展性等問題,因此最終雙向綁定會演進成什麼樣子,咱們對 Vue 3.0 拭目以待。
來源:https://segmentfault.com/a/1190000016593014