Vue学习(五)列表渲染,维护状态,事件处理,表单输入绑定

列表渲染

我们可以用v-for指令基于一个数组来渲染一个列表,v-for指令需要使用item in items形式的特殊语法,其中items是源数据数组,而item则是被迭代的数组元素的别名

<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

v-for块中,我们可以访问所有父作用域的property,v-for还支持一个可选的第二个参数,即当前项的索引。

<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
var example2 = new Vue({
  el: '#example-2',
  data: {
    parentMessage: 'Parent',
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRcjaVlL-1626616934057)(C:\Users\VV\AppData\Roaming\Typora\typora-user-images\image-20210718104909417.png)]

你也可以用 of 替代 in 作为分隔符

<div v-for="item of items"></div>

v-for里使用对象

你也可以用 v-for 来遍历一个对象的 property。

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>

new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-el9Y5EUJ-1626616934060)(C:\Users\VV\AppData\Roaming\Typora\typora-user-images\image-20210718105108415.png)]

你也可以提供第二个的参数为 property 名称 (也就是键名):

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dP1D3Eyx-1626616934063)(C:\Users\VV\AppData\Roaming\Typora\typora-user-images\image-20210718105140849.png)]

还可以用第三个参数作为索引:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tor7n5eP-1626616934064)(C:\Users\VV\AppData\Roaming\Typora\typora-user-images\image-20210718105206968.png)]

维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

<div v-for="item in items" v-bind:key="item.id">
  <!-- 内容 -->
</div>

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

数组更新检测

变更方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })

替换数组

变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()concat()slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。

例如:

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:

<ul v-for="set in sets">
  <li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
  sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

v-for 里使用值范围

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

<template> 上使用v-for

类似于 v-if,你也可以利用带有 v-for<template> 来循环渲染一段包含多个元素的内容。比如:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-forv-if 一同使用

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

上面的代码将只渲染未完成的 todo。

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 [`) 上。如:

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

在组件上使用 v-for

在自定义组件上,你可以像在任何普通元素上一样使用 v-for

<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的。

然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 prop:

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。

下面是一个简单的 todo 列表的完整例子:

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input
      v-model="newTodoText"
      id="new-todo"
      placeholder="E.g. Feed the cat"
    >
    <button>Add</button>
  </form>
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>

注意这里的 is="todo-item" attribute。这种做法在使用 DOM 模板时是十分必要的,因为在 <ul> 元素内只有 <li> 元素会被看作有效内容。这样做实现的效果与 <todo-item> 相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。

Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [
      {
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function () {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})

事件处理

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

示例:

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

<div id="example-2">
  <!-- `greet` 是在下面定义的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 对象中定义方法
  methods: {
    greet: function (event) {
      // `this` 在方法里指向当前 Vue 实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <button v-on:click="say('what')">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message) {
      alert(message)
    }
  }
})

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn: function (message, event) {
    // 现在我们可以访问原生事件对象
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。

Vue 还对应 addEventListener中的 passive 选项提供了 .passive 修饰符。

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能。

表单输入绑定

基础用法

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

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

文本

<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>

多个复选框,绑定到同一个数组:

<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>

new Vue({
  el: '...',
  data: {
    checkedNames: []
  }
})

单选按钮

<div id="example-4">
  <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>
</div>
new Vue({
  el: '#example-4',
  data: {
    picked: ''
  }
})

选择框

<div id="example-5">
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '...',
  data: {
    selected: ''
  }
})

多选时 (绑定到一个数组):

<div id="example-6">
  <select v-model="selected" multiple style="width: 50px;">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <br>
  <span>Selected: {{ selected }}</span>
</div>

new Vue({
  el: '#example-6',
  data: {
    selected: []
  }
})

值绑定

对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):

<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串。

复选框

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no"
>
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'

单选按钮

<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a

版权声明:本文为qq_45976392原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。