# 模板语法：指令与数据绑定

## 概述

在上一篇文章中，我们用 `ref` 和 `reactive` 定义了响应式数据。

现在的问题是：数据怎么显示到页面上？用户的操作怎么触发数据变化？

Vue 3 的模板语法解决的就是这两个问题。我们用一条主线串起来：

```
数据显示 {{ }} → 属性绑定 : → 条件显示 v-if → 列表循环 v-for → 用户点击 @ → 表单输入 v-model
```

## 文本插值

双大括号可以把数据插入到模板中：

```vue
<script setup lang="ts">
const name = '张三'
const age = 25
</script>

<template>
  <p>姓名：{{ name }}</p>
  <p>年龄：{{ age }}</p>
  <p>表达式：{{ age + 1 }}</p>
</template>
```

大括号里可以写任意合法的 JavaScript 表达式。

但不要写完整的语句，比如 `if` 或 `for`。

## 属性绑定

### 基本用法

用 `v-bind` 可以把 HTML 属性绑定到变量：

```vue
<script setup lang="ts">
const id = 'container'
const url = 'https://example.com'
</script>

<template>
  <div v-bind:id="id"></div>
  <a v-bind:href="url">链接</a>
</template>
```

> [!tip] 技巧
> `v-bind` 可以简写为 `:`。这是最常用的写法。

```vue
<template>
  <div :id="id"></div>
  <a :href="url">链接</a>
</template>
```

不加 `:` 的属性是普通字符串，加了 `:` 的属性会作为变量或表达式解析。

### class 绑定

`:class` 可以和普通 `class` 共存，Vue 会自动合并：

```vue
<script setup lang="ts">
const isActive = ref(true)
const hasError = ref(false)
</script>

<template>
  <div
    class="base-style"
    :class="{ active: isActive, error: hasError }"
  >
    内容
  </div>
</template>
```

`:class` 可以传入对象、数组或组合：

```vue
<script setup lang="ts">
const classObj = ref({
  active: true,
  'text-white': false,
})

const classArr = ref(['px-4', 'py-2'])
</script>

<template>
  <div :class="classObj"></div>
  <div :class="classArr"></div>
  <div :class="[classObj, classArr]"></div>
</template>
```

### style 绑定

`:style` 的用法和 `:class` 类似：

```vue
<script setup lang="ts">
const styleObj = ref({
  color: 'red',
  fontSize: '30px',
})
</script>

<template>
  <div :style="styleObj">红色大字</div>
</template>
```

### 绑定多个属性

使用无参数的 `v-bind` 一次传入多个属性：

```vue
<script setup lang="ts">
const attr = ref({
  id: 'container',
  class: 'wrapper',
  style: 'background-color: green',
})
</script>

<template>
  <div v-bind="attr"></div>
</template>
```

## 条件渲染

### `v-if` 系列

`v-if`、`v-else-if`、`v-else` 根据条件决定是否渲染元素：

```vue
<script setup lang="ts">
const score = ref(85)
</script>

<template>
  <p v-if="score >= 90">优秀</p>
  <p v-else-if="score >= 60">及格</p>
  <p v-else>不及格</p>
</template>
```

条件为 `false` 时，元素根本不会出现在 DOM 中。

### `v-show`

`v-show` 也控制显示隐藏，但元素始终存在：

```vue
<template>
  <p v-show="isVisible">我只是一直隐藏了</p>
</template>
```

### `v-if` vs `v-show`

| 对比   | `v-if`     | `v-show`                  |
| ---- | ---------- | ------------------------- |
| 渲染方式 | 条件假时不渲染    | 始终渲染，用 `display: none` 隐藏 |
| 切换开销 | 每次切换都创建/销毁 | 只切换 CSS                   |
| 初始开销 | 条件假时无开销    | 始终有初始渲染开销                 |
| 适用场景 | 切换不频繁      | 频繁切换                      |

> [!tip] 技巧
> 如果条件很少变化，用 `v-if`。如果条件频繁切换（如标签页），用 `v-show`。

## 列表渲染

用 `v-for` 遍历数组或对象：

