还在纠结用v-if还是v-show?看完这篇彻底搞懂Vue渲染机制!

作者:良山有风来日期:2025/10/10

你是不是也曾经在写Vue时纠结过:这里到底该用v-if还是v-show?

或者更惨的是,明明代码逻辑没问题,列表渲染却总是出现各种诡异bug:删除一个项,结果删错了;切换数据,页面状态全乱了...

别担心,今天我就来帮你彻底搞懂Vue的条件渲染和列表渲染,让你写出更优雅、更高效的代码!

v-if和v-show:看似相似,实则大不相同

先来看个最简单的例子:

1<!-- v-if 的用法 -->
2<div v-if="isVisible">我会在条件为真时渲染</div>
3
4<!-- v-show 的用法 -->  
5<div v-show="isVisible">我总是会被渲染,只是通过CSS控制显示</div>
6

看起来差不多对不对?但它们的底层机制完全不同!

v-if是真正的条件渲染:当条件为false时,元素根本不会出现在DOM中。就像你决定今天要不要带伞出门,不下雨就干脆不带。

v-show只是CSS切换:不管条件如何,元素都会被渲染到DOM中,只是通过display属性来控制显示隐藏。就像你总是带着伞,只是根据天气决定要不要撑开。

性能对比:什么时候该用哪个?

来段代码让你直观感受它们的差异:

1<template>
2  <div>
3    <!-- 频繁切换的场景 -->
4    <button @click="toggle">切换显示状态</button>
5    
6    <!-- 适合用v-show:频繁切换,初始渲染成本高 -->
7    <div class="heavy-component" v-show="isVisible">
8      <h3>这是一个很重的组件</h3>
9      <!-- 假设这里有很多复杂的DOM结构和计算 -->
10    </div>
11    
12    <!-- 适合用v-if:不常变化,或者条件为false时想节省资源 -->
13    <div v-if="hasPermission">
14      <h3>管理员专属内容</h3>
15      <!-- 这部分内容只有管理员能看到,普通用户根本不需要渲染 -->
16    </div>
17  </div>
18</template>
19
20<script>
21export default {
22  data() {
23    return {
24      isVisible: false,
25      hasPermission: false
26    }
27  },
28  methods: {
29    toggle() {
30      this.isVisible = !this.isVisible
31      // 如果这里用v-if,每次切换都要重新创建/销毁组件
32      // 用v-show就只是切换CSS,性能好很多
33    }
34  }
35}
36</script>
37

使用场景总结

  • 需要频繁切换显示状态 → 用 v-show
  • 运行时条件很少改变 → 用 v-if
  • 需要条件为false时完全节省资源 → 用 v-if
  • 初始渲染成本高的组件 → 考虑 v-show

列表渲染的key属性:小细节,大作用

现在我们来聊聊列表渲染中那个经常被忽略,却又极其重要的key属性。

先看一个没有key的典型问题:

1<template>
2  <div>
3    <div v-for="item in list">
4      {{ item.name }}
5      <input type="text" placeholder="试试在这里输入内容">
6    </div>
7    <button @click="removeFirst">删除第一项</button>
8  </div>
9</template>
10
11<script>
12export default {
13  data() {
14    return {
15      list: [
16        { id: 1, name: '苹果' },
17        { id: 2, name: '香蕉' }, 
18        { id: 3, name: '橙子' }
19      ]
20    }
21  },
22  methods: {
23    removeFirst() {
24      this.list.shift() // 删除第一项
25      // 问题来了:如果你在第一个input里输入了文字,删除后文字会跑到第二个input里!
26    }
27  }
28}
29</script>
30

为什么会这样?因为Vue在更新DOM时,默认使用"就地复用"策略。没有key的时候,它不知道哪个元素对应哪个数据,只能简单地进行位置对比。

加上key,问题迎刃而解

1<template>
2  <div>
3    <!-- 正确的做法:使用唯一标识作为key -->
4    <div v-for="item in list" :key="item.id">
5      {{ item.name }}
6      <input type="text" placeholder="现在输入内容再删除试试">
7    </div>
8  </div>
9</template>
10

