1 列表渲染
1.1 在 v-for 里使用数组
v-for 指令可以实现基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
- 在 v-for 块中,我们可以访问所有父作用域的
property
- 第一个参数
item
则是被迭代的数组元素的别名。 - 第二个参数,即当前项的索引
index
,是可选的。
html
<template>
<view>
<view v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</view>
</view>
</template>
<script>
export default {
data() {
return {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
}
}
</script>

1.2 在 v-for 里使用对象
你也可以用 v-for 来遍历一个对象的 property
。
- 第一个参数
value
是被迭代的对象元素的属性值。 - 第二个参数为
property
名称 (也就是键名)。 - 第三个参数作为索引。
html
<template>
<view>
<view v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</view>
</view>
</template>
<script>
export default {
data() {
return {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2021-05-10'
}
}
}
}
</script>

在遍历对象时,会按 Object.keys()
的结果遍历,但是不能保证它在不同 JavaScript
引擎下的结果都一致。
1.3 列表渲染分组
类似于 v-if
,你也可以利用带有 v-for
的 template
来循环渲染一段包含多个元素的内容。比如:
html
<template v-for="item in items">
<view>{{ item.message }}</view>
<view class="divider" role="presentation"></view>
</template>
1.4 维护状态
当 Vue
正在更新使用 v-for
渲染的元素列表时,它默认使用"就地更新"的策略。如果数据项的顺序被改变,Vue
将不会移动 DOM
元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出 。为了给 Vue
一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
attribute:
html
<view v-for="item in items" :key="item.id">
<!-- content -->
</view>
建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
- 如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
- 而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除/销毁 key 不存在的元素。
- 有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。如不提供 :key,会报一个 warning
, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。
html
<template>
<view>
<!-- array 中 item 的某个 property -->
<view v-for="(item,index) in objectArray" :key="item.id">
{{index +':'+ item.name}}
</view>
<!-- item 本身是一个唯一的字符串或者数字时,可以使用 item 本身 -->
<view v-for="(item,index) in stringArray" :key="item">
{{index +':'+ item}}
</view>
</view>
</template>
<script>
export default {
data () {
return {
objectArray:[{
id:0,
name:'li ming'
},{
id:1,
name:'wang peng'
}],
stringArray:['a','b','c']
}
}
}
</script>
1.5 注意事项
小程序端数据为差量更新方式,由于小程序不支持删除对象属性,使用的设置值为 null 的方式替代,导致遍历时可能出现不符合预期的情况,需要自行过滤一下值为 null 的数据
1.6 结合 <template v-for>
在Vue3
中,key
则应该被设置在 <template>
标签上
html
<template v-for="item in list" :key="item.id">
<view>...</view>
<text>...</text>
</template>
类似地,当使用 <template v-for>
时存在使用 v-if
的子节点,key
应改为设置在 <template>
标签上。
html
<template v-for="item in list" :key="item.id">
<view v-if="item.isVisible">...</view>
<view v-else>...</view>
</template>
1.7 在组件上使用 v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
。
html
<my-component v-for="item in items" :key="item.id"></my-component>
当在组件上使用 v-for 时,key是必须的。
1.8 v-for 与 v-if 一同使用
当它们处于同一节点,v-if
的优先级比 v-for
更高 ,这意味着 v-if
将没有权限访问 v-for
里的变量:
html
<!-- 这将引发错误,因为未在实例上定义属性"todo" -->
<view v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</view>
可以把 v-for
移动到 template
标签中来修正:
html
<template v-for="todo in todos">
<view v-if="!todo.isComplete">
{{ todo }}
</view>
</template>
2 事件处理
2.1 监听事件
我们可以使用 v-on
指令 (通常缩写为 @ 符号,下文简称为:@事件) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript
。用法为 v-on:click="methodName"
或使用快捷方式 @click="methodName"
(uni-app里一般都使用@缩写方式)指令的值,字符串里直接写js。比如下面的counter += 1
就是一段js。
html
<template>
<view>
<button @click="counter += 1">Add 1</button>
<text>The button above has been clicked {{ counter }} times.</text>
</view>
</template>
<script>
export default {
data() {
return {
counter:0
}
}
}
</script>
2.2 事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript
代码写在组件属性值里是不可行的。因此@事件还可以接收一个需要调用的方法名称。
html
<template>
<view>
<!-- `greet` 是在下面定义的方法名 -->
<button @click="greet">Greet</button>
</view>
</template>
<script>
export default {
data() {
return {
name: 'Vue.js'
}
},
// 在 `methods` 对象中定义方法
methods: {
greet(event){
// `event` 是原生 DOM 事件
console.log(event);
uni.showToast({
title: 'Hello ' + this.name + '!'
});
}
}
}
</script>
2.3 内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript
语句中调用方法:
html
<template>
<view>
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button>
</view>
</template>
<script>
export default {
methods: {
say(message) {
uni.showToast({
title: message
});
}
}
}
</script>
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
html
<template>
<view>
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
</view>
</template>
<script>
export default {
methods: {
warn(message, event) {
// 现在我们可以访问原生事件对象
if (event) {
//可访问 event.target等原生事件对象
console.log("event: ",event);
}
uni.showToast({
title: message
});
}
}
}
</script>
2.4 多事件处理器
事件处理程序中可以有多个方法,这些方法由逗号运算符分隔:
html
<template>
<view>
<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event); two($event)">
Submit
</button>
</view>
</template>
<script>
export default {
methods: {
one(event) {
// first handler logic...
console.log("event1: ",event);
},
two(event) {
// second handler logic...
console.log("event2: ",event);
}
}
}
</script>
2.5 事件修饰符
修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉 @事件对于触发的事件调用 event.preventDefault()。
@事件(v-on)提供了事件修饰符:
.stop
: 各平台均支持, 使用时会阻止事件冒泡,在非 H5 端同时也会阻止事件的默认行为.prevent
: 仅在 H5 平台支持.capture
: 仅在 H5 平台支持.self
: 仅在 H5 平台支持.once
: 仅在 H5 平台支持.passive
: 仅在 H5 平台支持
html
<!-- 阻止单击事件继续传播 -->
<view @click.stop="doThis"></view>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self
会阻止所有的点击,而 @click.self.prevent
只会阻止对元素自身的点击。
- 为兼容各端,事件需使用 @ 的方式绑定,请勿使用小程序端的
bind
和catch
进行事件绑定;也不能在 JS 中使用event.preventDefault()
和event.stopPropagation()
方法。 - 若需要禁止蒙版下的页面滚动,可使用
@touchmove.stop.prevent="moveHandle"
,moveHandle
可以用来处理touchmove
的事件,也可以是一个空函数。
html
<view class="mask" @touchmove.stop.prevent="moveHandle"></view>
- 按键修饰符:
uni-app
运行在手机端,没有键盘事件,所以不支持按键修饰符。
使用 v-on 或 @ 有几个好处
- 扫一眼
template
模板便能轻松定位在JavaScript
代码里对应的方法。 - 因为你无须在
JavaScript
里手动绑定事件,你的ViewModel
代码可以是非常纯粹的逻辑,和DOM
完全解耦,更易于测试。 - 当一个
ViewModel
被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
2.6 事件映射表
html
// 事件映射表,左侧为 WEB 事件,右侧为 ``uni-app`` 对应事件
{
click: 'tap',
touchstart: 'touchstart',
touchmove: 'touchmove',
touchcancel: 'touchcancel',
touchend: 'touchend',
tap: 'tap',
longtap: 'longtap', //推荐使用longpress代替
input: 'input',
change: 'change',
submit: 'submit',
blur: 'blur',
focus: 'focus',
reset: 'reset',
confirm: 'confirm',
columnchange: 'columnchange',
linechange: 'linechange',
error: 'error',
scrolltoupper: 'scrolltoupper',
scrolltolower: 'scrolltolower',
scroll: 'scroll'
}
3 表单输入绑定
3.1 v-model
你可以用 v-model 指令在表单 input
、textarea
及 select
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model 会忽略所有表单元素的 value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
在下面的示例中,输入框通过v-model
绑定了message
,用户在输入框里输入内容时,这个内容会实时赋值给message
。当然在代码里为message
赋值也会实时同步到界面上input里。这就是双向绑定。
html
<template>
<view>
<input v-model="message" placeholder="edit me">
<text>Message is: {{ message }}</text>
</view>
</template>
<script>
export default {
data() {
return {
message:""
}
}
}
</script>
3.2 uni-app表单组件
- H5 的
select
标签用picker
组件进行代替
html
<template>
<view>
<picker @change="bindPickerChange" :value="index" :range="array">
<view class="picker">
当前选择:{{array[index]}}
</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
index: 0,
array: ['A', 'B', 'C']
}
},
methods: {
bindPickerChange(e) {
console.log(e)
this.index = e.detail.value
}
}
}
</script>
- 表单元素
radio
用radio-group
组件进行代替
html
<template>
<view>
<radio-group class="radio-group" @change="radioChange">
<label class="radio" v-for="(item, index) in items" :key="item.name">
<radio :value="item.name" :checked="item.checked" /> {{item.value}}
</label>
</radio-group>
</view>
</template>
<script>
export default {
data() {
return {
items: [{
name: 'USA',
value: '美国'
},
{
name: 'CHN',
value: '中国',
checked: 'true'
},
{
name: 'BRA',
value: '巴西'
},
{
name: 'JPN',
value: '日本'
},
{
name: 'ENG',
value: '英国'
},
{
name: 'TUR',
value: '法国'
}
]
}
},
methods: {
radioChange(e) {
console.log('radio发生change事件,携带value值为:', e.detail.value)
}
}
}
</script>
4 计算属性和侦听器
4.1 计算属性computed
每一个计算属性都包含一个 getter
函数和一个 setter
函数 ,默认是利用 getter
函数来读取。所有 getter
和 setter
函数的 this
上下文自动地绑定为 Vue 实例。
4.1.1 计算属性的 getter
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如,有一个嵌套数组对象:
html
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
}
我们想根据 author 是否已经有一些书来显示不同的消息,可以使用模板内的表达式
html
<view>
<view>Has published books:</view>
<view>{{ author.books.length > 0 ? 'Yes' : 'No' }}</view>
</view>
此时,模板不再是简单的和声明性的。你必须先看一下它,然后才能意识到它执行的计算取决于 author.books。如果要在模板中多次包含此计算,则问题会变得更糟。所以,对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性。
html
<template>
<view>
<view>OHas published books:</view>
<view>{{ publishedBooksMessage }}</view>
</view>
</template>
<script>
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 计算属性的 getter
publishedBooksMessage() {
// `this` points to the vm instance
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
</script>
这里声明了一个计算属性 publishedBooksMessage
。尝试更改应用程序 data
中 books
数组的值,你将看到 publishedBooksMessage
如何相应地更改。
你可以像普通属性一样将数据绑定到模板中的计算属性。Vue 知道 publishedBookMessage
依赖于 author.books
,因此当 author.books
发生改变时,所有依赖 publishedBookMessage
绑定也会更新。而且最妙的是我们已经声明的方式创建了这个依赖关系:计算属性的 getter 函数没有副作用,这使得更易于测试和理解。
计算属性还可以依赖多个 Vue 实例的数据,只要其中任一数据变化,计算属性就会重新执行,视图也会更新。
4.1.2 计算属性的 setter
计算属性默认只有 getter
,不过在需要时你也可以提供一个 setter
, 当手动修改计算属性的值时,就会触发 setter
函数,执行一些自定义的操作。
html
<template>
<view>
<view>{{ fullName }}</view>
</view>
</template>
<script>
export default {
data() {
return {
firstName: 'Foo',
lastName: 'Bar'
}
},
computed: {
fullName: {
// getter
get(){
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue){
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
}
</script>
现在再运行 fullName = 'John Doe'
时,setter
会被调用,firstName
和 lastName
也会相应地被更新。
getter与setter区别
- get:通过设置get方法可以得到fullName的新值。
- set:通过set的方法,设置一个值(newValue)来改变fullName相关联的值,引起fullName重新的计算,相应的页面上fullName也会发生改变成新的内容。
4.2 计算属性缓存 vs 方法
我们可以通过在表达式中调用方法来达到同样的效果:
html
<template>
<view>
<view>{{ calculateBooksMessage() }}</view>
</view>
</template>
<script>
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
methods: {
calculateBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
</script>
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的反应依赖关系缓存的。
计算属性只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 author.books
还没有发生改变,多次访问 publishedBookMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。这也同样意味着下面的计算属性将不再更新,因为 Date.now ()
不是响应式依赖:
html
computed: {
now(){
return Date.now()
}
}
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。我们为什么需要缓存?
假设我们有一个性能开销比较大的计算属性 list
,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 list
。如果没有缓存,我们将不可避免的多次执行 list
的 getter
!如果你不希望有缓存,请用 method
来替代。
4.3 侦听器watch
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue
通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
当你有一些数据需要随着其它数据变动而变动时,就可以使用Watch
来监听他们之间的变化。一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 Vue
实例将会在实例化时调用 $watch()
,遍历 watch
对象的每一个 property
。
4.3.1 监听变量的值变化
html
<template>
<view>
<input type="number" v-model="a" style="border: red solid 1px;" />
<input type="number" v-model="b" style="border: red solid 1px;" />
<view>总和:{{sum}}</view>
<button type="default" @click="add">求和</button>
</view>
</template>
<script>
export default {
data() {
return {
a:1,
b:1,
sum: ""
}
},
watch: {
//使用watch来响应数据的变化,第一个参数为newVal新值,第二个参数oldVal为旧值
a: function(newVal, oldVal) {
console.log("a--newVal: ", newVal, "a--oldVal: ",oldVal);
},
b: function(newVal, oldVal) {
console.log("b--newVal: ", newVal, "b--oldVal: ",oldVal);
}
},
methods: {
add() {
this.sum = parseInt(this.a) + parseInt(this.b)
}
}
}
</script>
以上示例有个问题,就是页面刚加载时,因为没有变化,所以不会执行。下面用immediate
来解决。
4.3.2 选项:immediate
在选项参数中指定 immediate: true
将立即以表达式的当前值触发回调:watch
方法默认就是handler
,而当immediate:true
时,就会先执行handler
方法。
html
<template>
<view>
<input type="number" v-model="a" style="border: red solid 1px;" />
<input type="number" v-model="b" style="border: red solid 1px;" />
<view>总和:{{sum}}</view>
<button type="default" @click="add">求和</button>
</view>
</template>
<script>
export default {
data() {
return {
a:1,
b:1,
sum: ""
}
},
watch: {
a: {
handler(newVal, oldVal) {
console.log("a------: ", newVal, oldVal);
},
immediate: true//初始化绑定时就会执行handler方法
},
b: {
handler(newVal, oldVal) {
console.log("b------: ", newVal, oldVal);
},
immediate: true//初始化绑定时就会执行handler方法
}
},
methods: {
add() {
this.sum = parseInt(this.a) + parseInt(this.b)
}
}
}
</script>
4.3.3 选项:deep
为了发现对象内部值的变化,可以在选项参数中指定 deep: true
。深度监听一个对象整体的变化(即监听对象所有属性值的变化),注意监听数组的变更不需要这么做。
html
<template>
<view>
<input type="number" v-model="obj.a" style="border: red solid 1px;" />
<input type="number" v-model="obj.b" style="border: red solid 1px;" />
<view>总和:{{sum}}</view>
<button type="default" @click="add">求和</button>
</view>
</template>
<script>
export default {
data() {
return {
obj: {
a: 1,
b: 1,
},
sum:""
}
},
watch: {
obj: {
handler(newVal, oldVal) {
console.log('obj-newVal:' + JSON.stringify(newVal), 'obj-oldVal:' + JSON.stringify(oldVal), );
},
deep: true//对象中任一属性值发生变化,都会触发handler方法
}
},
methods: {
add() {
this.sum = parseInt(this.obj.a) + parseInt(this.obj.b)
}
}
}
</script>
4.3.4 监听对象中单个属性
如果不想监听 obj
中其他值,只想监听 obj.a
的值的变化,可以写成字符串形式监听。
html
export default {
data() {
return {
obj: {
a: 1,
b: 1,
}
}
},
watch: {
"obj.a": {//监听obj对象中的单个属性值的变化
handler(newVal, oldVal) {
console.log('obj-newVal:' + newVal, 'obj-oldVal:' + oldVal);
}
}
}
}
4.4 计算属性 vs 侦听属性
Vue
提供了一种更通用的方式来观察和响应 Vue
实例上的数据变动:侦听属性 。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。
html
export default {
data() {
return {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
}
},
watch: {
firstName: function(val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function(val) {
this.fullName = this.firstName + ' ' + val
}
}
}
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
html
export default {
data() {
return {
firstName: 'Foo',
lastName: 'Bar'
}
},
computed: {
fullName(){
return this.firstName + ' ' + this.lastName
}
}
}