Vue.js 指令系统完全指南:深入理解 v- 指令

Vue.js 的指令系统是其最强大的特性之一,通过以 v- 开头的特殊属性,我们可以在模板中声明式地绑定底层Vue实例的数据。本文将深入讲解Vue中最重要的指令,帮助掌握Vue的核心功能。

文章目录


1. v-model:双向数据绑定的核心

基本用法

v-model 是Vue中实现双向数据绑定的指令,主要用于表单元素。

vue 复制代码
<template>
  <div>
    <!-- 文本输入 -->
    <input v-model="message" placeholder="输入消息">
    <p>消息是: {{ message }}</p>
    
    <!-- 多行文本 -->
    <textarea v-model="text" placeholder="多行文本"></textarea>
    
    <!-- 复选框 -->
    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">{{ checked }}</label>
    
    <!-- 单选按钮 -->
    <input type="radio" id="one" value="One" v-model="picked">
    <label for="one">One</label>
    <input type="radio" id="two" value="Two" v-model="picked">
    <label for="two">Two</label>
    
    <!-- 选择框 -->
    <select v-model="selected">
      <option disabled value="">请选择</option>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '',
      text: '',
      checked: false,
      picked: '',
      selected: ''
    }
  }
}
</script>

修饰符

v-model 提供了三个有用的修饰符:

vue 复制代码
<!-- .lazy - 在 change 事件而非 input 事件触发时更新 -->
<input v-model.lazy="msg">

<!-- .number - 自动将用户输入转换为数值类型 -->
<input v-model.number="age" type="number">

<!-- .trim - 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">

自定义组件中的 v-model

vue 复制代码
<!-- 父组件 -->
<custom-input v-model="searchText"></custom-input>

<!-- 子组件 CustomInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  >
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

2. v-bind:属性绑定的万能钥匙

基本用法

v-bind 用于动态绑定一个或多个属性,或组件 prop 到表达式。

vue 复制代码
<template>
  <div>
    <!-- 绑定属性 -->
    <img v-bind:src="imageSrc" v-bind:alt="imageAlt">
    
    <!-- 缩写语法 -->
    <img :src="imageSrc" :alt="imageAlt">
    
    <!-- 绑定类名 -->
    <div :class="{ active: isActive, 'text-danger': hasError }"></div>
    <div :class="[activeClass, errorClass]"></div>
    <div :class="classObject"></div>
    
    <!-- 绑定样式 -->
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
    <div :style="[baseStyles, overridingStyles]"></div>
    <div :style="styleObject"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      imageSrc: 'https://example.com/image.jpg',
      imageAlt: '示例图片',
      isActive: true,
      hasError: false,
      activeClass: 'active',
      errorClass: 'text-danger',
      classObject: {
        active: true,
        'text-danger': false
      },
      activeColor: 'red',
      fontSize: 30,
      styleObject: {
        color: 'red',
        fontSize: '13px'
      },
      baseStyles: { color: 'blue' },
      overridingStyles: { fontSize: '20px' }
    }
  }
}
</script>

动态属性名

vue 复制代码
<template>
  <!-- 动态属性名 -->
  <a :[attributeName]="url">链接</a>
  
  <!-- 当 attributeName 为 "href" 时,等价于 -->
  <a :href="url">链接</a>
</template>

<script>
export default {
  data() {
    return {
      attributeName: 'href',
      url: 'https://www.example.com'
    }
  }
}
</script>

3. v-if / v-else-if / v-else:条件渲染

基本用法

这些指令用于条件性地渲染元素。

vue 复制代码
<template>
  <div>
    <h1 v-if="awesome">Vue is awesome!</h1>
    <h1 v-else>Oh no 😢</h1>
    
    <!-- 多条件判断 -->
    <div v-if="type === 'A'">A</div>
    <div v-else-if="type === 'B'">B</div>
    <div v-else-if="type === 'C'">C</div>
    <div v-else>Not A/B/C</div>
    
    <!-- 使用 template 包装多个元素 -->
    <template v-if="loginType === 'username'">
      <label>Username</label>
      <input placeholder="Enter your username" key="username-input">
    </template>
    <template v-else>
      <label>Email</label>
      <input placeholder="Enter your email address" key="email-input">
    </template>
  </div>
