Vue 中需要输入什么内容的时候,自然会想到使用 <input v-model="xxx" /> 的方式来实现双向绑定。下面是一个最简单的示例
<div> <h2>What's your name:</h2> <input v-model="name" /> <div>Hello {{ name }}</div> </div>
new Vue({ el: "#app", data: { name: "" } });
在这个示例的输入框中输入的内容,会随后呈现出来。这是 Vue 原生对 <input> 的良好支持,也是一个父组件和子组件之间进行双向数据传递的典型示例。不过 v-model 是 Vue 2.2.0 才加入的一个新功能,在此之前,Vue 只支持单向数据流。
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-modelVue 对原生组件进行了封装,所以 <input> 在输入的时候会触发 input 事件。但是自定义组件应该怎么呢?这里不妨借助 JsFiddle Vue 样板的 Todo List 示例。
JsFiddle 的 Vue 样板 点击 JsFilddle 的 Logo,在上面弹出面板中选择 Vue 样板即可样板代码包含 HTML 和 Vue(js) 两个部分,代码如下:
<div> <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 } } })
定义 Todo 组件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> <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 } ] } });
不过到此为止,数据仍然是单向的。从效果上来看,点击复选框可以反馈出删除线线效果,但这些动态变化都是在 todo 组件内部完成的,不存在数据绑定的问题。
为 Todo List 添加计数