指令✅
有用过指令么,Vue 有哪些内置指令 ?
答案
指令的本质是对模版上元素属性功能的增强,通过指令可以实现模版的动态渲染和相关副作用操作。
Vue 的内置指令如下
指令名称 | 描述 |
---|---|
v-if | 条件渲染, 满足条件元素才会挂载 |
v-else-if | 条件渲染, 只有在 v-if 不满足条件时匹配条件才会挂载 |
v-else | 条件渲染, 只有在 v-if、v-else-if 不满足条件时才会挂载 |
v-for | 列表渲染, 遍历数组或对象 |
v-bind | 动态绑定属性或组件 prop |
v-on | 事件监听, 绑定 DOM 事件, 也可采用 @ 语法糖 |
v-model | 双向绑定, 主要用于表单元素 |
v-show | 显示隐藏, 通过 display: none 控制元素的显示和隐藏 |
v-text | 文本插值, 将数据渲染为文本, 等效于使用 {{ }} 语法 |
v-html | 插入富文本,注意为了防止 xss 攻击,可以使用 xss 包转义script 等特殊标签 |
v-slot | 用于指定默认插槽和具名插槽,一般用 # 简写 |
v-pre | 跳过编译,显示默认内容,比如需要显直接{{}} 的场景 |
v-once | 只渲染一次,后续渲染将不再更新此节点 |
v-memo | 用来实现节点,复用满足条件才会触发重新渲染使用详见, v-memo |
v-cloak | 用于在 Vue 实例编译完成前,保持元素的隐藏状态, 配合 css 使用 |
延伸阅读
- Vue 内置指令 官方文档
有用过自定义指令么,说下使用场景?
答案
自定义指令用来实现跨组件的可复用操作逻辑,典型的场景包括
- 埋点,通过实现类似
v-tracker
来定于元素上的曝光,点击等事件 - dom 操作,比如类似 v-click-outside 实现判断是否点击到元素外的区域的逻辑
经验若涉及到一些通用的涉及组件或者 DOM 的操作和行为时,可以优先考虑是否可以通过指令来解决,本质上可以把指令理解为对于组件或者原生 DOM 进行了属性扩充,来增强原有功能。
自定义指令的方法如下
- 全局指令 使用
app.directive
方法注册全局指令
const app = createApp({})
// 使 v-highlight 在所有组件中都可用
app.directive('highlight', {
/* ... */
})
- 组件内 option
directives
定义指令
export default {
directives: {
highlight: {
/* ... */
}
}
}
- composition api 模式
<script setup>
// 在模板中启用 v-highlight
const vHighlight = {
mounted: (el) => {
el.classList.add('is-highlight')
}
}
</script>
<template>
<p v-highlight>This sentence is important!</p>
</template>
指令支持的核心钩子如下
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created (el, binding, vnode) {
},
// 在元素被插入到 DOM 前调用
beforeMount (el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted (el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate (el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated (el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount (el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted (el, binding, vnode) {}
}
指令设计的核心参数为 binding 中的配置包括
- value 传递给指令的值。例如在
v-my-directive="1 + 1"
中,值是 2。 - arg 传递给指令的参数 (如果有的话)。例如在
v-my-directive:foo
中,参数是 "foo"。 - modifiers 一个包含修饰符的对象 (如果有的话)。例如在
v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。 instance 使用该指令的组件实例。
参考示例
v-if 和 v-show 差别
答案
特性 | v-if | v-show |
---|---|---|
渲染方式 | 条件渲染,满足条件才会挂载 | 显示隐藏,实例始终存在,通过 display 属性控制显示 |
性能 | 性能开销较大,隐藏显示会重新挂载组件,触发组件钩子 | 性能开销较小,频繁切换不会影响性能 |
其他 | 适用于条件渲染基于不同逻辑渲染不同组件 | 适用于需要频繁显示和隐藏的场景,如模态框 |
为何 v-if 和 v-for 不能同时用在同一个元素上?
答案
核心原因是 v-if
的指令优先级高于 v-for
会导致无法访问到 v-for
中的变量, 可以通过 computed 来实现过滤功能。后者将 v-if 放在 v-for 的子元素中来实现控制。可阅读官方文档 v-for v-if 进一步学习。
v-bind 和 v-model 有什么区别, 如何使用
答案
v-model
是 v-bind
和 v-on
的语法糖,主要用于表单元素的双向绑定。它会自动处理输入事件并更新数据,而 v-bind
仅用于单向绑定属性。
v-bind
- v-bind 可以采用
:
语法糖, 如果绑定的是 DOM property, 可以使用.
语法糖,比如<div :someProperty.prop="someObject"></div>
等效为<div .someProperty="someObject"></div>
- v-bind 支持批量绑定多个属性,,详见 传递 props 官方文档
- v-bind 在渲染
style
和class
时,支持对象和数组语法, 详见 class style 官方文档
v-model
- 主要用于表单中实现表单元素的双向绑定,v-model 语法糖会自动处理输入事件并更新数据, 具体详见 表单绑定
<input v-model="text">
<!-- 会被转换为 -->
<input
:value="text"
@input="event => text = event.target.value">
- Vue3 中 v-model 支持绑定多个参数, 具体详见 组件 v-model 官方文档
<!-- UserName.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
<!-- App.vue -->
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
- v-model 还支持修饰符,主要包括
.lazy
、.number
、.trim
,你也可以自定义修饰符
<!-- MyComponent.vue -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="props.modelValue" @input="emitValue" />
</template>
<!-- App.vue -->
<MyComponent v-model.capitalize="myText" />
- 3.4+ 版本中 , 添加了
defineModel
函数来简化表单空间的属性和事件定义
<!-- MyComponent.vue -->
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<!-- App.vue -->
<MyComponent v-model.capitalize="myText" />
key 的作用
答案
key 为了尽可能实现组件的复用,对于 key 相同的元素,如果组件类型未发生变化,会只触发 patch 更新加移位操作,相比直接销毁历史元素再挂载性能更好。详见 key 官方文档。
注意不要出现重复的 key 值,这会导致渲染异常,渲染异常的具体原因是在计算 keyToNewIndexMap 时候,key 对应的索引值会被覆盖,同时后续在寻找新旧节点的 diff 时会导致 newIndexToOldIndexMap 映射错误
修饰符
答案
修饰符是 Vue 提供的一种语法糖,通过在事件或指令后添加以点(.)开头的后缀,简化常见 DOM 事件处理逻辑和对指令的扩展
-
事件修饰符
.stop
:阻止事件冒泡(event.stopPropagation()
).prevent
:阻止默认行为(event.preventDefault()
).capture
:使用捕获模式监听事件.self
:仅在事件由自身元素触发时才执行回调.once
:事件只触发一次.passive
:以 passive 方式监听,不能调用preventDefault()
主要用在滚动场景避免 js 阻塞滚动执行
提示事件修饰符可以组合使用结合顺序左到右,比如
@click.prevent.self
会阻止元素及其子元素的所有点击事件的默认行为,@click.self.prevent
则只会阻止对元素本身的点击事件的默认行为。 -
按键修饰符
.enter
:回车键.tab
:Tab 键.delete
:删除键(包括退格键).esc
:Esc 键.space
:空格键.up
、.down
、.left
、.right
:方向键.exact
:精确匹配修饰符,结合其他修饰符使用,如.ctrl
、.shift
、.alt
、.meta
(macOS 上的 Co mmand 键)
提示按键修饰符可以与其他按键或其他修饰符组合使用,如
@keyup.alt.enter
表示同时按下Alt + Enter
键,@keyup.enter.prevent
,表示按下回车键时阻止默认行为。 -
鼠标修饰符
.left
鼠标左键.right
鼠标右键.middle
鼠标中键
-
v-model 修饰符
.lazy
在change
事件后更新数据而不是input
事件.number
将输入的值转换为数字.trim
自动去除输入值的首尾空格
-
v-bind 修饰符
.camel
将 kebab-case 转换为 camelCase.prop
强制绑定为 DOM property,3.2+ 支持.attr
强制绑定为 DOM attribute,3.2+ 支持
vue 是如何识别和解析指令
答案
Vue 识别和解析指令的流程如下:
- 模板解析:Vue 编译器将模板解析为抽象语法树(AST),识别出所有以
v-
开头的指令。 - 提取参数和修饰符:编译器分析指令的参数(如
v-bind:src
的src
)和修饰符(如.sync
)。 - 表达式处理:对指令表达式(如
v-if="isShow"
的isShow
)进行解析,生成可执行的 JavaScript 代码。 - 生成渲染函数:编译器将 AST 转换为渲染函数,指令会被映射为具体的运行时代码。
- 运行时执行:渲染函数执行时,指令对应的逻辑会被调用,实现数据绑定、条件渲染等功能。
以 v-bind
为例,编译器会将其解析为属性绑定的代码,最终在渲染函数中动态设置 DOM 属性。其他指令也是类似流程:编译时识别,运行时执行具体逻辑。
具体可以去 template explorer 查看指令编译后结果