Vue3 学习 02——组件属性,“控制流”

之前学过,使用v-bind指令,简写为:,去绑定状态到属性,实际上这玩意儿玩法还是挺多的,如 class、style 也是用它来定义,而且有更多特殊处理来帮助编码。

关于 attribute

如果绑定的状态为 null 或 undefined,则不绑定该 attribute,下面的例子证明这一点——输入框为空时(为空字符串),文字是黑色,输入框有内容时(id 非空),文字是蓝色,输入 white 时,文字是白色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup lang="ts">
import { ref } from 'vue';

const color = ref<string>()

</script>

<template>
<input type="text" v-model="color" />
<span :id="color === '' ? undefined : color">Hello, Happy World!</span>
</template>

<style scoped>
span {
color: black;
}
span[id] {
color: blue;
}
#white {
color: white;
}
</style>

如果不给定要绑定的属性名,则可以传入一个 js 对象,同时绑定多个属性名,下面的例子同时绑定了 id 和 style,这里也注意到 Vue 对 style 做过特殊处理,能直接传对象作为 style。

1
2
3
4
5
6
7
8
9
10
11
12
<script setup lang="ts">
const attrs = {
id: 'white',
style: {
'fontSize': '100px'
}
}
</script>

<template>
<span :="attrs">Hello, Happy World!</span>
</template>

要绑定到的参数名也是可以使用 js 变量指定的,该变量为 null 表示不绑定,它的语法类似对象结构语法中的[x]: y这样。但这里要特别注意——Vue 中的 html 是要符合 HTML 标准的,也就是说:[x]这部分要满足 HTML 标准——该标准不允许出现空格,引号(而且如果直接在 html 中使用 vue,这里的大写字母还会被转换成小写),因此这里要避免使用 js 表达式

下面展示了这一语法,输入 disabled 能发现第二个输入会置灰,注意像disabled这样的布尔属性也得给它 value:

1
2
3
4
5
6
7
8
9
<script setup lang="ts">
import { ref } from 'vue';
const attrName = ref('')
</script>
<template>
<input v-model="attrName" />
<input :[attrName]="true" />
</template>

对于修饰符部分(即@click.prevent里的prevent),修饰符也是可以传多个的,直接链式往后加即可,如@click.stop.prevent

对于 class,允许传入数组、对象;对于数组,将元素中的非空的字符串拼接到 class 中,对于对象,找到值是真值的键拼接到 class 中,下面的例子展示了这个特性的实践法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">
import { ref } from 'vue';

const isBlue = ref(false)
const isBold = ref(false)
</script>

<template>
<input type="checkbox" v-model="isBlue" id="blue" /> <label for="blue">blue</label>
<input type="checkbox" v-model="isBold" id="bold" /> <label for="bold">bold</label>
<span :class="['big', null, isBold && ['bold', null], [{blue: isBlue}]]">Hello, Happy World!</span>
</template>

<style lang="css" scoped>
.big {
font-size: 100px;
}
.blue {
color: blue;
}
.bold {
font-weight: bolder;
}
</style>

对于自定义的组件如果暴露出 class,Vue 会自动将父组件传入的 class 和该组件的 class 混合。这个具体在学组件时再研究。

对于 style,样式名既能够使用 HTML 标准的中划线分割,也可以使用小驼峰,小驼峰方便在 js 代码中使用

“控制流”

Vue 不使用 js 去控制组件渲染逻辑,而是提供了相应内置指令去进行渲染,常用的包括条件渲染v-if,迭代渲染v-for,以及是否显示v-show

条件渲染 v-if

顾名思义,有v-if,就有v-elsev-else-if,下面是示例。

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import { ref } from 'vue';
const text = ref(1)
</script>
<template>
<input type="number" v-model="text" />
<p v-if="text % 15 == 0">Fizz Buzz</p>
<p v-else-if="text % 5 == 0">Buzz</p>
<p v-else-if="text % 3 == 0">Fizz</p>
<p v-else>sad</p>
</template>

这个可能会让人感觉不太习惯……这里的 v-if、v-else 指令不是包裹着要处理的元素(比如就像 mybatis 的 if 那样),而是直接写在元素上。

这时候就要问了——如果我要同时控制多个元素的显示怎么办?使用template标签,它类似 React 的<>标签,不会对应实际的 DOM 标签,只是用于分组。

1
2
3
<template v-if="...">
...
</template>

可见性 v-show

v-show指令控制标签是否显示,它和v-if指令的区别在于,**v-show总是渲染生成 DOM 元素,仅通过 display 属性去控制是否显示**,而v-if在不满足条件时是不会将元素插入到 DOM 中的。

如果元素要频繁地控制是否显示,使用v-show是更合适的。

迭代渲染 v-for

v-for可能是最让人迷惑的内置指令——它不是像 for 语句那样外面一个 for,要迭代生成的元素在语句块里面,而是直接写在要迭代生成的元素上,下面是一个 todo 清单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup lang="ts">
import { ref } from 'vue';

const todos = ref([] as string[])
const input = ref('')

function appendTodo() {
if (input.value) {
todos.value.push(input.value)
input.value = ''
}
}

</script>
<template>
<input type="text" v-model.trim="input" />
<button @click="appendTodo">add</button>
<ol>
<ul v-for="todo in todos" :key="todo">{{ todo }}</ul>
</ol>
</template>

这里,todos 列表中有几个元素,就会生成多少个 ul……Vue 这么设计估计也是有自己的考量,不深究。

注意v-for指令的语法,这里写todo in todos,让人想到 for 迭代器的语法,但显然不一样——如果是 js 中,这里的 in 会导致 todo 是数组的下标,即 0,1,2 等。也就是说,这里也得特殊对待。实际上,对于v-for的表达式:

  1. 这里可以用 of 替代 in,功能完全一致,只不过 of 更符合 js 的 for 迭代器的一般认知
  2. 这里的左侧可以接受两个参数,写作 (todo, index) in todos,这时候的 index 就为下标
  3. 左侧可以进行解构,如({name, desc, deadline}, index) in todos这样
  4. 右侧可以是数组也可以是对象,第一个参数总是 value,对数组,第二个参数是 index,对对象,第二个参数是 key,第三个参数是 index
  5. 右侧也可以直接是数字,比如n in 10,此时 n 是range(1, 11)。注意这里是从 1 开始的。

v-for有几个需要特别注意的点:

  1. 尽量(总是)使用:key去绑定一个 key 让 Vue 能够唯一地标识各个元素;这让 Vue 能够就地更新每个元素,一般来说性能会更好,且避免无谓的重新渲染
  2. v-for不要和v-if同时使用——v-if先执行,因此v-if无法访问v-for中定义的变量

有两种情况会让人将v-forv-if同时使用——要控制整个列表是否渲染,以及控制特定列表项是否渲染。对于前者,将 v-if 挪到一个“wrapper”上——用 template 包裹这个v-forv-if标在 template 上;对于后者,在 js 中使用计算属性提前做筛选,或者把v-for挪到一个“wrapper”上——在template上标注v-forv-if标注在要渲染的列表项上。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 控制整个列表是否显示的情况 -->
<template v-if="showTodoList">
<li v-for="todo in todos">
{{ todo.name }}
</li>
</template>

<!-- 要筛选元素的情况 -->
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>