加上item.id作为key后,Vue就能准确追踪每个节点的身份,删除操作再也不会混乱了!

key的高级用法:强制组件重建

key的真正威力不止于此,它还能用来强制组件重新渲染!

假设我们有这样一个需求:一个计数器组件,需要在某些情况下完全重置:

1<template>
2  <div>
3    <!-- 普通用法:切换showCounter时组件会保持状态 -->
4    <CounterComponent v-if="showCounter" />
5    
6    <!-- 高级用法:通过改变key来强制重新创建组件 -->
7    <CounterComponent 
8      v-if="showCounter" 
9      :key="componentKey" 
10    />
11    
12    <button @click="resetComponent">重置计数器</button>
13  </div>
14</template>
15
16<script>
17export default {
18  data() {
19    return {
20      showCounter: true,
21      componentKey: 0
22    }
23  },
24  methods: {
25    resetComponent() {
26      // 改变key值,Vue会认为这是不同的组件,从而销毁旧实例,创建新实例
27      this.componentKey += 1
28    }
29  }
30}
31</script>
32

这个技巧在很多场景下都超级有用:

场景1:表单重置

1<template>
2  <div>
3    <!-- 用户提交表单后,通过改变key来清空所有输入 -->
4    <UserForm :key="formKey" />
5    <button @click="handleSubmit">提交</button>
6    <button @click="resetForm">重置表单</button>
7  </div>
8</template>
9
10<script>
11export default {
12  data() {
13    return {
14      formKey: 0
15    }
16  },
17  methods: {
18    handleSubmit() {
19      // 提交表单逻辑...
20    },
21    resetForm() {
22      // 简单粗暴但有效:改变key,整个表单组件重新创建
23      this.formKey += 1
24    }
25  }
26}
27</script>
28

场景2:路由参数变化但组件相同

1<template>
2  <div>
3    <!-- 同一个组件,不同ID的用户详情页 -->
4    <UserProfile :key="$route.params.userId" />
5  </div>
6</template>
7

场景3:强制重新触发生命周期

1<template>
2  <div>
3    <!-- 需要重新执行mounted等生命周期时 -->
4    <DataFetcher :key="refreshKey" />
5    <button @click="refreshData">刷新数据</button>
6  </div>
7</template>
8
9<script>
10export default {
11  data() {
12    return {
13      refreshKey: 0
14    }
15  },
16  methods: {
17    refreshData() {
18      this.refreshKey += 1 // 强制重新创建组件,重新执行mounted
19    }
20  }
21}
22</script>
23

实战技巧:组合使用的最佳实践

在实际项目中,我们经常需要把这些技巧组合使用。来看一个综合例子:

1<template>
2  <div>
3    <!-- 用户列表 -->
4    <div 
5      v-for="user in filteredUsers" 
6      :key="user.id"
7      class="user-item"
8    >
9      <!-- 用户基本信息总是显示 -->
10      <div class="user-basic">
11        <span>{{ user.name }}</span>
12        <button @click="toggleDetails(user.id)">
13          {{ showDetails[user.id] ? '收起' : '展开' }}
14        </button>
15      </div>
16      
17      <!-- 详细信息:不常切换,用v-if节省资源 -->
18      <div v-if="showDetails[user.id]" class="user-details">
19        <p>邮箱:{{ user.email }}</p>
20        <p>电话:{{ user.phone }}</p>
21        <!-- 假设详情部分有很多复杂内容 -->
22      </div>
23      
24      <!-- 编辑状态:频繁切换,用v-show保持响应 -->
25      <div v-show="editingUser === user.id" class="edit-form">
26        <input v-model="user.name" />
27        <button @click="saveUser(user)">保存</button>
28      </div>
29    </div>
30    
31    <!-- 空状态提示:用v-if,没有数据时完全不需要渲染 -->
32    <div v-if="filteredUsers.length === 0" class="empty-state">
33      暂无用户数据
34    </div>
35  </div>
36</template>
37
38<script>
39export default {
40  data() {
41    return {
42      users: [],
43      showDetails: {},
44      editingUser: null
45    }
46  },
47  computed: {
48    filteredUsers() {
49      // 假设有过滤逻辑
50      return this.users
51    }
52  },
53  methods: {
54    toggleDetails(userId) {
55      // 使用Vue.set或this.$set确保响应式
56      this.$set(this.showDetails, userId, !this.showDetails[userId])
57    }
58  }
59}
60</script>
61

