前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Vue常用指令02-v-on/v-bind/v-model/自定义指令【1小时掌握vue3系列】

Vue常用指令02-v-on/v-bind/v-model/自定义指令【1小时掌握vue3系列】

作者头像
方才编程_公众号同名
发布2024-11-18 20:26:45
发布2024-11-18 20:26:45
15200
代码可运行
举报
文章被收录于专栏:方才编程方才编程
运行总次数:0
代码可运行

本文转载自专栏《1小时掌握vue3》:https://fangcaicoding.cn/course/12/65

大家好!我是方才,目前是8人后端研发团队的负责人,拥有6年后端经验&3年团队管理经验。 系统学习践行者!近期在系统化输出前端入门相关技术文章,期望能帮大家构建一个完整的知识体系。 如果对你有所帮助,记得一键三连! 我创建了一个编程学习交流群(扫码关注后即可加入),秉持“一群人可以走得更远”的理念,期待与你一起 From Zero To Hero! 茫茫人海,遇见即是缘分!方才兄送你ElasticSearch系列知识图谱、前端入门系列知识图谱、系统架构师备考资料!

Hello,大家好!我是方才,前面已经学习条件渲染和列表渲染的指令,今天我们一口气把剩下的内容搞定。包括监听事件v-on、动态绑定v-bind、双向绑定v-model以及不常用的指令和自定义指令。

v-on 监听事件

给元素绑定事件监听器。这部分vue官方(vue官网)的内容,写得还是非常清楚的。方才兄在这里重点结合实际场景做个简单的讲解。

  • 缩写:@
  • 期望的绑定值类型:Function | Inline Statement | Object (不带参数)

类似【内联事件处理器】、【事件修饰符】、【鼠标按键修饰符】大家可以直接移步官网。方才兄在这里演示下最常用的:方法事件处理器和按键修饰符。

方法事件

基于文章列表页的示例代码,监听元素的click点击事件,语法@click="方法名" v-on:click="方法名" 都是可以的:

代码语言:javascript
代码运行次数:0
复制
<template>

  <p>文章列表总数:{{ articleList.length }}</p>
  <button @click="addArticle">@click添加文章 </button>
  <button v-on:click="addArticle">v-on:click添加文章</button>
  <div v-for="(article,index) in articleList">
      <h1>序号:{{index+1}}</h1>
      <h2>欢迎来到:{{ article.title }}</h2>
      <h4>摘要:{{ article.summary }}</h4>
      <p>作者:{{ article.author }} 阅读量:{{ article.readCt }}</p>
  </div>

</template>

<script setup>

import {ref} from "vue";

const articleList = ref([{
      title: "一小时构建Vue知识体系-default",
      summary: "关注方才兄,一小时构建Vue知识体系-default",
      author: "方才",
      readCt: 12
    }
    ]
)

const addArticle = () => {
  articleList.value.push({
    title: "一小时构建Vue知识体系-" + Math.floor(Math.random() * 100),
    summary: "关注方才兄,一小时构建Vue知识体系-3",
    author: "方才3",
    readCt: Math.floor(Math.random() * 100)
  })
}


</script>


<style scoped>

</style>

实现的效果就是,每点击一次按钮,文章列表就会新增一篇文章:

按键修复符

在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on@ 监听按键事件时添加按键修饰符。

按键修饰符主要用于自定义快捷键,比如说vue官网的ctr+k快速搜索就是基于该机制实现的。

image-20241114205835314

语法结构为:@按键行为.按键名称,比如说@keydown.enter,当enter键被按下时触发。

  • 按键修饰符有两个按键行为事件可选,一个是按下按键时触发keydown ,一个是松开按键时触发 keyup;
  • 按键名称:Vue 为一些常用的按键提供了别名,其他26个按键以名称为准: 系统按键修饰符:
    • .ctrl
    • .alt
    • .shift
    • .meta
    • .enter
    • .tab
    • .delete (捕获“Delete”和“Backspace”两个按键)
    • .esc
    • .space
    • .up
    • .down
    • .left
    • .right

