文章目录
- [一、v-if vs v-show](#一、v-if vs v-show)
-
- [1. 核心原理对比](#1. 核心原理对比)
- [2. 隐藏元素的 CSS 方案:display, opacity, visibility](#2. 隐藏元素的 CSS 方案:display, opacity, visibility)
- [3. 如何选择 v-if 与 v-show?](#3. 如何选择 v-if 与 v-show?)
-
- [场景 A:优先使用 v-if](#场景 A:优先使用 v-if)
- [场景 B:优先使用 v-show](#场景 B:优先使用 v-show)
- [4. 总结建议](#4. 总结建议)
- [二、v-if 与 v-for 的优先级冲突](#二、v-if 与 v-for 的优先级冲突)
-
- [1. 结论:优先级对比](#1. 结论:优先级对比)
- [2. 详细区别与底层原理](#2. 详细区别与底层原理)
-
- [A Vue 2:v-for 优先](#A Vue 2:v-for 优先)
- [B Vue 3:v-if 优先](#B Vue 3:v-if 优先)
- [3. 为什么即使在 Vue 2 中也不建议混用?](#3. 为什么即使在 Vue 2 中也不建议混用?)
- 三、自定义指令的使用场景
-
- [1. 编写自定义指令 (v-debounce)](#1. 编写自定义指令 (v-debounce))
- [2. 全局注册指令](#2. 全局注册指令)
- [3. 在组件中使用](#3. 在组件中使用)
- [4. Vue 3 自定义指令要点总结](#4. Vue 3 自定义指令要点总结)
-
-
- [💡 提示](#💡 提示)
-
一、v-if vs v-show
在 Vue 开发中,控制元素的显示与隐藏是高频操作。理解这两者的区别,本质上是理解**"DOM 存在性"与"CSS 样式控制"**的权衡。
1. 核心原理对比
v-if:指令式条件渲染
- 本质 :它是动态的 DOM 挂载/卸载。
- 行为 :当条件为
false时,Vue 直接从 DOM 树中删除该元素(或根本不创建);当条件变为true时,Vue 会触发组件的生命周期钩子并将其重新插入 DOM。 - 特性:它是"惰性"的。如果初始条件为假,它什么都不做,直到条件第一次变为真。
v-show:样式切换
- 本质 :它是简单的 CSS 属性切换。
- 行为 :无论条件真假,元素始终会被渲染并保留在 DOM 树中。Vue 只是简单地切换该元素的内联样式
display。 - 特性:元素始终存在,只是视觉上不可见。
2. 隐藏元素的 CSS 方案:display, opacity, visibility
这三个属性都能让元素"消失",但在渲染引擎中的表现截然不同:
| 属性 | 占据空间 | 响应事件 | 触发重排(Reflow) | 备注 |
|---|---|---|---|---|
display: none |
否 | 否 | 是 | v-show 使用此属性。完全从渲染树移除,不占位。 |
visibility: hidden |
是 | 否 | 否(仅重绘) | 元素像"透明人",位子占着,但看不见摸不着。 |
opacity: 0 |
是 | 是 | 否 | 仅仅是透明度,用户依然可以点击该不可见区域。 |
3. 如何选择 v-if 与 v-show?
选择的标准主要取决于切换频率 和初始性能的权衡。
场景 A:优先使用 v-if
- 使用场景:用户权限控制(如管理员可见)、登录模态框、条件分支较多的复杂组件。
- 理由 :如果该元素在整个页面生命周期中可能永远不会显示,
v-if可以节省初始渲染的内存和开销。 - 缺点:切换开销大,因为涉及 DOM 的销毁和重建。
场景 B:优先使用 v-show
- 使用场景:Tab 标签页切换、搜索过滤结果、频繁出现的 Tooltip 或侧边栏。
- 理由:虽然初始渲染开销略高(无论是否显示都要渲染),但切换时极其流畅,仅改变一行 CSS。
- 缺点:由于元素始终存在,如果页面有大量隐藏的高负载组件,会占用较多内存。
4. 总结建议
"首屏性能"选
v-if(不用的不加载);"交互流畅"选v-show(预先加载好)。
特别提示: 在使用 v-if 切换多个相似元素时,务必带上唯一的 key。这能帮助 Vue 的 Diff 算法准确识别节点,避免不必要的组件重新初始化,从而优化性能。
二、v-if 与 v-for 的优先级冲突
在 Vue 开发中,v-if 和 v-for 是最常用的指令,但它们在同一个元素上使用时,不同版本的 Vue 有着截然不同的处理机制。
1. 结论:优先级对比
| Vue 版本 | 优先级更高者 | 编译器行为 |
|---|---|---|
| Vue 2.x | v-for |
先进行循环,再对循环出的每一项进行条件判断。 |
| Vue 3.x | v-if |
先进行条件判断,通过后再进入循环逻辑。 |
2. 详细区别与底层原理
A Vue 2:v-for 优先
在 Vue 2 中,编译器会将 v-for 放在外层处理。
-
底层逻辑 :
javascript// 即使只需要展示 1 个元素,也会遍历整个 list render() { return this.list.map(item => { if (item.shouldShow) { return _c('li', [ _v(item.name) ]) } return _e() // 为不显示的项生成空节点占位 }) } -
缺点 :即使 v-if 的条件不成立,Vue 依然会执行完整的循环,并为每一项生成虚拟节点(VNode),造成极大的性能浪费。
B Vue 3:v-if 优先
Vue 3 为了优化性能,将 v-if 的优先级提升到了最高。
-
底层逻辑:先判断 v-if 里的条件,如果为真才去解析循环。
-
潜在风险:由于 v-if 先于 v-for 执行,此时循环变量(如 item)还未定义。
报错示例:
javascript
<li v-for="item in list" v-if="item.isActive">...</li>
3. 为什么即使在 Vue 2 中也不建议混用?
虽然 Vue 2 能通过"先循环后判断"跑通逻辑,但官方严格禁止这种写法,原因如下:
-
性能负担:每一轮渲染都要过滤整个数组,无论数据是否发生变化。
-
内存消耗:大量不需要显示的项会产生冗余的虚拟节点,增加 Diff 算法的对比时间。
-
逻辑耦合:将数据过滤逻辑写在 HTML 模板中,导致业务代码难以维护和复用。
三、自定义指令的使用场景
下面以 v-debounce(防抖指令)为例,为你演示如何从零编写并全局注册、使用。
1. 编写自定义指令 (v-debounce)
防抖指令的核心逻辑是:拦截点击事件,在设定的延迟时间内,如果再次触发,则重新计时。
javascript
// directives/debounce.js
export default {
/**
* mounted: 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
* el: 指令绑定到的元素。这可以用于直接操作 DOM。
* binding: 一个对象,包含传给指令的值(value)、参数等
*/
mounted(el, binding) {
// 1. 校验传入的必须是一个函数
if (typeof binding.value !== 'function') {
console.warn('v-debounce 必须绑定一个函数');
return;
}
let timer = null;
const delay = 500; // 默认防抖时间 500ms
// 2. 定义处理逻辑:将点击事件封装在定时器中
el._debounceHandler = function (event) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
// 调用传入的函数,并将 event 对象传回去
binding.value(event);
}, delay);
};
// 3. 监听原生点击事件
el.addEventListener('click', el._debounceHandler);
},
/**
* beforeUnmount: 在卸载绑定元素的父组件之前调用
*/
beforeUnmount(el) {
// 4. 关键:组件卸载前必须移除监听器,防止内存泄漏
el.removeEventListener('click', el._debounceHandler);
}
};
2. 全局注册指令
在 main.js 中将该指令挂载到应用实例上。
javascript
import { createApp } from 'vue';
import App from './App.vue';
import debounce from './directives/debounce';
const app = createApp(App);
// 注册全局指令:第一个参数是指令名称(不需要带 v-)
app.directive('debounce', debounce);
app.mount('#app');
3. 在组件中使用
使用时直接在标签上写 v-debounce,并传入你想执行的方法。
javascript
<template>
<div class="container">
<h3>防抖指令测试</h3>
<button v-debounce="handleSave">
立即提交 (500ms 内多次点击只执行一次)
</button>
<p>执行次数: {{ count }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const handleSave = () => {
count.value++;
console.log('数据已提交到后台...');
};
</script>
4. Vue 3 自定义指令要点总结
- 钩子函数 :Vue 3 指令钩子(
created,beforeMount,mounted,updated等)与组件生命周期高度一致。 - 清理工作 :必须在
beforeUnmount或unmounted中手动移除监听事件或定时器,避免内存泄漏。 - 参数传递 :
binding.value: 获取指令绑定的值(如:函数、变量)。binding.arg: 获取指令参数(如:v-focus:argument中的argument)。binding.modifiers: 获取修饰符(如:v-focus.stop中的stop)。
💡 提示
自定义指令是处理 "如何做" (DOM 操作)的,而组件逻辑是处理 "做什么"(业务数据)的。请确保不要在指令里写太重的业务逻辑。