</template>

<script>
export default {
  data() {
    return {
      awesome: true,
      type: 'A',
      loginType: 'username'
    }
  }
}
</script>

v-if vs v-show

vue 复制代码
<template>
  <div>
    <!-- v-if 是"真正"的条件渲染,会销毁和重建元素 -->
    <p v-if="showIf">通过 v-if 显示</p>
    
    <!-- v-show 只是简单地切换元素的 CSS display 属性 -->
    <p v-show="showShow">通过 v-show 显示</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showIf: true,
      showShow: true
    }
  }
}
</script>

使用建议:

  • 如果需要非常频繁地切换,则使用 v-show
  • 如果在运行时条件很少改变,则使用 v-if

4. v-for:列表渲染

基本用法

v-for 指令用于基于源数据多次渲染元素或模板块。

vue 复制代码
<template>
  <div>
    <!-- 遍历数组 -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.message }}
      </li>
    </ul>
    
    <!-- 带索引的遍历 -->
    <ul>
      <li v-for="(item, index) in items" :key="item.id">
        {{ index }} - {{ item.message }}
      </li>
    </ul>
    
    <!-- 遍历对象 -->
    <ul>
      <li v-for="value in object" :key="value">
        {{ value }}
      </li>
    </ul>
    
    <!-- 遍历对象,包含键名 -->
    <ul>
      <li v-for="(value, name) in object" :key="name">
        {{ name }}: {{ value }}
      </li>
    </ul>
    
    <!-- 遍历对象,包含键名和索引 -->
    <ul>
      <li v-for="(value, name, index) in object" :key="name">
        {{ index }}. {{ name }}: {{ value }}
      </li>
    </ul>
    
    <!-- 遍历数字 -->
    <span v-for="n in 10" :key="n">{{ n }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, message: 'Foo' },
        { id: 2, message: 'Bar' }
      ],
      object: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  }
}
</script>

维护状态(key的重要性)

vue 复制代码
<template>
  <div>
    <!-- 不推荐:没有key -->
    <div v-for="item in items">
      {{ item.name }}
    </div>
    
    <!-- 推荐:使用唯一的key -->
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
    
    <!-- 数组变更方法 -->
    <button @click="addItem">添加项目</button>
    <button @click="removeItem">删除项目</button>
    <button @click="updateItem">更新项目</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: '项目1' },
        { id: 2, name: '项目2' }
      ]
    }
  },
  methods: {
    addItem() {
      this.items.push({ 
        id: Date.now(), 
        name: `项目${this.items.length + 1}` 
      })
    },
    removeItem() {
      this.items.pop()
    },
    updateItem() {
      if (this.items.length > 0) {
        this.items[0].name = '更新的项目1'
      }
    }
  }
}
</script>

5. v-on:事件处理

基本用法

v-on 指令用于监听DOM事件,并在触发时执行JavaScript代码。

vue 复制代码
<template>
  <div>
    <!-- 基本用法 -->
    <button v-on:click="counter += 1">Add 1</button>
    <p>按钮被点击了 {{ counter }} 次</p>
    
    <!-- 缩写语法 -->
    <button @click="greet">Greet</button>
    
    <!-- 内联处理器中的方法 -->
    <button @click="say('hi')">Say hi</button>
    <button @click="say('what')">Say what</button>
    
    <!-- 访问原始的DOM事件 -->
    <button @click="warn('Form cannot be submitted yet.', $event)">
      Submit
    </button>
    
    <!-- 多个事件处理器 -->
    <button @click="one($event), two($event)">
      Submit
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0,
      name: 'Vue.js'
    }
  },
  methods: {
    greet(event) {
      alert('Hello ' + this.name + '!')
      if (event) {
        alert(event.target.tagName)
      }
    },
    say(message) {
      alert(message)
    },
    warn(message, event) {
      if (event) {
        event.preventDefault()
      }
      alert(message)
    },
    one(event) {
      console.log('第一个处理器')
    },
    two(event) {
      console.log('第二个处理器')
    }
  }
}
</script>

