表单控件在实际开发中使用非常频繁,我们做的各种应用或多或少会需要获取用户提交的信息。针对这种场景,Vue 提供了
v-model
指令帮助我们进行快速的处理表单元素的数据更新。除此之外,我们还会以一个 Todo List 的功能进行 Vue 对数组更新检测的补充说明。
你可以用
v-model
指令在表单、
及
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但
v-model
本质上不过是语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
value
property 和 input
事件;checked
property 和 change
事件;value
作为 prop 并将 change
作为事件。<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}p>
<span>Multiline message is:span>
<p style="white-space: pre-line;">{{ message }}p>
<br>
<textarea v-model="message" placeholder="add multiple lines">textarea>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}label>
<br>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jacklabel>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">Johnlabel>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mikelabel>
<br>
<span>Checked names: {{ checkedNames }}span>
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">Onelabel>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Twolabel>
<br>
<span>Picked: {{ picked }}span>
<select v-model="selected">
<option disabled value="">请选择option>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>Selected: {{ selected }}span>
如果
v-model
表达式的初始值未能匹配任何选项元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
针对使用
元素进行多选的场景,原生
的多选操作不够友好(要按住Ctrl),建议使用
div + li
来模拟实现,在这不再演示。
对于单选按钮,复选框及选择框的选项,
v-model
绑定的值通常是静态字符串 (对于复选框也可以是布尔值),但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用v-bind
实现,并且这个 property 的值可以不是字符串。
<input type="radio" v-model="pick" id="one" :value="toggle">
<label for="one">是label>
<input type="radio" v-model="pick" id="two" :value="!toggle">
<label for="two">否label>
<br>
<select v-model="selected">
<option v-for="item in SelectableList" :key="item.id" :value="item.id">{{ item.name }}option>
select>
export default {
data() {
return {
pick: true,
toggle: true,
selected: 1,
SelectableList: [{
id: 1,
name: '张三'
},{
id: 2,
name: '李四'
}]
}
}
}
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如
filter()
、concat()
和slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组,你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
现在,我们以一个 Todo List 的例子进行部分变更方法的演示。这个待办事项表应具有以下功能:新增待办事项、标记已完成事项、列表过滤显示、删除事项、置顶重要事项等功能。
我们先实现一个基础版本的 Todo List,即
双向绑定
输入框、监听
添加按钮和用v-for
渲染待办事项列表:
<label for="inp">事项内容:label>
<input type="text" id="inp" v-model="inpVal">
<button @click="addItem">新增button>
<ul>
<li v-for="(item, index) in todoList" :key="item.time">
<input type="checkbox" id="checkbox" v-model="item.done">
<p>{{ index + 1 }}、p>
<p>{{ item.content }}p>
<p class="time">{{ item.timeStr }}p>
li>
ul>
export default {
data() {
return {
inpVal: '',
todoList: []
}
},
methods: {
addItem() {
const content = this.inpVal.trim()
if(content) {
const now = new Date()
this.todoList.push({
content,
done: false,
timeStr: now.toLocaleString(),
time: now.getTime()
})
this.inpVal = ''
} else {
alert('请输入事项内容')
}
}
}
}
// 加点样式免得太过难看
ul{
list-style: none;
}
ul>li{
display: flex;
align-items: center;
}
.time{
margin-left: 10px;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
color: #fff;
background-color: rgb(57, 217, 235);
}
我们循环渲染
todoList
,而todoList
的数据来自this.todoList.push(...)
的添加。现在我们体验下这个例子:
现在我们要加一个过滤功能,即切换显示全部事项和未完成的事项。我们先添加个计算属性
filteredList
替换todoList
来作为我们循环的数组,并在 data 中添加showIncomplete
来作为过滤的条件变量:
<label for="inp">事项内容:label>
<input type="text" id="inp" v-model="inpVal">
<button @click="addItem">新增button>
<button @click="showIncomplete = !showIncomplete">{{showIncomplete?'显示全部':'仅显示未完成'}}button>
<ul>
<li v-for="(item, index) in filteredList" :key="item.time">
<input type="checkbox" id="checkbox" v-model="item.done">
<p>{{ index + 1 }}、p>
<p>{{ item.content }}p>
<p class="time">{{ item.timeStr }}p>
li>
ul>
computed: {
filteredList() {
return this.todoList.filter(item => !(this.showIncomplete && item.done))
}
}
现在我们给它添加一个置顶功能:
<li v-for="(item, index) in filteredList" :key="item.time">
<input type="checkbox" id="checkbox" v-model="item.done">
<p>{{ index + 1 }}、p>
<p>{{ item.content }}p>
<p class="time">{{ item.timeStr }}p>
<button v-show="index !== 0" @click="topItem(item)">置顶button>
li>
// methods 中添加 topItem 方法
topItem(para) {
const index = this.todoList.findIndex(item => item.time === para.time)
this.todoList.splice(index, 1)
this.todoList.unshift(para)
}
最后一步,加个删除的功能:
<li v-for="(item, index) in filteredList" :key="item.time">
<input type="checkbox" id="checkbox" v-model="item.done">
<p>{{ index + 1 }}、p>
<p>{{ item.content }}p>
<p class="time">{{ item.timeStr }}p>
<button v-show="index !== 0" @click="topItem(item)">置顶button>
<button @click="deleteItem(item)">删除button>
li>
// 添加 deleteItem 方法,并简化 topItem
topItem(para) {
this.deleteItem(para)
this.todoList.unshift(para)
},
deleteItem(para) {
const index = this.todoList.findIndex(item => item.time === para.time)
this.todoList.splice(index, 1)
}
通过这个Todo List的演示,综合的回顾了我们之前学习的知识点,也体验了 Vue 对数组的更新检测,其他没有演示到的方法就请自行编码体验。
本章我们介绍了 Vue 中的表单输入绑定以及 Vue 的数组更新检测,并以一个完成的 Todo List 功能进行了知识点的串联。那么之后,我们会更深入的说明 Vue 的样式绑定以及介绍能有效提高我们编码效率的 Sass。