避坑指南:常见错误与解决方案

错误1:用索引作为key

1<!-- 不要这样做! -->
2<div v-for="(item, index) in list" :key="index">
3  {{ item.name }}
4</div>
5
6<!-- 应该这样做 -->
7<div v-for="item in list" :key="item.id">
8  {{ item.name }}
9</div>
10

为什么不能用索引?因为当列表顺序变化时,索引无法正确追踪元素!

错误2:v-if和v-for用在一起

1<!-- 性能不好 -->
2<div v-for="item in list" v-if="item.isActive" :key="item.id">
3  {{ item.name }}
4</div>
5
6<!-- 更好的做法 -->
7<div v-for="item in activeItems" :key="item.id">
8  {{ item.name }}
9</div>
10
11<script>
12export default {
13  computed: {
14    activeItems() {
15      return this.list.filter(item => item.isActive)
16    }
17  }
18}
19</script>
20

错误3:忘记处理边界情况

1<template>
2  <div>
3    <!-- 好的做法:考虑各种边界情况 -->
4    <div v-if="isLoading">加载中...</div>
5    <div v-else-if="list.length === 0">暂无数据</div>
6    <div v-else v-for="item in list" :key="item.id">
7      {{ item.name }}
8    </div>
9  </div>
10</template>
11

总结

今天我们一起深入探讨了Vue条件渲染和列表渲染的核心技巧:

  1. v-if vs v-show:理解它们的本质区别,根据使用频率和性能需求做出正确选择
  2. key的重要性:不仅是避免渲染bug,更是Vue高效更新的关键
  3. key的高级用法:强制组件重建,解决状态管理难题
  4. 组合使用的最佳实践:在实际项目中灵活运用这些技巧

记住,好的开发者不仅要让代码能运行,更要让代码运行得高效、优雅。这些看似小的细节,往往决定了你代码的质量。

现在,回头检查一下你的项目,有没有可以优化的地方?欢迎在评论区分享你的实战经验!


还在纠结用v-if还是v-show?看完这篇彻底搞懂Vue渲染机制!》 是转载文章,点击查看原文


相关推荐


JavaScript性能优化实战:从指标到落地的全链路方案
weixin_439647792025/10/8

JavaScript性能优化实战:从指标到落地的全链路方案 实际项目中,性能优化往往不是单一手段的应用,而是“指标监测-瓶颈定位-方案实施-效果验证”的全链路过程。本文将结合电商、管理系统等真实场景,提供可落地的性能优化闭环方案。 一、性能指标体系:从“感觉卡顿”到“数据说话” 性能优化的第一步是建立可量化的指标体系,避免凭主观感受判断优化效果。前端核心性能指标可分为三类: 1. 加载性能指标 LCP(最大内容绘制):衡量首屏加载速度,目标值<2.5sTTI(交互时间):页面可完全交互


Flutter 开发:应用颜色使用 Class 还是 Enum?—— 你应该选择哪一个?
JarvanMo2025/10/7

在开始一个新的 Flutter 项目时,第一步就是定义你的颜色调色板(color palette) 。一个一致且可维护的颜色系统不仅能保持你的设计简洁,还能让你的应用扩展变得更加容易。 但这里有一个开发者经常面临的常见问题: 👉 在 Flutter 中,你是应该使用带有静态常量的 Class(类) ,还是使用 **Enum(枚举)**来管理颜色呢? 随着 Dart 2.17 中**增强型枚举(enhanced enums)**的到来,答案变得更有趣了。下面我们通过示例、优缺点来探讨这两种方法。


Qt Theme —— 纯 qss 的 Qt 主题
hubenchang05152025/10/5

#Qt Theme —— 纯 qss 的 Qt 主题 源码地址:https://github.com/hubenchang0515/QtTheme/ Qt Theme 是一个纯 qss 的 Qt 主题项目,能够极为简单对已有项目的风格进行改进。 支持 C++、PyQt5、PyQt6、PySide2、PySide6,并以 WebAssembly 的方式在 GitHub Pages 上发布。 #安装 这里演示一下在 Python 上的使用,首先进行安装: pip install QtTheme