事件修饰符

Vue为 v-on 提供了事件修饰符来处理DOM事件细节。

vue 复制代码
<template>
  <div>
    <!-- 阻止单击事件继续传播 -->
    <a @click.stop="doThis"></a>
    
    <!-- 提交事件不再重载页面 -->
    <form @submit.prevent="onSubmit"></form>
    
    <!-- 修饰符可以串联 -->
    <a @click.stop.prevent="doThat"></a>
    
    <!-- 只有修饰符 -->
    <form @submit.prevent></form>
    
    <!-- 添加事件监听器时使用事件捕获模式 -->
    <div @click.capture="doThis">...</div>
    
    <!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
    <div @click.self="doThat">...</div>
    
    <!-- 点击事件将只会触发一次 -->
    <a @click.once="doThis"></a>
    
    <!-- 滚动事件的默认行为将会立即触发,而不会等待onScroll完成 -->
    <div @scroll.passive="onScroll">...</div>
  </div>
</template>

按键修饰符

vue 复制代码
<template>
  <div>
    <!-- 只有在 key 是 Enter 时调用 vm.submit() -->
    <input @keyup.enter="submit">
    
    <!-- 其他按键修饰符 -->
    <input @keyup.tab="tabHandler">
    <input @keyup.delete="deleteHandler">
    <input @keyup.esc="escHandler">
    <input @keyup.space="spaceHandler">
    <input @keyup.up="upHandler">
    <input @keyup.down="downHandler">
    <input @keyup.left="leftHandler">
    <input @keyup.right="rightHandler">
    
    <!-- 系统修饰键 -->
    <input @keyup.ctrl="ctrlHandler">
    <input @keyup.alt="altHandler">
    <input @keyup.shift="shiftHandler">
    <input @keyup.meta="metaHandler">
    
    <!-- 组合使用 -->
    <input @keyup.ctrl.enter="ctrlEnterHandler">
    
    <!-- 鼠标按钮修饰符 -->
    <button @click.left="leftClick">左键</button>
    <button @click.right="rightClick">右键</button>
    <button @click.middle="middleClick">中键</button>
  </div>
</template>

6. 其他重要指令

v-text 和 v-html

vue 复制代码
<template>
  <div>
    <!-- v-text:更新元素的textContent -->
    <span v-text="msg"></span>
    <!-- 等价于 -->
    <span>{{ msg }}</span>
    
    <!-- v-html:更新元素的innerHTML -->
    <p v-html="html"></p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello World',
      html: '<strong>粗体文本</strong>'
    }
  }
}
</script>

v-show

vue 复制代码
<template>
  <div>
    <!-- 根据表达式的真假值,切换元素的display CSS属性 -->
    <h1 v-show="ok">Hello!</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      ok: true
    }
  }
}
</script>

v-pre

vue 复制代码
<template>
  <div>
    <!-- 跳过这个元素和它的子元素的编译过程 -->
    <span v-pre>{{ this will not be compiled }}</span>
  </div>
</template>

v-once

vue 复制代码
<template>
  <div>
    <!-- 只渲染元素和组件一次 -->
    <h1 v-once>{{ title }}</h1>
    
    <!-- 这也适用于子组件 -->
    <my-component v-once :comment="msg"></my-component>
    
    <!-- 带有v-for的v-once -->
    <ul>
      <li v-for="i in list" v-once :key="i">{{ i }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '只渲染一次的标题',
      msg: '这是一条消息',
      list: [1, 2, 3]
    }
  }
}
</script>

v-cloak

vue 复制代码
<template>
  <!-- 防止页面加载时显示未编译的Mustache标签 -->
  <div v-cloak>
    {{ message }}
  </div>
</template>

<style>
[v-cloak] {
  display: none;
}
</style>

<script>
export default {
  data() {
    return {
      message: 'Hello World'
    }
  }
}
</script>

7. 自定义指令

全局注册

javascript 复制代码
// main.js
const app = createApp({})

// 注册一个全局自定义指令 v-focus
app.directive('focus', {
  // 当被绑定的元素挂载到DOM中时...
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})

