跳到主要内容

指令✅

有用过指令么,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 使用

延伸阅读

有用过自定义指令么,说下使用场景?​

答案

自定义指令用来实现跨组件的可复用操作逻辑,典型的场景包括

  1. 埋点,通过实现类似 v-tracker 来定于元素上的曝光,点击等事件
  2. dom 操作,比如类似 v-click-outside 实现判断是否点击到元素外的区域的逻辑
提示

经验若涉及到一些通用的涉及组件或者 DOM 的操作和行为时,可以优先考虑是否可以通过指令来解决,本质上可以把指令理解为对于组件或者原生 DOM 进行了属性扩充,来增强原有功能。

自定义指令的方法如下

  1. 全局指令 使用 app.directive 方法注册全局指令
const app = createApp({})

// 使 v-highlight 在所有组件中都可用
app.directive('highlight', {
/* ... */
})
  1. 组件内 option directives 定义指令
export default {
directives: {
highlight: {
/* ... */
}
}
}
  1. 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 使用该指令的组件实例。

参考示例

<script src="https://unpkg.com/vue@3"></script>
<div id="app">
   <button @click="toggleColor">切换颜色</button>
   <select v-model="styletype" style="margin-left:10px;">
      <option value="backgroundColor">背景色</option>
      <option value="color">文字色</option>
   </select>
   <!-- 注意这里是在 html 中使用,参数不要用驼峰不然无法找到 -->
   <div
      v-my-style:[styletype].color="currentColor"
   >
      这是自定义指令演示
   </div>
</div>
<script>
const { createApp, ref } = Vue;

createApp({
   setup() {
      const currentColor = ref('orange');
      const styletype = ref('color');
      function toggleColor() {
         currentColor.value = currentColor.value === 'orange' ? 'skyblue' : 'orange';
      }
      return { currentColor, styletype, toggleColor };
   }
})
.directive('my-style', {
   mounted(el, binding) {
      // 动态参数:binding.arg
      if (binding.arg === 'backgroundColor') {
         el.style.backgroundColor = binding.value;
      } else if (binding.arg === 'color') {
         el.style.color = binding.value;
      }
      // 修饰符:binding.modifiers.color
      if (binding.modifiers.color) {
         el.style.fontWeight = 'bold';
      }
   },
   updated(el, binding) {
      // 只清除当前未选中的样式
      if (binding.arg === 'backgroundColor') {
         el.style.backgroundColor = binding.value;
      } else if (binding.arg === 'color') {
         el.style.color = binding.value;
      }
   }
})
.mount('#app');
</script>
<style>

</style>

v-if 和 v-show 差别​

答案
特性v-ifv-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

  1. v-bind 可以采用 : 语法糖, 如果绑定的是 DOM property, 可以使用 . 语法糖,比如 <div :someProperty.prop="someObject"></div> 等效为 <div .someProperty="someObject"></div>
  2. v-bind 支持批量绑定多个属性,,详见 传递 props 官方文档
  3. v-bind 在渲染 style 和 class 时,支持对象和数组语法, 详见 class style 官方文档

v-model

  1. 主要用于表单中实现表单元素的双向绑定,v-model 语法糖会自动处理输入事件并更新数据, 具体详见 表单绑定
<input v-model="text">

<!-- 会被转换为 -->
<input
:value="text"
@input="event => text = event.target.value">
  1. 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"
/>
  1. 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" />
  1. 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 映射错误

ref 的作用?​

答案

ref 用来实现在访问 DOM 或组件实例。一般用在需要直接调用 DOM 操作或者消费组件实例的属性和方法时。 可以采用如下策略定义 ref。

  1. 如果直接在 options api 中元素上绑定 <p ref="p">hello</p> ,可以通过 this.$refs.p 访问到 DOM 元素

  2. 如果在组合式 API 中使用 ref,直接访问同名 ref 变量即可

    <script setup>
    import { useTemplateRef } from 'vue'

    const pRef = useTemplateRef('p')
    </script>

    <template>
    <p ref="p">hello</p>
    </template>
  3. ref 也支持函数来自定义赋值方式

    <ChildComponent :ref="(el) => child = el" />
提示

注意在消费 ref 时,确保组件已经挂载完成,可以结合 mounted 钩子或者 nextTick 来确保 DOM 已经挂载完成

修饰符​

答案

修饰符是 Vue 提供的一种语法糖,通过在事件或指令后添加以点(.)开头的后缀,简化常见 DOM 事件处理逻辑和对指令的扩展

  1. 事件修饰符

    • .stop:阻止事件冒泡(event.stopPropagation())
    • .prevent:阻止默认行为(event.preventDefault())
    • .capture:使用捕获模式监听事件
    • .self:仅在事件由自身元素触发时才执行回调
    • .once:事件只触发一次
    • .passive:以 passive 方式监听,不能调用 preventDefault() 主要用在滚动场景避免 js 阻塞滚动执行
    提示

    事件修饰符可以组合使用结合顺序左到右,比如 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为, @click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。

  2. 按键修饰符

    • .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,表示按下回车键时阻止默认行为。

  3. 鼠标修饰符

    • .left 鼠标左键
    • .right 鼠标右键
    • .middle 鼠标中键
  4. v-model 修饰符

    1. .lazy 在 change 事件后更新数据而不是 input 事件
    2. .number 将输入的值转换为数字
    3. .trim 自动去除输入值的首尾空格
  5. v-bind 修饰符

    1. .camel 将 kebab-case 转换为 camelCase
    2. .prop 强制绑定为 DOM property,3.2+ 支持
    3. .attr 强制绑定为 DOM attribute,3.2+ 支持

vue 是如何识别和解析指令​

答案

Vue 识别和解析指令的流程如下:

  1. 模板解析:Vue 编译器将模板解析为抽象语法树(AST),识别出所有以 v- 开头的指令。
  2. 提取参数和修饰符:编译器分析指令的参数(如 v-bind:src 的 src)和修饰符(如 .sync)。
  3. 表达式处理:对指令表达式(如 v-if="isShow" 的 isShow)进行解析,生成可执行的 JavaScript 代码。
  4. 生成渲染函数:编译器将 AST 转换为渲染函数,指令会被映射为具体的运行时代码。
  5. 运行时执行:渲染函数执行时,指令对应的逻辑会被调用,实现数据绑定、条件渲染等功能。

以 v-bind 为例,编译器会将其解析为属性绑定的代码,最终在渲染函数中动态设置 DOM 属性。其他指令也是类似流程:编译时识别,运行时执行具体逻辑。

具体可以去 template explorer 查看指令编译后结果