简单演示下keydownkeyup的区别,参考示例代码:

代码语言:javascript
代码运行次数:0
复制
<!--  省略了和上面代码重复的代码,方便阅读 -->
<input type="text" placeholder="按下a键 添加文章" @keydown.a="addArticle"/>
  <br/>
  <br/>
  <input type="text" placeholder="松开a键 添加文章" @keyup.a="addArticle"/>

效果如下,对于@keyup.a如果一直按住a键,虽然输入框会输入多个字符,但只会调用一次添加文章的方法,因为a键只被松开了一次;

对于@keydown.a如果一直按住a键,输入框会输入多少个a字符,就会调用多少次添加文章的方法,一直按住a键就等于不停的按a键。

组合快捷键,比如说ctrl+a

代码语言:javascript
代码运行次数:0
复制
  <input type="text" placeholder="组合键ctrl+a键 添加文章" @keyup.ctrl.a="addArticle"/>
注意事项

按键修饰符的自定义快捷键,默认情况下,仅当绑定快捷键的元素获得焦点时,键盘事件才能被正确的触发。@keyup.ctrl.a的效果示例,包括@keydown.a@keyup.a也是一样的:

若需要在当前页面全局实现快捷键,需要做一些额外的实现。方才兄在这里演示基于 window.addEventListener 全局监听快捷键的方式实现一个demo(若全局快捷键比较多,可以考虑使用 Vue 插件 vue-shortkey):

  • <script setup>标签中,基于vue的生命周期函数onMounted 注册监听按键事件 window.addEventListener('keydown', handleKeydown);
  • 在回调函数handleKeydown中,去实现我们预期的快捷键行为。
代码语言:javascript
代码运行次数:0
复制
<script setup>

...

onMounted(() => {
  console.log("mounted")
  // 在全局监听键盘事件
  window.addEventListener('keydown', handleKeydown);
})
const handleKeydown = (event) => {
  if (event.key === 'a' && event.ctrlKey) {
    // 阻止默认行为,例如浏览器的快捷键
    event.preventDefault();
    addArticle();
  }
}

</script>

现在就可以在当前页面使用ctr+a快速添加文章了,不需要聚焦到input组件中。

大家也可以访问方才兄的博客,搜索快捷 ctr+k就是基于该机制实现的,体验下效果。

扩展下

了解了以上内容,不知道大家有没有灵光乍现的感觉。我们基于自定义快捷键和全局监听机制,是可以实现很多自定义功能的。

比如说基于该机制,去修改一些默认的快捷键的行为。