局部注册

vue 复制代码
<template>
  <input v-focus />
</template>

<script>
export default {
  directives: {
    // 在模板中启用v-focus
    focus: {
      // 指令的定义
      mounted(el) {
        el.focus()
      }
    }
  }
}
</script>

指令钩子函数

javascript 复制代码
const myDirective = {
  // 在绑定元素的父组件被挂载前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  
  // 在元素被插入到DOM前调用
  mounted(el, binding, vnode, prevVnode) {},
  
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  
  // 在绑定元素的父组件及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

高级自定义指令示例

javascript 复制代码
// 颜色指令
app.directive('color', {
  mounted(el, binding) {
    el.style.color = binding.value
  },
  updated(el, binding) {
    el.style.color = binding.value
  }
})

// 权限指令
app.directive('permission', {
  mounted(el, binding) {
    const { value } = binding
    const roles = ['admin', 'user'] // 从store或其他地方获取用户角色
    
    if (value && value instanceof Array && value.length > 0) {
      const permissionRoles = value
      const hasPermission = roles.some(role => {
        return permissionRoles.includes(role)
      })
      
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  }
})

8. 实践

指令实践

  1. 合理选择v-if和v-show

    • 频繁切换使用v-show
    • 条件很少改变使用v-if
  2. v-for必须使用key

    vue 复制代码
    <!-- 推荐 -->
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
    
    <!-- 不推荐 -->
    <li v-for="item in items">{{ item.name }}</li>
  3. 避免v-if和v-for同时使用

    vue 复制代码
    <!-- 不推荐 -->
    <li v-for="user in users" v-if="user.isActive" :key="user.id">
      {{ user.name }}
    </li>
    
    <!-- 推荐:使用computed -->
    <li v-for="user in activeUsers" :key="user.id">
      {{ user.name }}
    </li>
  4. 事件处理器优化

    vue 复制代码
    <!-- 推荐:使用方法 -->
    <button @click="handleClick">点击</button>
    
    <!-- 不推荐:复杂的内联表达式 -->
    <button @click="items.push({ id: Date.now(), name: 'new' }), updateCount++">
      添加
    </button>

Vue.js 指令快速参考表

核心指令对照表

指令 作用 语法示例 修饰符 使用场景
v-model 双向数据绑定 <input v-model="message"> .lazy .number .trim 表单输入、自定义组件
v-bind 单向属性绑定 :src="url" :class="{active: isActive}" 动态属性、样式、类名
v-if 条件渲染(销毁/创建) <div v-if="show">content</div> 条件很少改变的元素
v-else-if 多条件渲染 <div v-else-if="type === 'A'">A</div> 多分支条件判断
v-else 否则渲染 <div v-else>default</div> 条件渲染的最后分支
v-show 条件显示(CSS display) <div v-show="visible">content</div> 频繁切换显示/隐藏
v-for 列表渲染 <li v-for="item in items" :key="item.id"> 数组、对象、数字遍历
v-on 事件监听 @click="handler" @keyup.enter="submit" .stop .prevent .once 用户交互事件处理
v-text 更新文本内容 <span v-text="message"></span> 纯文本显示
v-html 更新HTML内容 <div v-html="htmlContent"></div> 动态HTML内容
v-pre 跳过编译 <span v-pre>{``{ raw }}</span> 显示原始模板语法
v-once 只渲染一次 <h1 v-once>{``{ title }}</h1> 静态内容性能优化
v-cloak 隐藏未编译模板 <div v-cloak>{``{ message }}</div> 防止模板闪烁

常用修饰符详解

v-model 修饰符

修饰符 说明 示例
.lazy 在 change 事件触发时同步 <input v-model.lazy="msg">
.number 自动转换为数字类型 <input v-model.number="age">
.trim 自动去除首尾空格 <input v-model.trim="msg">

v-on 事件修饰符

修饰符 说明 示例
.stop 阻止事件冒泡 @click.stop="doThis"
.prevent 阻止默认行为 @submit.prevent="onSubmit"
.capture 使用事件捕获模式 @click.capture="doThis"
.self 只在事件目标是元素自身时触发 @click.self="doThat"
.once 事件只触发一次 @click.once="doThis"
.passive 立即触发默认行为 @scroll.passive="onScroll"

v-on 按键修饰符

修饰符 说明 示例
.enter 回车键 @keyup.enter="submit"
.tab Tab键 @keyup.tab="nextField"
.delete 删除键 @keyup.delete="deleteItem"
.esc Escape键 @keyup.esc="cancel"
.space 空格键 @keyup.space="play"
.up/.down/.left/.right 方向键 @keyup.up="moveUp"
.ctrl/.alt/.shift/.meta 系统修饰键 @keyup.ctrl.enter="save"

使用场景对比

v-if vs v-show

特性 v-if v-show
渲染方式 条件性渲染(真正的删除/创建) 基于CSS display切换
切换开销 高(重新渲染) 低(只改变CSS)
初始开销 低(条件为假时不渲染) 高(总是渲染)
适用场景 条件很少改变 频繁切换
生命周期 会触发组件的生命周期 不会触发生命周期

v-for 使用要点

遍历类型 语法 参数说明
数组 v-for="item in items" item: 数组元素
数组+索引 v-for="(item, index) in items" item: 元素, index: 索引
对象 v-for="value in object" value: 属性值
对象+键名 v-for="(value, key) in object" value: 值, key: 键名
对象+键名+索引 v-for="(value, key, index) in object" value: 值, key: 键名, index: 索引
数字 v-for="n in 10" n: 1到10的数字

最佳实践速查

✅ 推荐做法

  • v-for 必须使用 :key
  • 使用计算属性代替复杂的模板表达式
  • 事件处理器使用方法而非内联表达式
  • 合理选择 v-if 和 v-show
  • 使用缩写语法(: 代替 v-bind:@ 代替 v-on:

❌ 避免做法

  • v-for 和 v-if 在同一元素上使用
  • 使用数组索引作为 key(除非必要)
  • 在模板中编写复杂逻辑
  • 忘记使用事件修饰符优化性能
  • 滥用 v-html(XSS风险)

自定义指令语法

注册方式

javascript 复制代码
// 全局注册
app.directive('focus', {
  mounted(el) { el.focus() }
})

// 局部注册
directives: {
  focus: {
    mounted(el) { el.focus() }
  }
}

钩子函数

钩子 触发时机
beforeMount 绑定元素的父组件被挂载前
mounted 元素被插入到DOM后
beforeUpdate 绑定元素的父组件更新前
updated 父组件及所有子节点都更新后
beforeUnmount 绑定元素的父组件卸载前
unmounted 绑定元素的父组件卸载后

参数说明

javascript 复制代码
directive(el, binding, vnode, prevVnode) {
  // el: 绑定的元素
  // binding: { value, oldValue, arg, modifiers, instance, dir }
  // vnode: 虚拟节点
  // prevVnode: 之前的虚拟节点
}
相关推荐
伍哥的传说1 小时前
Radash.js 现代化JavaScript实用工具库详解 – 轻量级Lodash替代方案
开发语言·javascript·ecmascript·tree-shaking·radash.js·debounce·throttle
程序视点2 小时前
IObit Uninstaller Pro专业卸载,免激活版本,卸载清理注册表,彻底告别软件残留
前端·windows·后端
前端程序媛-Tian2 小时前
【dropdown组件填坑指南】—怎么实现下拉框的位置计算
前端·javascript·vue
iamlujingtao2 小时前
js多边形算法:获取多边形中心点,且必定在多边形内部
javascript·算法
嘉琪0012 小时前
实现视频实时马赛克
linux·前端·javascript
烛阴3 小时前
Smoothstep
前端·webgl
若梦plus3 小时前
Eslint中微内核&插件化思想的应用
前端·eslint
爱分享的程序员3 小时前
前端面试专栏-前沿技术:30.跨端开发技术(React Native、Flutter)
前端·javascript·面试
超级土豆粉3 小时前
Taro 位置相关 API 介绍
前端·javascript·react.js·taro
若梦plus3 小时前
Webpack中微内核&插件化思想的应用
前端·webpack