Vue 的表单输入绑定和数组更新检测(六)

前言

表单控件在实际开发中使用非常频繁,我们做的各种应用或多或少会需要获取用户提交的信息。针对这种场景,Vue 提供了v-model指令帮助我们进行快速的处理表单元素的数据更新。除此之外,我们还会以一个 Todo List 的功能进行 Vue 对数组更新检测的补充说明。


表单输入绑定

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

文本

<input v-model="message" placeholder="edit me">
<p>Message is: {
   
   { message }}</p>

20220317_155738.gif

多行文本

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{
   
   { message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

20220317_160327.gif

复选框

<!-- 单个复选框,绑定到布尔值 -->
<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">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {
   
   { checkedNames }}</span>

20220317_161503.gif

单选按钮

<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {
   
   { picked }}</span>

20220317_162006.gif

选择框

<select v-model="selected">
  <option disabled value="">请选择</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<span>Selected: {
   
   { selected }}</span>

20220317_171726.gif

如果 v-model 表达式的初始值未能匹配任何选项<select>元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

针对使用<select>元素进行多选的场景,原生<select>的多选操作不够友好(要按住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: '李四'
      }]
    }
  }
}

20220317_174653.gif

数组更新检测

变更方法和替换数组

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(...)的添加。现在我们体验下这个例子:

20220318_150750.gif

现在我们要加一个过滤功能,即切换显示全部事项和未完成的事项。我们先添加个计算属性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))
    }
}

20220318_153013.gif

现在我们给它添加一个置顶功能:

  <!-- 列表项添加置顶按钮 --> 
  <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)
}

20220318_155722.gif

最后一步,加个删除的功能:

  <!-- 列表项添加删除按钮 -->
  <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)
}

20220318_161253.gif

通过这个Todo List的演示,综合的回顾了我们之前学习的知识点,也体验了 Vue 对数组的更新检测,其他没有演示到的方法就请自行编码体验。


内容预告

本章我们介绍了 Vue 中的表单输入绑定以及 Vue 的数组更新检测,并以一个完成的 Todo List 功能进行了知识点的串联。那么之后,我们会更深入的说明 Vue 的样式绑定以及介绍能有效提高我们编码效率的 Sass。

猜你喜欢

转载自blog.csdn.net/d2235405415/article/details/123578867