Skip to content

22|Vue 条件渲染、列表渲染与表单

页面内容不总是固定的:根据权限显示不同菜单、根据状态显示不同标签、遍历数据生成表格行。Vue 提供 v-ifv-showv-for 三个核心指令处理这些动态渲染需求。表单处理则是前端与用户的直接交互入口,需要理解双向绑定和表单验证。

一、条件渲染

v-if

根据条件决定是否渲染元素:

vue
<template>
  <div v-if="isAdmin">管理员面板</div>
  <div v-else-if="isEditor">编辑面板</div>
  <div v-else>只读视图</div>
</template>

<script setup>
import { ref } from "vue";

const isAdmin = ref(false);
const isEditor = ref(true);
</script>

v-if 是"真正的"条件渲染——条件为 false 时,元素从 DOM 中移除;条件变为 true 时,重新创建并挂载。

v-show

vue
<div v-show="isVisible">条件内容</div>

v-show 通过 CSS display: none 切换可见性,元素始终存在于 DOM 中。

v-if vs v-show

v-ifv-show
切换开销高(创建/销毁 DOM)低(切换 CSS)
初始渲染条件为 false 时不渲染始终渲染
适用场景条件很少变化需要频繁切换

如果组件内部有昂贵的初始化逻辑(如发送请求、创建图表),用 v-if 避免不必要的开销;如果是标签页切换这种高频操作,用 v-show 更流畅。

在 template 上使用 v-if

template 标签本身不会渲染到 DOM,适合包裹多个元素:

vue
<template v-if="hasData">
  <h2>数据列表</h2>
  <table>...</table>
</template>

二、列表渲染

v-for

遍历数组渲染列表:

vue
<template>
  <ul>
    <li v-for="server in servers" :key="server.id">
      {{ server.hostname }} - {{ server.status }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from "vue";

const servers = ref([
  { id: 1, hostname: "web-01", status: "running" },
  { id: 2, hostname: "web-02", status: "stopped" },
]);
</script>

:key 是必须的,且必须是唯一标识。Vue 用 key 追踪每个节点的身份,在数据变化时高效复用或更新 DOM。不要用数组索引作为 key(除非列表只增不减且不会重排)。

获取索引

vue
<li v-for="(server, index) in servers" :key="server.id">
  {{ index + 1 }}. {{ server.hostname }}
</li>

遍历对象

vue
<ul>
  <li v-for="(value, key) in serverInfo" :key="key">
    {{ key }}: {{ value }}
  </li>
</ul>

v-for 和 v-if 不要同层使用

vue
<!-- 错误:Vue 会报错,优先级冲突 -->
<li v-for="item in list" v-if="item.active">{{ item.name }}</li>

<!-- 正确:用计算属性先过滤 -->
<li v-for="item in activeItems" :key="item.id">{{ item.name }}</li>

<script setup>
const activeItems = computed(() => list.value.filter((i) => i.active));
</script>

三、表单绑定

v-model

v-model 在表单元素和数据之间建立双向绑定:输入框的值变化时数据自动更新,数据变化时输入框的值自动更新。

vue
<template>
  <input v-model="hostname" placeholder="主机名">
  <p>输入的值: {{ hostname }}</p>
</template>

<script setup>
import { ref } from "vue";

const hostname = ref("");
</script>

各种表单元素

vue
<template>
  <!-- 文本输入 -->
  <input v-model="form.hostname" type="text">

  <!-- 数字输入 -->
  <input v-model.number="form.port" type="number">

  <!-- 复选框 -->
  <input v-model="form.active" type="checkbox"> 启用

  <!-- 单选框 -->
  <input v-model="form.env" type="radio" value="dev"> 开发
  <input v-model="form.env" type="radio" value="prod"> 生产

  <!-- 下拉选择 -->
  <select v-model="form.region">
    <option value="bj">北京</option>
    <option value="sh">上海</option>
  </select>

  <!-- 多行文本 -->
  <textarea v-model="form.description"></textarea>
</template>

<script setup>
import { reactive } from "vue";

const form = reactive({
  hostname: "",
  port: 8080,
  active: true,
  env: "dev",
  region: "bj",
  description: "",
});
</script>

修饰符

修饰符作用示例
.lazy失去焦点后同步(默认是 input 事件)v-model.lazy="text"
.number自动转为数字v-model.number="age"
.trim去除首尾空格v-model.trim="name"

四、表单提交

阻止默认提交

vue
<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.hostname" required>
    <button type="submit">提交</button>
  </form>
</template>

<script setup>
import { reactive } from "vue";

const form = reactive({
  hostname: "",
});

async function handleSubmit() {
  const response = await fetch("/api/servers", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(form),
  });

  if (response.ok) {
    alert("创建成功");
    form.hostname = "";   // 清空表单
  }
}
</script>

@submit.prevent 阻止表单默认的页面刷新提交,改由 JavaScript 处理。

五、常见错误

列表缺少 key

vue
<!-- 错误:没有 key,Vue 无法高效追踪节点 -->
<li v-for="item in list">{{ item.name }}</li>

<!-- 正确 -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

用索引作为 key

vue
<!-- 危险:列表重排或删除时,key 和实际数据对应不上 -->
<li v-for="(item, index) in list" :key="index">...</li>

如果列表会增删改或排序,始终用数据的唯一标识作为 key

直接修改 v-for 的数组元素(reactive)

js
const list = reactive([{ name: "a" }, { name: "b" }]);

// 错误:直接通过索引赋值不会触发响应
list[0] = { name: "c" };

// 正确:用 splice
list.splice(0, 1, { name: "c" });

// 如果用 ref 包装数组,可以直接赋值
const list = ref([{ name: "a" }]);
list.value[0] = { name: "c" };   // 正常响应