Vue.js: data、v-model 與雙向綁定
14 Apr 2017
如何利用 data 與 v-model 實現雙向綁定?原理、語法與表單元件範例。
data
vue instance 可傳入選項物件,其中可設定 data(資料)。
data 是用來
- 儲存元件內部狀態或資料
 - 和 v-model 合作實現雙向綁定
 
資料型別
data 可以是 object 或 function,但元件(component)的 data 只能是 function,這是因為元件內各自擁有自己的 data,而非共用的關係。
原理
在 observer 中,data 透過Object.defineProperty()為元件內屬性重新定義getter和setter method。當 data 被修改時,會透過setter通知變化,觸發 watcher 重新計算、更新與渲染 DOM element。
語法
設定 data,如下所示。
<div id="app">
  ${ message }
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    message: 'Hello World!',
  },
});
// 由於部落格會把使用雙花括號的內容吃掉,所以設定 delimiters 以顯示完整程式碼。
元件的 data 只能是 function。
var Component = Vue.extend({
  data: function() {
    return {
      msg: 'Hello World!',
    };
  },
});
打開瀏覽器會看到

由於 vue instance 重新設定了getter和setter方法,因此 vm.$data.message 完全等於 vm.message。
vm.$data.message === vm.message; // true
注意
- 自訂屬性名稱不要使用
$或_開頭,以免和 vue 所定義的屬性或 API 衝突。因此,對於使用$或_開頭命名的屬性,vue 都不會處理。 - 動態加入的屬性無法擁有 reactivity 的特性(例如:雙向綁定等),因此在建立實體前要先宣告所有會用到的屬性。
 - 如果要對 data 做深拷貝(deep copy),可將 
vm.$data傳入JSON.parse(JSON.stringify())。 
v-model
v-model 是綁定在表單元件或自訂元件上,為實現雙向綁定用的。表單元件像是<input>、<select>和<textarea>,分別說明如下。
Input Text 單行文字

<div id="app">
  <input type="text" v-model="message" />
  <div>${ message }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    message: 'Hello World!',
  },
});
Textarea 多行文字

<div id="app">
  <textarea v-model="message"/></textarea>
  <div>${ message }</div>
</div>
js 程式碼同上-Input Text 單行文字。
Checkbox 複選框
使用v-model 綁定 toggle 的值:當 toggle 為 true,勾選此項目;當 toggle 為 false,不勾選此項目。
一開始先設定 toggle 為 false,不勾選此項目。

<div id="app">
  <input type="checkbox" v-model="toggle" /><label>我是複選框</label>
  toggle = ${ toggle }
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    toggle: false,
  },
});
再來,可在開發者工具的 console tab 上操作 toggle 為 true,勾選此項目。

toggle = true;
或依據給訂的值決定是否勾選,若 toggle 為 1 則勾選,若 toggle 為 2 則不勾選。
<div id="app">
  <input type="checkbox" v-model="toggle" :true-value="1" :false-value="2" />
  <label>我是複選框</label>
  toggle = ${ toggle }
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    toggle: 0,
  },
});
設定 toggle 為 1,勾選此項目。

vm.toggle = 1;
設定 toggle 為 2,不勾選此項目。

vm.toggle = 2;
將多個 checkbox 綁定到同一個群組。在這裡設定 group 為一個陣列,裡面存放勾選選項的 value 字串。例如,若 group 為 ["1", "2"],則勾選第一個和第二個複選框。

<div id="app">
  <input type="checkbox" v-model="group" value="1" /><label>我是複選框 1</label>
  <input type="checkbox" v-model="group" value="2" /><label>我是複選框 2</label>
  <input type="checkbox" v-model="group" value="3" /><label>我是複選框 3</label>
  <div>group = ${ group }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    group: [],
  },
});
Radio Button 單選按鈕

<div id="app">
  <input type="radio" v-model="selected" value="我是單選按鈕 1" /><label>我是單選按鈕 1</label>
  <input type="radio" v-model="selected" value="我是單選按鈕 2" /><label>我是單選按鈕 2</label>
  <input type="radio" v-model="selected" value="我是單選按鈕 3" /><label>我是單選按鈕 3</label>
  <div>selected = ${ selected }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    selected: '尚未選擇任何一項',
  },
});
Select 下拉選單
單選。

<div id="app">
  <select v-model="selected">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <div>selected = ${ selected }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    selected: null,
  },
});
多選,在<select>加上 multiple 屬性。

<div id="app">
  <select v-model="selected" multiple>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <div>selected = ${ selected }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    selected: [],
  },
});
選定選項 B。

vm.selected = ['B'];
Modifiers 修飾符
.lazy
原本雙向綁定的更新方式是以 input 事件監聽,亦即資料變動即更新,但使用.lazy會改用 change 事件監聽,event trigger 才更新。
如下所示,更改 input 內的值並不會馬上變更 model 的資料,而是等到滑鼠移到輸入框外,觸發 change 事件才更新。

<div id="app">
  <input v-model.lazy="message" type="text" />
  <div>${ message }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    message: 'Hello World!',
  },
});
對照一下前面的例子,若無.lazy,是以 input 事件監聽,資料改變就會更新。

.number
將字串轉為數字。
如果沒有強制轉換,我們在v-model所得到的值的資料型態是 string。
如下所示,在輸入框輸入數字,然後印出這個數字的資料型別。

<div id="app">
  <input v-model="count" />
  <div>${ count }</div>
  <div>${ type }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    count: 0,
    type: '',
  },
  watch: {
    count: function(val) {
      this.type = typeof val;
    },
  },
});
使用.number強制轉為數字。
如下所示,輸入 1 後,得到的資料型別是 number,而非 string。

<div id="app">
  <input v-model.number="count" />
  <div>${ count }</div>
  <div>${ type }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    count: 0,
    type: '',
  },
  watch: {
    count: function(val) {
      this.type = typeof val;
    },
  },
});
.trim
去除首尾空白。
如下所示,輸入前面有空白的字串,滑鼠移開後不會去除空白。

<div id="app">
  <input v-model="message" />
  <div>${ message }</div>
</div>
var vm = new Vue({
  el: '#app',
  delimiters: ['${', '}'],
  data: {
    message: 'Hello World!',
    type: '',
  },
});
v-model 使用.trim去除空白。
如下所示,輸入前面有空白的字串,滑鼠移開後會去除空白。

<div id="app">
  <input v-model.trim="message" />
  <div>${ message }</div>
</div>
關於自定義元件與v-model的狀況可參考這篇-使用自定義事件的表單輸入元件。