一个例子:博客文章内容,在用户未登录时,ctrl+c 默认弹出登录页,阻塞默认的复制行为(效果这里就不截图了,有兴趣的可以去站点测试:https://fangcaicoding.cn/course/12)。

代码语言:javascript
代码运行次数:0
复制
<script setup>
onMounted(() => {
  console.log("mounted")
  // 在全局监听键盘事件
  window.addEventListener('keydown', handleKeydown);
})
const handleKeydown = async (event) => {
  // 监听 ctrl+k 打开搜索框
  if (event.key === 'c' && event.ctrlKey) {
    if (!userStore.isLogin()) {
      // 阻止默认行为,例如浏览器的快捷键
      event.preventDefault();
      // 打开登录对话框
      toLogin();
    } else {
      ElMessage.success('内容复制成功!')
    }
  }
}
</script>

其他仅了解的内容

系统修饰符

系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。

  • .ctrl
  • .alt
  • .shift
  • .meta

举例来说:

代码语言:javascript
代码运行次数:0
复制
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>

请注意,系统按键修饰符和常规按键不同。与 keyup 事件一起使用时,该按键必须在事件发出时处于按下状态。换句话说,**keyup.ctrl 只会在你仍然按住 ctrl 但松开了另一个键时被触发。若你单独松开 ctrl 键将不会触发。**

.exact 修饰符

.exact 修饰符允许精确控制触发事件所需的系统修饰符的组合。

代码语言:javascript
代码运行次数:0
复制
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

v-bind 动态绑定

绑定属性

动态的绑定一个或多个 attribute,也可以是组件的 prop。用于绑定 classstyle或组件的 attribute

  • 缩写:
    • : 或者 . (当使用 .prop 修饰符)
    • 值可以省略 (当 attribute 和绑定的值同名时,需要 3.4+ 版本)
  • 期望:any (带参数) | Object (不带参数)
  • 参数:attrOrProp (可选的)

就以绑定图片地址为例,<img :src="article.imgUrl" alt="图片" style="width: 100px" />

代码语言:javascript
代码运行次数:0
复制
  <div v-for="(article,index) in articleList">
    <h1>序号:{{ index + 1 }}</h1>
    <img :src="article.imgUrl" alt="图片" style="width: 100px" />
    <h2>欢迎来到:{{ article.title }}</h2>
    <h4>摘要:{{ article.summary }}</h4>
    <p>作者:{{ article.author }} 阅读量:{{ article.readCt }}</p>
  </div>

官方也提供了示例:

代码语言:javascript
代码运行次数:0
复制
<img v-bind:src />

<!-- 缩写形式的动态 attribute 名 (3.4+),扩展为 :src="src" -->
<img :src />
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

注意,如果你不用v-bind,程序会认为你写的内容是一个文件:

<img src="article.imgUrl" alt="图片" style="width: 100px" />

所以你的图片是本地资源,是不需要v-bind的。示例:

代码语言:javascript
代码运行次数:0
复制
    <h2>本地图片</h2>
    <img src="./assets/img.png" alt="图片" style="width: 100px" />

绑定Html css

v-bind还可以用于动态绑定样式。这个日常使用频率也是比较高的。就以博客的教程列表来说,针对当前查看的文章列表实现一个特殊的选中样式,就用到了该机制。

我们可以给 :class (v-bind:class 的缩写) 传递一个对象来动态切换 class:

代码语言:javascript
代码运行次数:0
复制
<div :class="{ activeClass: isActive }"></div>

上面的语法表示 activeClass 是否存在 取决于数据属性 isActive 的真假值。

我们可以在对象中写多个字段来操作多个 class。此外,:class 指令也可以和一般的 class attribute 共存。

方才兄这里还是以文章列表的渲染为例,期望有一个默认的通用样式,同时奇偶行有不同的样式:

代码语言:javascript
代码运行次数:0
复制
  <p>文章列表总数:{{ articleList.length }}</p>
  <button @click="addArticle">添加文章</button>
  <div v-for="(article,index) in articleList">
    <p class="defaultClass" :class="(index+1) % 2 === 0? 'evenClass' : 'oddClass'">序号:{{ index + 1 }}</p>
  </div>

配合以下样式:

代码语言:javascript
代码运行次数:0
复制
<style scoped>
.defaultClass {
  font-size: 16px;
  color: black;
}
.evenClass{
  color: red;
}
.oddClass{
  color: green;
}
</style>

渲染的结果会是:

代码语言:javascript
代码运行次数:0
复制
/* 偶数的样式 */
<p class="defaultClass evenClass"></p>
/* 奇数的样式 */
<p class="defaultClass oddClass"></p>

绑定内联样式

以下内容,方才兄就直接引用官网的内容,做一个简单介绍

:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:

代码语言:javascript
代码运行次数:0
复制
const activeColor = ref('red')
const fontSize = ref(30)
代码语言:javascript
代码运行次数:0
复制
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

ps:样式中,加载响应式数据,可以用于干嘛呢?头像绘制,比如说飞书的群头像,允许用户选择背景色和输入文字,就可以基于该机制实现。

尽管推荐使用 camelCase,但 :style 也支持 kebab-cased 形式的 CSS 属性 key (对应其 CSS 中的实际名称),例如:

代码语言:javascript
代码运行次数:0
复制
<div :style="{ 'font-size': fontSize + 'px' }"></div>

直接绑定一个样式对象通常是一个好主意,这样可以使模板更加简洁:

代码语言:javascript
代码运行次数:0
复制
const styleObject = reactive({
  color: 'red',
  fontSize: '30px'
})

template

代码语言:javascript
代码运行次数:0
复制
<div :style="styleObject"></div>

同样的,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。

v-model 双向绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦:

代码语言:javascript
代码运行次数:0
复制
<input
  :value="text"
  @input="event => text = event.target.value">

v-model 指令帮我们简化了这一步骤:

代码语言:javascript
代码运行次数:0
复制
<input v-model="text">

v-model 指令的作用就是在表单输入元素或组件上创建双向绑定。

什么叫双向绑定呢?两个对象:响应式数据变量 和 表单组件的值,双向绑定:互相影响,更改其中一个对象的值,另一个对象的值也会变更。

方才兄这里以文章信息的表单编辑为例,来体验下“双向绑定”:

  • 使用input标签修改文章的标题。
代码语言:javascript
代码运行次数:0
复制
<template>

  <h1>文章详情展示</h1>
  <h2>欢迎来到:{{ article.title }}</h2>
  <h4>摘要:{{ article.summary }}</h4>
  <p>作者:{{ article.author }} 阅读量:{{ article.readCt }}</p>

  <h1 style="color:red;">编辑文章</h1>
  标题: <input type="text" v-model="article.title" placeholder="edit me"/><br>
  <button @click="saveArticle">保存文章</button>
</template>

<script setup>

import {ref} from "vue";

const article = ref({
      title: "一小时构建Vue知识体系-default",
      summary: "关注方才兄,一小时构建Vue知识体系-default",
      author: "方才",
      imgUrl: "https://fangcaicoding.cn/oss/cover/css_learn.png",
      readCt: 12
    }
)

const saveArticle = () => {
  alert(article.value.title + "  文章已保存!")
}

</script>

<style scoped>
input, textarea {
  width: 500px;
  height: 50px;
  padding: 5px;
  margin-bottom: 10px;
}
</style>

效果如下:

  • 初始页面,输入框的标题和文章详情的内容一致,响应式数据成功渲染了表单输入框组件的值;
  • 当我们在输入框中修改标题时,文章详情的展示也会实时被渲染出来,说明单输入框组件的值也反向更新了响应式变量的值,点击保存按钮,文章标题也是被修改之后的。
  • 这就是双向绑定!

v-model和表单的其他组件使用也是类似的,方才兄在这里就不冗余展示了,后续我们会基于element-plus去实现博客系统的各种功能,在实践中去学习更佳!

若想要提前了解其他组件的绑定,可以直接阅读vue官网:https://cn.vuejs.org/guide/essentials/forms.html。

仅需要了解的指令

可以简单浏览下官网:https://cn.vuejs.org/api/built-in-directives,了解所有的内置指令。

  • v-pre:元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。
  • v-once:仅渲染元素和组件一次,并跳过之后的更新。
  • v-memo:缓存一个模板的子树。在元素和组件上都可以使用。v-memo 仅用于性能至上场景中的微小优化,应该很少需要。

高级使用-自定义指令

Vue 支持用户注册自定义的指令 (Custom Directives)。

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。

一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。

一个指令的定义对象可以提供几种钩子函数 (都是可选的):

代码语言:javascript
代码运行次数:0
复制
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) {}
}