```vue
<script setup lang="ts">
const items = ref(['苹果', '香蕉', '橘子'])
const user = ref({
  姓名: '张三',
  年龄: 18,
  性别: '男',
})
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      {{ index }}: {{ item }}
    </li>
  </ul>

  <ul>
    <li v-for="(value, key) in user" :key="key">
      {{ key }}: {{ value }}
    </li>
  </ul>
</template>
```

> [!info] 说明
> `:key` 是 Vue 用来追踪每个元素的标识。遍历时建议提供一个唯一值作为 `key`，能提升渲染性能。

## 事件处理

### 基本用法

用 `v-on` 监听事件，简写为 `@`：

```vue
<script setup lang="ts">
const count = ref(0)

function handleClick() {
  count.value++
}
</script>

<template>
  <p>点击次数：{{ count }}</p>
  <button @click="handleClick">点击</button>
</template>
```

也可以直接写内联表达式：

```vue
<template>
  <button @click="count++">点击</button>
</template>
```

### 事件修饰符

DOM 事件会经历三个阶段：捕获 → 目标 → 冒泡。

Vue 提供了事件修饰符，简化事件细节的处理：

| 修饰符 | 作用 | 使用场景 |
|--------|------|----------|
| `.stop` | 阻止事件冒泡 | 子元素点击不想触发父元素 |
| `.prevent` | 阻止默认行为 | 阻止表单提交、链接跳转 |
| `.self` | 只有事件源是自己时才触发 | 避免子元素误触 |
| `.capture` | 在捕获阶段触发 | 提前拦截事件 |
| `.once` | 只触发一次 | 一次性操作，防止重复提交 |
| `.passive` | 不调用 `preventDefault` | 滚动性能优化 |

修饰符可以串联：

```vue
<template>
  <!-- 阻止冒泡 -->
  <div @click="parentClick">
    <button @click.stop="childClick">点我</button>
  </div>

  <!-- 阻止表单刷新 -->
  <form @submit.prevent="onSubmit">
    <button type="submit">提交</button>
  </form>

  <!-- 阻止右键菜单 -->
  <div @click.right.prevent="openMenu">右键菜单</div>
</template>
```

### 按键修饰符

按键修饰符处理键盘和鼠标操作：

```vue
<template>
  <!-- 回车键提交 -->
  <input @keyup.enter="submit" />

  <!-- Ctrl + Enter -->
  <textarea @keyup.ctrl.enter="send"></textarea>

  <!-- 精确按键组合 -->
  <button @click.ctrl.exact="onCtrlClick">Ctrl + 点击</button>
  <button @click.exact="onClick">仅点击，无修饰键</button>
</template>
```

## 表单绑定

### `v-model` 双向绑定

表单元素需要同时做两件事：把数据显示到输入框，用户输入时更新数据。

手动实现需要写两行代码：

```vue
<input :value="text" @input="event => text = event.target.value" />
```

Vue 提供了 `v-model` 简化成一行：

```vue
<input v-model="text" />
```

完整示例：

```vue
<script setup lang="ts">
const message = ref('')
</script>

<template>
  <p>你输入的内容：{{ message }}</p>
  <input v-model="message" placeholder="输入文字" />
</template>
```

### 修饰符

| 修饰符 | 作用 | 示例 |
|--------|------|------|
| `.number` | 自动转数字 | `<input v-model.number="age" />` |
| `.trim` | 去除首尾空格 | `<input v-model.trim="title" />` |
| `.lazy` | 失焦时同步，而非输入时 | `<input v-model.lazy="content" />` |

```vue
<template>
  <input v-model.number="age" type="number" />
  <input v-model.trim="username" />
</template>
```

---

模板语法是 Vue 3 中"**视图层**"的核心。你现在可以：

- 用 `{{ }}` 和 `:` 显示数据
- 用 `v-if` 和 `v-for` 控制显示结构
- 用 `@` 和 `v-model` 处理用户操作

但所有代码都写在一个文件里，代码会越来越长。

下一步：**怎么把页面拆成独立的小块来管理？**


---

> 作者: Aphros  
> URL: https://blog.papergate.top/posts/%E6%A8%A1%E6%9D%BF%E8%AF%AD%E6%B3%95%E6%8C%87%E4%BB%A4%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A/  