零基础从头教学Linux(Day 43)
小白银子2025/10/4

Nginx实现跨域与防盗链配置指南 四、 Nginx配置跨域 CORS 4.1 跨域的定义 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。 4.2 同源的定义 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。 与 URL http://store.company.com/dir/page.html 的源进行对比的示例: http://store.comp


C语言实战项目:贪吃蛇(1)
高山有多高2025/10/3

前言:         通过持续数月的C语言系统学习,我们已经掌握了包括指针操作、结构体使用、文件IO等核心编程能力。为了检验学习成果并提升实战经验,在本篇技术博客中,我将带领大家开发一个具有里程碑意义的经典游戏项目 -- 贪吃蛇。          温馨提示:本篇博客为贪吃蛇游戏的前言准备。          一、贪吃蛇游戏效果演示   游戏效果演示: 二、贪吃蛇游戏设计          2.1 贪吃蛇游戏的最终目标            使⽤C


手把手部署 HFish 蜜罐:从防火墙配置到登录使用,新手也能轻松上手
着迷不白2025/10/2

​   在网络安全防护中,蜜罐工具能帮我们主动探测攻击行为,而 HFish 作为一款轻量易用的开源蜜罐,深受运维和安全从业者青睐。今天就带大家从 0 到 1 完成 HFish 的部署,全程步骤清晰,即使是新手也能跟着操作 —— 说不定部署完这套流程,老板看到规范的防护配置,还得给你加两千块工资呢!   一、前置准备:配置防火墙,开放关键端口 HFish 运行需要两个核心 TCP 端口:4433 端口用于 Web 管理界面访问,4434 端口用于节点与管理端的通信。为避免端口被防火墙拦截,我们需要


iOS 26 系统流畅度检测 从视觉特效到帧率稳定的实战策略
2501_916013742025/10/2

iOS 26 推出的 Liquid Glass 视觉语言,带来了全新的界面风格和动效体验,同时也给系统的渲染、合成、动画处理带来更高要求。在部分用户反馈中,升级 iOS 26 后出现系统卡顿、触控延迟、应用滑动不顺畅等问题。 要判断 iOS 26 是否真的“流畅”或在哪些场景有退化,需要有一套严谨的检测流程,而不是凭印象。 一、影响 iOS 26 流畅度的系统变化与挑战点 在 iOS 26 中,以下几个系统/界面变动是最可能牵扯到流畅性的问题点: Liquid Glass 界面开销 新系统的大


范式革命:RDMA 如何让网络成为 “分布式内存总线”
apple_ttt10/2/2025

摘要: RDMA技术通过内存访问范式革命,绕开远程CPU干预,实现设备间直接数据交互,显著降低延迟。其三大协议(InfiniBand、RoCE、iWARP)在性能、成本和兼容性上各有取舍:InfiniBand追求极致性能但成本高;RoCEv2兼容以太网但需精细配置;iWARP基于TCP,性能受限。RDMA虽解决了CPU瓶颈,但异构设备间的缓存一致性问题仍未解决,成为分布式计算向“单机化”演进的关键障碍。未来需结合CXL等一致性协议进一步突破。


学习Python中Selenium模块的基本用法(18:使用ActionChains操作鼠标)
gc_229910/1/2025

学习并验证使用Selenium模块的ActionChains操作鼠标的基本用法


在 VSCode 中运行 Vue.js 项目
小二爱编程·9/30/2025

这篇教程详细介绍了如何在VSCode中运行Vue.js项目。首先需要安装Node.js、Vue CLI和VSCode,然后通过Vue CLI创建新项目并安装依赖。接着在VSCode中打开项目文件夹,安装推荐的插件(如Volar)。最后使用npm run serve命令启动开发服务器,在浏览器访问即可查看运行效果。教程还说明了项目调试方法,包括自动刷新和断点调试。整个过程详细易懂,适合前端开发者和初学者学习使用VSCode开发Vue.js项目。

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0