钩子参数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。
  • binding:一个对象,包含以下属性。
    • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。
  • prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

懒加载图片的示例

我们可以基于自定义指令来实现一个懒加载图片的指令。

  • 自定义指令ImgLazyLoad.js
代码语言:javascript
代码运行次数:0
复制
export default {
    mounted(el, binding) {
        const options = {
            root: null, // 视口作为根
            rootMargin: '0px', // 不设置外边距
            threshold: 0.1, // 元素进入视口 10% 时触发
        };

        // 设置占位符图片(避免在网络加载完成前显示空白)
        el.setAttribute(
            'src',
            'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
        );

        const loadImage = () => {
            const imageSrc = binding.value; // 获取真实图片地址
            if (imageSrc) {
                el.src = imageSrc; // 设置真实图片地址
                el.dataset.loaded = 'true'; // 标记为已加载
                observer.unobserve(el); // 停止观察
            }
        };

        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    loadImage(); // 进入视口时加载真实图片
                }
            });
        }, options);

        observer.observe(el); // 开始观察元素
    },
};
  • main.js 中注册 v-lazy-load指令:
代码语言:javascript
代码运行次数:0
复制
import { createApp } from 'vue'
import App from './App.vue'
import lazyLoadDirective from './components/directives/ImgLazyLoad.js';

createApp(App)
    .directive('lazy-load', lazyLoadDirective)
    .mount('#app')
  • App.vue使用自定义指令:
代码语言:javascript
代码运行次数:0
复制
<template>
  <img
      v-for="(image, index) in images"
      :key="index"
      v-lazy-load="image.src"
      alt="Blog Image"
      class="lazy-image"
  />
</template>

<script setup>
import {ref} from 'vue';

const images = ref([
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113225240775.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113192259847.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241021225853769.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241021225853769.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113220459108.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113191757798.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113222135766.png'},
  {src: 'https://fangcaicoding.cn/oss/assets/14-Vue%E6%9C%80%E5%B8%B8%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4/image-20241113191757798.png'},
])
</script>

<style>
.lazy-image {
  width: 100%;
  height: auto;
  opacity: 0; /* 初始透明度为0,隐藏图片以便在加载后显示 */
  transition: opacity 0.5s ease-in-out; /* 添加过渡效果,图片透明度变化会在 0.5秒内平滑进行 */
}

/* 定义当图片加载完成后应用的样式 */
.lazy-image[data-loaded] {
  opacity: 1; /* 将透明度设置为1,显示图片 */
}

</style>

运行效果如下,最初只加载第一张图片,随着滚动条的下拉,逐步加载出现在视图中的图片文件:

可以打开【开发者工具】,查看【network】的图片加载顺序图,体验懒加载的效果:

总结

以上,就是vue常用的指令,截止目前,我们已经掌握了vue的工程化构建、生命周期函数以及常用的内置指令,我相信大家已经可以基于单文件组件去完成一些项目的开发了。

后续方才兄将继续分享vue3的组件化,将重复代码封装为组件,提升代码的可维护性。欢迎大家点赞关注。


近期更新计划

近期更新计划(有需要的小伙伴,记得点赞关注哟!)

  1. 输出vue、router、elementplus等前端框架技术文章,期望能帮助大家快速建立相关的知识体系;
  2. 基于vue3+springboot3的前后端分离的博客系统已经开源啦,欢迎大家star!https://gitee.com/fangcaicoding/fangcai-coding-blog

“学编程,一定要系统化”——若你也是系统学习的践行者,记得点赞关注,期待与你一起 From Zero To Hero!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 方才编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • v-on 监听事件
    • 方法事件
    • 按键修复符
      • 注意事项
      • 扩展下
    • 其他仅了解的内容
      • 系统修饰符
      • .exact 修饰符
  • v-bind 动态绑定
    • 绑定属性
    • 绑定Html css
    • 绑定内联样式
  • v-model 双向绑定
  • 仅需要了解的指令
  • 高级使用-自定义指令
    • 钩子参数
    • 懒加载图片的示例
  • 总结
  • 近期更新计划
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档