# Vue 基础 - 模板语法、事件处理、计算属性、侦听器

TIP

从本节内容开始,正式学习 Vue 的核心基础,模板语法、事件处理、 计算属性、侦听器从入门到实际项目开发中的最佳实践。这也是我们完成 Vue 项目开发前的必备技能 !

# 一、模板语法

TIP

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。

最基本的数据绑定形式是文本插值,其次是内置的指令。

# 1、文本插值

TIP

文本插值语法,使用的是 “Mustache” 语法(即双大括号)

<div>{{message}}</div>

代码演示

以上代码中{{}}语法,最终会被替换为对应组件实例中 message 属性的值。同时每次 message 属性值更改时,页面也会同步更新。

<script>
  export default {
    data() {
      return {
        message: "Hello Vue",
      };
    },
  };
</script>

<template>
  <div>{{ message }}</div>
</template>

渲染后,对应 HTML 代码如下

<div>Hello Vue</div>

# 1.1、文本插值使用范围

TIP

文本插值用于解析标签体内容,所以只能出现在标签体内。

<!--以下写法是错误的 , 因为文本插值语法出现在标签属性中-->
<div title="{{message}}"></div>

代码演示

<script>
export default {
  data() {
    return {
      message: "Hello Vue",
    };
  },
};
</script>

<template>
  <div title="{{ message }}">{{ message }}</div>
</template>

渲染后,对应 HTML 代码如下

<div title="{{message}}">Hello Vue</div>

注:

我们发现title属性中的并没有被解析,而是原样输出了。

要动态绑定一个属性值值,需要用到v-bind指令,后面会讲到

# 1.2、绑定数据格式

TIP

  • {{}} 中绑定的数据只支持单一表达式,也就是一段能够被求值的 JavaScript 代码
  • 是否为单一表达式,最简单的判断方法是,是否可以合法地写在 return 后面

以下写法,都可以看做是一个单一表达式

<!-- message是一个变量 -->
<div>{{ message }}</div>
<!--a + b 数学运算 或 1 > 2 -->
<div>{{ a + b }}</div>
<!-- fn() 函数调用 -->
<div>{{ fn() }}</div>
<!--三元表达式 -->
<div>{{ ok ? 'YES' : 'NO' }}</div>
<!--对象.方法()-->
<div>{{ message.split('').reverse().join('') }}</div>

以下写法,不是表达式,是属于 JS 语句,不能出现在{{}}

if(){}
for(var i=0;i<5;i++){}
switch(){ }

# 1.3、受限的全局访问

TIP

<!-- 显示日期 结果:当前日期 -->
<div>{{ new Date() }}</div>
<!-- 开平方 结果: 2-->
<div>{{ Math.sqrt(4) }}</div>
<!-- 正则测试,结果:true-->
<div>{{ /^\d{5}$/.test("12345") }}</div>

# 1.4、访问自定义全局属性

TIP

  • app.config.globalProperties一个用于注册能够被应用内所有组件实例访问到的全局属性的对象
  • 在模板中,通过文本插值{{}}可以访问到全局属性
// 注册自定义全局属性
app.config.globalProperties.username = "艾编程";

代码演示

  • main.js中通过app.config.globalProperties.username添加全局username属性
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.config.globalProperties.username = "艾编程";
app.mount("#app");
  • App.vue文件的模板中,可以使用文本插值访问该属性值,如下:
<template>
  <div>{{ username }}</div>
</template>

渲染后,对应 HTML 代码如下:

<div>艾编程</div>

# 1.5、总结:文本插值

TIP

在 Vue 模板中,需要访问到组件实例属性,可以采用文本插值 {{}}语法

文本插值使用范围

文本插值语法用于解析标签体内容,所以{{}}只能出现在标签体内,出现在其它地方不会被解析

绑定数据格式

  • 文本插值{{}}大括号中只支持单一的 JS 表达式
  • 判断是否为单一表达式,最简单的方法是,是否可以合法地写在 return 后面

在文本插值{{}}中可以访问到以下内容

  • 组件实例属性
  • 有限的全局对象
  • 自定义全局属性

# 2、指令语法

TIP

  • 在 Vue 中,指令是带有 v- 前缀的特殊 attribute(属性)
  • Vue 提供了许多内置指令,如:v-bindv-modelv-htmlv-textv-onv-ifv-showv-for 等。

# 3、v-bind 指令

TIP

v-bind指令,用于动态的绑定一个 或 多个属性

<div v-bind:title="message"></div>

注:

  • 以上v-bind命令指示 Vue 将div元素的title属性值与组件的message属性值保持一致
  • 如果message的值是 null 或者 undefined,那么title属性将会从渲染的元素上移除

代码演示

<script>
export default {
  data() {
    return {
      message: "Hello Vue",
      id: null,
    };
  },
};
</script>

<template>
  <div v-bind:title="message" v-bind:id="id">{{ message }}</div>
</template>

最终渲染后生成的 HTML 代码如下:

<div title="Hello Vue">Hello Vue</div>

因为id属性的值为 null,所以该属性最终不会出现在渲染的元素上。

# 3.1、v-bind 简写

TIP

v-bind的应用非常多,所以官方为v-bind提供了特定的简写语法。

如:v-bind:title="" 简写为 :title=""

<div v-bind:title="message"></div>
<!-- 以上 v-bind:title 简写成 :title -->
<div :title="message"></div>

# 3.2、v-bind 绑定布尔型属性

TIP

如果元素原生支持某些布尔类型的属性,v-bind针对布尔类型属性, 依据 true / false 值来决定属性是否应该存在于该元素上

如果不是元素原生支持的,则v-bind绑定的自定义属性的值为布尔类型,则正常显示该布尔值。

<!--isButtonDisabled属性的值,如果为false表示移除这个属性,true表示按扭禁用 -->
<button :disabled="isButtonDisabled">Button</button>

<!--isChecked属性的值,如果为false表示不选中,true表示选中-->
<input type="checkbox" :checked="isChecked" />

<!--isInputDisable属性的值,如果为false表示可输入,true表示禁用,不能输入-->
<input type="text" :disabled="isInputDisabled" />

代码演示

<script>
export default {
  data() {
    return {
      isButtonDisabled: false,
      isChecked: true,
      isInputDisabled: true,
      bool: true,
    };
  },
};
</script>

<template>
  <!--原生支持的布尔类型属性-->
  <button :disabled="isButtonDisabled">Button</button>
  <input type="checkbox" :checked="isChecked" />
  <input type="text" :disabled="isInputDisabled" />
  <!--自定义布尔类型属性-->
  <div :bool="bool"></div>
  <!--
		扩展知识:
            如果元素的某个自定义属性是布尔属性,则一般会采用如下简写
            <div bool></div>   表示有bool属性
            <div></div>  表示没有bool属性
	-->
</template>

以上代码最终渲染后效果如下:

image-20230612161826069

# 3.3、v-bind 动态绑定多个属性

如果你有像这样的一个包含多个属性的 JavaScript 对象

data() {
  return {
    objectOfAttrs: {
      id: 'container',
      class: 'wrapper'
    }
  }
}

可以通过不带参数v-bind,将他们一次性绑定到某个元素上。

<div v-bind="objectOfAttrs"></div>

v-bind绑定方式,编译后的 html 结构如下:

<div id="container" class="wrapper"></div>

# 3.4、绑定数据格式

TIP

  • v-bind绑定的数据格式与上面提到的文本插值{{}}是一样,只支持单一表达式
  • 大部分指令属性后面的期望值是为一个表达式(除了少数几个例外,如:v-forv-onv-slot
<div :title="1 + 3" :data-date="new Date().getTime()"></div>
<button :disabled="3 > 2 ? true : false">提交</button>

# 3.5、动态参数

TIP

  • v-bind:后面跟着的标识,我们管他叫指令的参数,比如上面提到title, id, class 属性都可以看做是参数。
  • 如果需要动态指定参数,则需要把参数放在一对方括号内,方括内可以使用一个 JS 表达式,表达式最终返回一个值。
<a v-bind:[attributeName]="url"> ... </a>
<!--简写-->
<a :[attributeName]="url"> ... </a>

代码演示

  • App.vue文件内容如下
<script>
export default {
  data() {
    return {
      attributeName: "href",
      url: "http://www.icoding.com",
    };
  },
};
</script>

<template>
  <a :[attributeName]="url">艾编程</a>
</template>

编译成后,生成如下 HTML,插入到#app容器中

<a href="http://www.icoding.com">艾编程</a>

# 3.6、动态参数注意事项

  • 动态参数值的限制

动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。

以下代码是不合法的,会抛出一个错误,因为参数中表达式的值是一个数字类型。

<script>
  export default {
    data() {
      return {
        attributeName: 123,
        url: "http://www.icoding.com",
      };
    },
  };
</script>

<template>
  <a :[attributeName]="url">艾编程</a>
</template>
  • 动态参数语法的限制

动态参数表达式因为某些字符的缘故有一些语法限制,比如空格引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:

<!-- 以下写法都会抛出一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>

如果你需要传入一个复杂的动态参数,推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。

# 3.7、总结:v-bind 指令

TIP

v-bind 指令用于动态绑定一个或多个属性,v-bind指令的可以简写成:

  • v-bind绑定属性的值为 null,最终该属性不会出现在元素上
  • v-bind针对原生布尔类型属性, 依据 true / false 值来决定属性是否应该存在于该元素上
  • v-bind绑定数据格式只支持单一表达式
  • v-bind指令可以动态指定参数,参数支持单一表达式,但有以下两个限制
    • 动态参数中表达式的值应当是一个字符串,或者是 null
    • 动态参数表达式因为某些字符的缘故有一些语法限制,比如空格引号

# 二、事件处理 与 methods

TIP

本小节我们将重点学习以下内容:

  • 如何监听事件
  • 事件处理器的两种值:内联事件处理器、方法事件处理器
  • methods 方法
  • 区分方法事件处理器与内联事件处理器
  • 方法和内联事件处理器中访问 event 事件对象
  • 事件修饰符
  • 按键修饰符
  • 系统按键修饰符
  • .exact修饰符
  • 鼠标键盘修饰符
  • 事件绑定总结

# 1、监听事件

TIP

我们可以使用v-on指令(简写为@)来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。

用法

  • 监听事件: v-on:click="handler"@click="handler"
  • handler为事件处理器,事件触发后,就会触发事件处理器来执行 JS 代码
<script>
  export default {
    data() {
      return {
        count: 0,
      };
    },
  };
</script>

<template>
  <div>{{ count }}</div>
  <!-- 点击按扭,属性 count 的值加1 -->
  <button v-on:click="count++">Add 1</button>
  <button @click="count++">Add 1</button>
</template>

以上代码最终渲染效果如下:

GIF2023-6-1217-22-32

# 2、事件处理器

TIP

事件处理器 (handler) 的值可以是:

  • 内联事件处理器:事件被触发时执行的内联 JavaScript 语句
  • 方法事件处理器:一个指向组件上定义的方法的属性名或是路径(如:obj.fn

# 2.1、内联事件处理器

TIP

当我们在触发事件时,只需要执行一些简单的 JS 语句时,可以采用内联事件处理器

<script>
export default {
  data() {
    return {
      count: 0,
      isShow: true,
    };
  },
};
</script>

<template>
  <!-- 点击按扭 count+1 -->
  <button @click="count++">Add 1</button>
  <p>Count is: {{ count }}</p>

  <!-- 点击按扭,对isShow属性值取反 false变true,true变false-->
  <button @click="isShow = !isShow">{{ isShow }}</button>
</template>

最终渲染效果如下:

GIF2023-6-1217-32-05

注:

  • 点击第一个button按扭时,会执行count++
  • 点击第二个按扭时,会对变量isShow的值取反

# 2.2、方法事件处理器

TIP

方法事件处理器:一个指向组件上定义的方法的属性名或路径

<script>
export default {
  data() {
    return {
      state: {
        show() {
          console.log("Hello Vue3!!");
        },
      },
    };
  },
  // methods选项
  methods: {
    // 这里的方法,可以直接在模板中使用
    sayHello() {
      console.log("Hello Vue");
    },
  },
};
</script>

<template>
  <!--方法事件处理器:  指向组件上定义的方法的属性名-->
  <button class="box" @click="sayHello">sayHello</button>

  <!-- 方法事件处理器: 指向路径 -->
  <button @click="state.show">show</button>
</template>

以上代码最终渲染效果如下:

GIF2023-6-1217-42-49

注:

  • 当点击sayHello按扭时,会触发click事件,然后执行methods选项中的sayHello方法。
  • 当点击show按扭时,会触发click事件,然后执行state.show方法。

# 3、methods 方法

TIP

methods方法,用于声明要混入到组件实例中的方法

  • 声明的方法可以直接通过组件实例访问,或者在模板语法表达式中使用
  • 声明的方法(非箭头函数)内部的this指向为组件实例
  • 即:methods 选项中声明的方法内可以通过 this.属性 的方式,访问methods选项中的其它方法和 data 方法返回对象中的属性

注意

methods选项中声明的方法时避免使用箭头函数,因为它们不能通过 this 访问组件实例。

代码演示

<script>
export default {
  data() {
    return {
      message: "Hello Vue",
    };
  },
  // 组件实例需要用到的方法在methods选项中声明
  methods: {
    showMessage() {
      return this.message;
    },
    sayHello() {
      // this指向组件实例
      console.log(this);
      // 访问 data中属性
      console.log(this.message);
      // 访问methods中声明的方法
      console.log(this.sshoMessage());
    },
  },
};
</script>

<template>
  <div>{{ showMessage() }}</div>
  <div class="box" @click="sayHello"></div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: skyblue;
}
</style>

以上代码最终渲染效果如下:

image-20230425201901504

# 4、区分方法与内联事件处理器

TIP

Vue 内部模板编译器会通过检查 v-on 的值来断定是何种形式的事件处理器。

  • 值是合法的合法的 JS 标识符或属性访问路径,会被视为方法事件处理器,如:foofoo.barfoo['bar']
  • 值不是合法标识符,会被视为内联事件处理器,如:而 foo()count++
<!--方法事件处理器-->
<div class="box" @click="sayHello"></div>
<!--内联事件处理器-->
<div class="box" @click="sayHello()"></div>

# 5、内联事件处理器中访问 event 事件对象

TIP

有时我们需要在内联事件处理器中访问原生 DOM 事件,你可以向该处理器方法传入一个特殊的 $event 变量。

<div class="box box1" @click="print(`$event为事件对象`, $event)"></div>

注:

  • $event变量,相当于原生事件处理函数中的第一个参数event(事件对象)
  • $evnet变量的书写位置没要求,但名字是固定的。

代码演示

<script>
  export default {
    methods: {
      print(message, event) {
        // event 为原生的JS事件对象
        console.log(message, event);
        // event.target 触发事件的目标元素
        console.log("触发事件的目标元素", event.target);
      },
    },
  };
</script>

<template>
  <div class="box" @click="print(`鼠标事件对象`, $event)"></div>
</template>

<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
</style>

.box元素触发点击事件后,在控制台输出如下内容:

image-20230612181216876

# 6、方法事件处理器中访问 event 事件对象

TIP

对于方法事件处理器,处理器方法默认会把event事件对象作为方法的第一个参数传入

<!-- print 方法调用后,他的第一个参数就是evnet事件对象-->
<div class="box box1" @click="print"></div>

代码演示

<script>
  export default {
    methods: {
      print(event) {
        // event 为原生的JS事件对象
        console.log(event);
        // event.target 触发事件的目标元素
        console.log("触发事件的目标元素", event.target);
      },
    },
  };
</script>

<template>
  <div class="box" @click="print"></div>
</template>

<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
</style>

.box元素上点击后,在控制输出内容如下:

image-20230612181559311

# 7、事件绑定对象写法

TIP

如果我们想在一个元素上绑定多个事件,我们可以写多个v-on@来监听不同事件

<!-- 普通写法,绑定多个事件 -->
<div @click="scale" @mouseover="bgcolor"></div>

也可以采用如下对象的写法

<!-- 对象语法  绑定多个事件 click与 mouseover事件-->
<div v-on="{ click: scale, mouseover: bgcolor }"></div>

注意:

当使用对象语法时,不支持后面讲的任何事件修饰符

代码演示

<script>
export default {
  methods: {
    scale(e) {
      e.target.style.transform = "scale(1.2)";
    },
    bgcolor(e) {
      e.target.style.backgroundColor = "red";
    },
  },
};
</script>

<template>
  <!-- 普通写法,绑定多个事件 -->
  <div class="box" @click="scale" @mouseover="bgcolor"></div>
  <!-- 对象语法,绑定多个事件 -->
  <div class="box" v-on="{ click: scale, mouseover: bgcolor }"></div>
</template>
<style>
.box {
  width: 100px;
  height: 100px;
  margin: 50px;
  background-color: skyblue;
}
</style>

以上代码表示,当鼠标滑动到元素时背景变红,在元素上点击时,放大 1.2 倍,如下:

GIF2023-4-2716-53-04

# 8、事件修饰符

TIP

Vue 为v-on指令提供了事件修饰符,修饰符帮我们处理了许多 DOM 事件的细节,让我们有更多的精力专注于数据的逻辑处理。

修饰符是用 . 表示的指令后缀,事件修饰符包含以下这些:

事件修饰符 说明
.stop 阻止事件冒泡
.prevent 阻止事件的默认行为
.self 只有 event.target是元素本身时才会触发事件处理器。即事件是由元素本身触发,而非冒泡触发
.capture 添加事件监听器时,使用 capture 捕获模式
.once 最多触发一次处理函数
.passive 滚动事件的默认行为 (scrolling) ,将立即发生而非等待 onScroll 完成

用法

<!--阻止事件冒泡-->
<div class="box" @click.stop="doThis">box</div>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<div @click.self="doThat">...</div>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

注:

  • 修饰符可以使用链式书写
  • 可以只有修饰符
  • 使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。

因此使用 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为,而 @click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。

# 8.1、stop 修饰符

TIP

stop 事件修饰符,用来阻止事件冒泡

<script>
export default {
  methods: {
    boxFn1() {
      alert("box1");
    },
    boxFn2() {
      alert("box2");
    },
    boxFn3() {
      alert("box3");
    },
  },
};
</script>

<template>
  <div class="box1" @click="boxFn1">
    box1
    <div class="box2" @click="boxFn2">
      box2
      <div class="box3" @click.stop="boxFn3">box3</div>
    </div>
  </div>
</template>

<style>
.box1 {
  width: 300px;
  height: 300px;
  padding: 50px;
  background-color: tomato;
}

.box2 {
  width: 200px;
  height: 200px;
  padding: 50px;
  background-color: khaki;
}

.box3 {
  width: 100px;
  height: 100px;
  background-color: skyblue;
}
</style>

image-20230426171437865

注:

以上代码运行后,当在box3上点击后,因为阻止了事件冒泡,所以只会弹出box3

当在box2上点击后,因为没有阻止事件冒泡,所以会弹出box2box1

# 8.2、prevent 修饰符

TIP

prevent事件修饰符,用来阻止事件的默认行为

<script>
  export default {
    methods: {
      onsubmit() {
        alert("这里可以通过发送ajax请求来提交数据");
      },
    },
  };
</script>

<template>
  <!-- 点击按扭提交事件将不会发生页面的刷新与跳转 -->
  <form action="http://www.icodingedu.com" @submit.prevent="onsubmit">
    <input type="submit" value="提交" />
  </form>
  <a href="http://www.icodingedu.com" @click.prevent> 艾编程</a>
</template>

注:

当我们点击提交按扭和艾编程时,页面并不会发生跳转,因为.prevent修饰符阻止了事件的默认行为。

# 8.3、self 修饰符

TIP

只有 event.target是元素本身时才会触发事件处理器,也就是只有点击元素本身时才会触发对应事件,不支持子元素事件通过冒泡到自身。

<script>
export default {
  methods: {
    boxFn1(event) {
      console.log(event.target.className);
    },
  },
};
</script>

<template>
  <div class="box1" @click.self="boxFn1">
    box1
    <div class="box2">box2</div>
  </div>
</template>

<style>
.box1 {
  width: 200px;
  height: 200px;
  padding: 50px;
  background-color: tomato;
}

.box2 {
  width: 100px;
  height: 100px;
  padding: 50px;
  background-color: khaki;
}
</style>

image-20230426175502076

# 8.4、capture 修饰符

TIP

capture修饰符表示,添加事件监听器(原生addEventListener)时,使用 capture 捕获模式

<script>
export default {
  methods: {
    boxFn1() {
      alert("box1");
    },
    boxFn2() {
      alert("box2");
    },
    boxFn3() {
      alert("box3");
    },
  },
};
</script>

<template>
  <div class="box1" @click="boxFn1">
    box1
    <div class="box2" @click.capture="boxFn2">
      box2
      <div class="box3" @click="boxFn3">box3</div>
    </div>
  </div>
</template>

<style>
.box1 {
  width: 300px;
  height: 300px;
  padding: 50px;
  background-color: tomato;
}

.box2 {
  width: 200px;
  height: 200px;
  padding: 50px;
  background-color: khaki;
}

.box3 {
  width: 100px;
  height: 100px;
  background-color: skyblue;
}
</style>

image-20230426171437865

注:

因为box2的点击事件是在捕获阶段执行的,所以在box3上点击后,会选弹出box2,再box3box1

# 8.5、once 修饰符

TIP

once修饰符表示最多触发一次处理函数。

相当于先通过addEventListener添加事件监听,执行一次后,通removeEventListener方法取消了事件监听。

<script>
export default {
  methods: {
    doThis(e) {
      alert(e.target.className);
    },
  },
};
</script>

<template>
  <div class="box" @click.once="doThis"></div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: skyblue;
}
</style>

image-20230426181144395

# 8.6、passive 修饰符

TIP

.passive修饰符,是用来告诉浏览器你没有阻止事件的默认行为。

你可能会想,不阻止事件的默认行为不加.prevent 修饰符就好了,为什么要加.passive修饰符呢 ?

这里,不想阻止事件的默认行为,真正的目的是告诉浏览器,你可以不用去查询程序有没有阻止默认事件,也就是提前告诉浏览器程序不会阻止。

如果不提前告知,每次事件产生,浏览器都会去查询是否由preventDefault()阻止该次事件的默认动作。每次使用内核线程查询preventDefault()会使滑动卡顿,使用.passive修饰符跳过内核线程查询,可以大大的提高流畅度。

.passive提前告诉浏览器不想阻止事件默认行为的目的是提高性能,减少移动端滑动卡顿问题

一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能 (opens new window)

注意:

请勿同时使用 .passive.prevent,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。

如果你这么做了,则 .prevent 会被忽略,并且浏览器会抛出警告。

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成
	也就是并不需要在执行事件时,发现内部没有阻止默认行为,再执行默认行为,
	同时如果内部调用了event.preventDefault()方法阻止默认行为,也会被忽略掉。
-->
<div @scroll.passive="onScroll">...</div>

# 9、按键修饰符

TIP

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

以下是常用的按键修饰符

按键修饰符 说明
.enter 当按下enter回车键时,才调用事件处理函数
.tab 当按下tab键时,才调用事件处理函数
.delete 当按下delete键时,才调用事件处理函数
.esc 当按下esc键时,才调用事件处理函数
.space 当按下backspace退格键时,才调用事件处理函数
.up 当按下向上方向键时,才调用事件处理函数
.down 当按下向下方向键时,才调用事件处理函数
.left 当按下向上方向键时,才调用事件处理函数
.right 当按下向上方向键时,才调用事件处理函数

# 9.1、复习:键盘事件

TIP

常用的键盘事件有keyupkeydown,我们通过键盘事件的event.key属性来获取当前按下的键。

一般使用键盘事件都是documentinput 元素

代码演示

<script>
export default {
  methods: {
    message(e) {
      // e.key获取按下的物理键的值,如:a,,2,enter,ctrl,
      // ArrowUp(↑),ArrowDown(↓),ArrowRight(←),ArrowLeft(→)
      console.log(e.key);
      if (e.key.toLowerCase() === "enter") {
        console.log("输入框内容--", e.target.value);
      }
    },
  },
};
</script>

<template>
  <input type="text" @keyup="message" />
</template>

注:

以上代码表示,当按下键盘按键输入内容时,控制台会打印按下的键值。

如果按下的是enter回车键,还会把输入框中的内容全部打印出来。

如下:

GIF2023-4-2714-14-26

# 9.2 、enter 修修饰符

<script>
export default {
  methods: {
    message(e) {
      console.log(e.target.value);
    },
  },
};
</script>

<template>
  <input type="text" @keyup.enter="message" />
</template>

注:

以上代码表示,当在输入框中按下enter键时,会把input输出框中的内容全部打印在控制台。

# 9.3、tab 修饰符

TIP

tab 键相对比较特殊,当我们在input框中,按下tab键时,会使input失去焦点,所以tab修鉓一般都是与keydown事件结合使用。

<script>
export default {
  methods: {
    changeColor(e) {
      // 禁用input输入框
      e.target.disabled = true;
    },
  },
};
</script>

<template>
  <div><input type="text" @keydown.tab="changeColor" /></div>
</template>

注:

以上代码表示,在input获取焦点时,按下tab可以禁用input输入框。

# 9.4、up、down、left、right 修饰符

修饰符 作用
.up 当按下向上方向键时,才调用事件处理函数
.down 当按下向下方向键时,才调用事件处理函数
.left 当按下向上方向键时,才调用事件处理函数
.right 当按下向上方向键时,才调用事件处理函数
<script>
export default {
  methods: {
    doThis(e) {
      e.target.value += "→";
    },
  },
};
</script>

<template>
  <input type="text" @keydown.right="doThis" />
</template>

注:

以上代码表示,在输入框中按下方向键时,会在输入框中输入方向键。

# 9.5、其它修饰符

TIP

对于以上没有提到的一些功能,我们可以使用KeyboardEvent.key暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。

KeyboardEvent.key有哪些值,详细查阅-MDN 官方文档-按键名 (opens new window)

<script>
  export default {
    methods: {
      message(e) {
        console.log(e.target.value);
      },
    },
  };
</script>

<template>
  <!--当按下按盘上的home键时,才触发事件的处理函数-->
  <input type="text" @keydown.home="message" />
  <!--当按下按盘上的pagedown (简写 pg dn)键时,才触发事件的处理函数-->
  <input type="text" @keydown.page-down="message" />
</template>

# 10、系统按键修饰符

以下是常用的系统按键修饰符

系统按键修饰符 说明
.ctrl 当按下ctrl键时,才会触发事件处理函数
.alt 当按下alt键时,才会触发事件处理函数
.shift 当按下shift键时,才会触发事件处理函数
.meta 当按下meta键时,才会触发事件处理函数

# 10.1、复习:ctrlKey、altKey、shiftKey

<script>
  export default {
    methods: {
      changeColor(e) {
        // 事件触发时 `ctrl `键是否按下,如果按下,值为true,否则为false
        if (e.ctrlKey) {
          e.target.style.backgroundColor = "red";
        }
        // 事件触发时 `alt`键是否按下,如果按下,值为true,否则为false
        if (e.altKey) {
          e.target.style.backgroundColor = "yellow";
        }
        // 事件触发时 `shift`键是否按下,如果按下,值为true,否则为false
        if (e.shiftKey) {
          e.target.style.backgroundColor = "blue";
        }
        // 事件触发时 `meta `键是否按下,如果按下,值为true,否则为false
        if (e.metaKey) {
          e.target.style.backgroundColor = "green";
        }
      },
    },
  };
</script>

<template>
  <div class="box" @mousedown="changeColor"></div>
</template>
<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
</style>

以上代码表示:

  • 当按下ctrl 键,再按下鼠标时,.box元素背景变红red
  • 当按下alt 键,再按下鼠标时,.box元素背景变黄yellow
  • 当按下shift 键,再按下鼠标时,.box元素背景变蓝blue
  • 当按下meta键,再按下鼠标时,.box元素背景变绿green

GIF2023-4-2715-29-32

# 10.2、系统按键修饰符使用

TIP

系统按键修饰符通常配keyupkeydownmousedownmouseup使用

  • 配合keyup使用:按下系统按键的同时,再按下其他键,随后释放其它键,事件才被触发
  • 配合keydown使用,只要按下系统按键,事件就可以触发
  • 配合mousedown使用,需要先按下系统按键,再按下鼠标,才能触发事件
  • 配合mouseup使用,按下系统按键的同时再按鼠标,随后松开鼠标,事件才被触发
<script>
export default {
  methods: {
    changeColor(e) {
      e.target.style.backgroundColor = "red";
    },
  },
};
</script>

<template>
  <div class="box" @mousedown.ctrl="changeColor"></div>
  <div class="box" @mouseup.ctrl="changeColor"></div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  margin: 20px;
  background-color: skyblue;
}
</style>

注:

以上代码表示,当在第一个box上按下ctrl+鼠标时,背景变成红色。

当在第二个box上按下ctrl+鼠标后,再松开鼠标,背景变成红色。

# 11、.exact 修饰符

TIP

.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符

<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

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

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

代码演示

<script>
export default {
  methods: {
    changeColor(e) {
      e.target.style.backgroundColor = "red";
    },
  },
};
</script>

<template>
  <div class="box" @click.ctrl.exact="changeColor"></div>
  <div class="box" @click.exact="changeColor"></div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  margin: 20px;
  background-color: skyblue;
}
</style>

注:

以上代码表示, 仅当按下Ctrl 且未按任何其他键时,再点击第一个box,背景色会变红。

在点击第二个box元素时,同时没有按下任何其它的键,背景颜色就会变红

# 12、鼠标按键修饰符

TIP

以下修饰符将处理程序限定为由特定鼠标按键触发的事件。

鼠标按键修饰符 说明
.left 当鼠标左键被按下时,才会触发事件处理函数
.right 当鼠标右键被按下时,才会触发事件处理函数
middle 当鼠标中间滚轮被按下时,才会触发事件处理函数
<script>
export default {
  methods: {
    changeColor(e) {
      e.target.style.backgroundColor = "red";
    },
  },
};
</script>

<template>
  <!--@contextmenu.prevent 禁止在点击鼠标右键时,显示系统上下文菜单-->
  <div class="box" @mousedown.right="changeColor" @contextmenu.prevent></div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  margin: 20px;
  background-color: skyblue;
}
</style>

注:

以上代码表示,在.box元素上右键时,元素背景颜色变成红色。

# 13、总结:事件绑定

TIP

总结 Vue 事件绑定相关核心重点知识

# 13.1、事件的基本使用

TIP

  • 使用v-on:xxx=handler@xxx=handler方式绑定事件,其中 xxx 是事件的名称,handler 为事件处理器。
  • 事件处理器handler分为:内联事件处理器与方法事件处理器
    • 内联事件处理器:事件被触发时执行的内联 JavaScript 语句
    • 方法事件处理器:一个指向组件上定义的方法的属性名或是路径(如:obj.fn )。
  • 事件处理器如果为一个方法或方法的调用,这个方法需要在组件的 methods 方法上定义。
  • 事件处理器中访问event事件对象,要区分@click=show@click=show($event,1,2)两种情况。前者在调用函数show是,会自动把 event 事件对象作为方法的第一个参数传入,后者中的$event指代的就是传入的事件对象。
  • 如果需要给一个元素同时绑定多个事件,可以采用对象写法,不过对象写法不支持任何修饰符。

# 13.2、事件修饰符

TIP

事件修饰符可以帮我们处理了许多 DOM 事件的细节,让我们更专注于事件中数据逻辑的处理。

事件修饰符 说明
stop 阻止事件冒泡
prevent 阻止事件的默认行为
self 只有 event.target是元素本身时才会触发事件处理器,即:只有事件从元素本身发出才触发处理函数
capture 添加事件监听器时,使用 capture 捕获模式
once 最多触发一次处理函数
passive 滚动事件的默认行为 (scrolling) 将立即发生而非等待 onScroll 完成

# 13.3、按键修饰符

TIP

按键修饰符主要与键盘事件keyupkeydown进行配合,当按下键盘上对应的按键时,才调用事件处理函数。

主要有以下修饰符

按键修饰符 说明
.enter 当按下enter回车键时,才调用事件处理函数
.tab 当按下tab键时,才调用事件处理函数
.delete 当按下delete键时,才调用事件处理函数
.esc 当按下esc键时,才调用事件处理函数
.space 当按下backspace退格键时,才调用事件处理函数
.up 当按下向上方向键时,才调用事件处理函数
.down 当按下向下方向键时,才调用事件处理函数
.left 当按下向上方向键时,才调用事件处理函数
.right 当按下向上方向键时,才调用事件处理函数
其它修饰符 对于以上没有提到的一些功能,我们可以使用KeyboardEvent.key暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。

# 13.4、系统按键修饰符

TIP

系统按键修饰符通常配keyupkeydownmousedownmouseup使用。

表示当某个系统按键被按下时才会触发鼠标与键盘事件的事件处理函数。

系统按键修饰符 说明
.ctrl 当按下ctrl键时,才会触发事件处理函数
.alt 当按下alt键时,才会触发事件处理函数
.shift 当按下shift键时,才会触发事件处理函数
.meta 当按下meta键时,才会触发事件处理函数

# 13.5、.exact 修饰符

TIP

.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符

<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

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

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

# 13.6、鼠标按键修饰符

TIP

当按下鼠标对应的按键时(左键、中间滚轮、右键),才触发事件处理函数。

具体如下:

鼠标按键修饰符 说明
.left 当鼠标左键被按下时,才会触发事件处理函数
.right 当鼠标右键被按下时,才会触发事件处理函数
middle 当鼠标中间滚轮被按下时,才会触发事件处理函数

# 13.7、修饰符注意事项

TIP

  • 修饰符可以使用链式书写
  • 可以只有修饰符
  • 链式书写时,要注意修饰符的书写顺序。

# 三、计算属性 computed

TIP

计算属性是我们必须要掌握的重点知识,我们从以下几个方面展开讲解

  • 为什么需要计算属性
  • 计算属性的基本使用
  • 计算属性的完整完写法
  • 计算属性注意事项
  • 计算属性和 methods 方法的对比

详细查阅:Vue 官方文档 - 计算属性 (opens new window)

# 1、为什么需要计算属性

TIP

虽然在模板的插值语法{{}}v-bind指令中可以使用表达式,但如果涉及的逻辑太多,会让模板变得臃肿,难以维护。

比如:以下示例中,求三门课的平均分

<script>
export default {
  data() {
    return {
      username: "清心",
      css: 80,
      js: 90,
      vue: 99,
    };
  },
};
</script>

<template>
  <div>
    <h3>{{ username }}老师三门课</h3>
    <div>平均分: {{ ((css + js + vue) / 3).toFixed(2) }}</div>
  </div>
</template>

注:

上面模板中求平均分的代码看起来有些复杂,同时,如果需要在多个地方求平均分,我们需要在多个地方重复书写此代码,这肯定不是我们想要的。

计算属性就可以帮我们解决这个问题,能让模板中的代码更简洁,当然计算属性还有其它的优点,后面会讲到。

# 2、计算属性的基本使用

TIP

计算属性的本质是根据其它的属性值计算得到另一个值,使用方式与一般属性的使用方式一样,可以直接在模板中调用。

计算属性需要书写在的组件的computed选项中,同时支持以下两种写法

  • 简写形式:用于创建只读的计算属性(常用)
  • 完整写法:用于创建可读可写的计算属性(了解)

# 2.1、简写形式:创建只读计算属性

TIP

如果我们只需要读取一个计算属性的值,而不用修改他的值。

可以采用如下简写形式

// 简写形式
computed:{
	// myComputed 为计算属性,当读取计算属性值时,会调用该函数
	myComputed(){ }
}

我们用计算属性来改造下上面的案例,如下:

<script>
export default {
  data() {
    return {
      username: "清心",
      css: 80,
      js: 90,
      vue: 99,
    };
  },
  // 所有计算属性都写在computed这个选项中
  computed: {
    // 一个计算属性。
    average() {
      return ((this.css + this.js + this.vue) / 3).toFixed(2);
    },
  },
};
</script>

<template>
  <div>
    <h3>{{ username }}老师三门课</h3>
    <!-- 直接在模板中使用计算属性 -->
    <div>平均分: {{ average }}</div>
  </div>
</template>

注:

  • 以上代码中的average是一个只读的计算属性,属性的值不能直接被修改的
  • 不过他的值会根据他所依赖的 响应式数据 而发生更改。比如css、js、vue中,只要有一个属性的值发生了变化,那average的值就会被重新计算。

以上代码最终渲染效果如下图:

GIF2023-4-2917-15-09

# 2.2、完整写法:创建可读可写计算属性

TIP

如果一个计算属性需要支持可读可写,则需要采用如下完整写法,同时创建getset方法。

computed:{
    // myComputed 为计算属性
	myComputed:{
        // 当获取计算属性的值时,get方法会被调用
		get(){},
         //  当修改计算属性的值时,set方法会被调用,newValue为myComputed的最新值
		set(newValue){ }
	}
}

利用计算属性实现如下效果

GIF2023-6-1317-43-57

注:

  • 当用户的姓名被修改时,其对应的姓和名也会发生更改
  • 当用户的姓或名发生修改时,其姓名会被发生更改

完整代码

<script>
  export default {
    data() {
      return {
        firstName: "清",
        lastName: "心",
      };
    },
    computed: {
      // 计算属性
      fullName: {
        // 当读取fullName值时,get方法会被调用
        get() {
          console.log("get被调用了");
          return this.firstName + "-" + this.lastName;
        },
        // 当修改fullName的值时,set方法会被调用
        set(newValue) {
          console.log("set被调用了");
          [this.firstName, this.lastName] = newValue.split("-");
        },
      },
      /*
            以下fullName为只读计算属性,fullName的值不能直接被修改
                fullName() {
                  return this.firstName + "-" + this.lastName
                }
            */
    },
  };
</script>

<template>
  <div>姓:<input v-model="firstName" /></div>
  <div>名:<input v-model="lastName" /></div>
  <div>姓名:<input v-model="fullName" /></div>
</template>

注:

  • 以上fullName计算属性,被定义为可读可写。
  • 当读取fullName计算属性时,会调用get方法,返回值为fullName的值。
  • 当在input文本框中修改fullName的值时,就会调用set方法,修改firstNamelastName的值,当fristNamelastName的值被更改后,又会触发get方法,重新计算fullName的值

# 3、计算属性注意事项

TIP

计算属性的get方法不应有副作用

这里的副作用是指:get方法应该只能用来计算返回具体的值,而不应该在内部做其它的操作,比如:异步请求或更改 DOM。

计算属性最好是只读的

因为计算属性的值是通过其它的属性计算后得到的,所以计算属性可以看做是一个“临时快照”,每当源数据发生变化时,就会创建一个新的快照。

如果想要更新计算属性值,应该去更新它所依赖的源数据,以触发get方法来重新计算,得到新的值。

# 4、计算属性与 methods 方法对比

TIP

到现在为止,我们对计算属性有了一定的认知,但你肯定还有如下疑问:

计算属性能实现的功能,在methods选项中定义一个方法,也可以做到,那为什么还需要弄出一个计算属性呢 ?

是因为计算属性与methods方法之间还存在以下细微的区别:

计算属性值会基于其响应式依赖被缓存

  • 也就是一个计算属性仅会在其依赖的响应式数据发生更新时才会重新计算值,否则无论页面中读取多少次计算属性,他都是直接返回之前计算得到的结果,而不会去重复执行 get 函数来重新计算值。
  • 后面模板重新渲染时,也是直接读取之前计算得到的结果

methods 中定义的方法无缓存

  • methods中定义的方法调用多少次,就会执行多少次。
  • 同时还会在模板重新渲染时再次调用。

代码演示

<script>
export default {
  data() {
    return {
      username: "清心",
      css: 80,
      js: 90,
      vue: 99,
    };
  },
  methods: {
    avg() {
      console.log("avg方法被调用");
      return ((this.css + this.js + this.vue) / 3).toFixed(2);
    },
  },
  // 所有计算属性都写在computed这个选项中
  computed: {
    average() {
      console.log("average计算属性get被调用");
      return ((this.css + this.js + this.vue) / 3).toFixed(2);
    },
  },
};
</script>

<template>
  <div>
    <h3>{{ username }}老师三门课</h3>
    <div>平均分: {{ average }}</div>
    <div>平均分: {{ average }}</div>
    <div>------methods-----</div>
    <div>平均分: {{ avg() }}</div>
    <div>平均分: {{ avg() }}</div>
  </div>
</template>

当初始渲染时,会在控制台输出如下结果:

image-20230429180321658

分析以上截图:

  • 第一次读取average计算属性时,会调用get方法返回计算的结果,第二次读到average计算属性时,直接读取了上一次计算的结果,并没有再次调用get方法来重新计算结果。
  • 第一次读取avg()时,方法avg()被调用了一次,第二次读取时,再次被调用了。

接下来,我们在devtools工具中,来修改username属性的值,我们观察到以下现象。

GIF2023-4-2918-08-45

注:

username的值发生改变时,vue 会重新解析渲染模板

  • 但此时average计算属性的get方法并没有被执行,因为直接读取了之前average计算得到的结果。
  • avg方法,被调用了两次。

# 5、计算属性总结

TIP

计算属性定义

计算属性是通过现在属性重新计算而来。计算属性与普通属性一样,可以直接在模板中使用。

计算属性的基本使用

如果计算属性为一个只读的,可以采用以下简写形式(相当只定义了 get 方法)

computed: {
	// myComputed为计算属性,myComputed()方法相当于计算属性的get方法
    myComputed(){
      // 省略复杂计算步骤
      return xxx;
    }
  }
  • 如果计算属性为可读可写的,则需要采用以下完整写法
computed: {
    // myComputed为计算属性,同时书写get与set方法
    myComputed:{
        // 读取计算属性,get方法被调用
        get(){
            // 省略复杂计算步骤
            return xxx;
        },
        // 修改计算属性值,set方法被调用
        set(newValue){
           // 省略内部赋值细节
        }
    }
}

计算属性的 get 方法什么时候执行

  • 当初次读取计算属性时,get方法会被执行
  • 当计算属性依赖的响应式数据发生改变时,会再次调用get方法更新值。

计算属性注意事项

  • 计算属性的get方法不应该有副作用,只参于计算并返回值,并应该有异步请求与 DOM 操作等
  • 计算属性应该被定义为只读的,最好不要直接修改计算属性值

计算属性于 methods 方法对比

  • 算属性值会基于其响应式依赖被缓存,并且模板重新渲染也不会重新求值
  • methods 方法调用多少次,就会执行多少次。同时还会在模板重新渲染时再次调

计算属性的优点

  • 可以简化模板中的代码,对于相对复杂的逻辑,可以直接用计算属性来代替
  • 因为计算结果会基于其响应式依赖被缓存,所以性能比较高,也更适合复用。

# 四、侦听器 watch

TIP

侦听器主要是用来监视某个响应式属性,当这个属性的值发生变化时我们可以作一些额外的操作,比如:发送异步请求或执行 DOM 操作等。

从以下几个方面来展开讲解:

侦听器的创建与使用

  • 侦听器:完整版-对象写法
  • 侦听器:简化版-函数写法

侦听器配置选项

  • 即时回调的侦听器
  • 深度侦听器
  • 回调的触发时机

副作用清理,创建、停止侦听器

  • $watch()方法创建侦听器
  • 停止侦听器
  • watch 侦听器与computed的对比

# 1、侦听器的创建与使用

TIP

当我们需要侦听某个响应式属性的变化时,我们可以在watch选项中来监听该属性。

watch 侦听器有以下两种写法:

  • 完整版:对象写法
  • 简化版:函数写法

# 1.1、侦听器:完整版-对象写法

TIP

如果侦听器除了handler回调函数,还需要有其它额外的配置,则需要采用以下完整写法

watch:{
   // key 为监听的响应式属性
    key:{
        // newValue 为属性变化后值  oldValue 为属性变化后值
        handler(newValue,oldValue){ }
        // options 相关配置可以直接写在这个后面
        // .....
    }
}

注:

以上代码中的key 为监听的响应式属性,当key的属性值发生变化时,handler方法就会被调用

代码演示

利用侦听器来实现:千米与米之间的相互转换效果

  • 当修改千米数据,对应米数据也会更新;
  • 当修改米数据时,千米数据也会更新。

GIF2023-5-512-33-26

<script>
  export default {
    data() {
      return {
        kilometers: 0, // 千米
        meters: 0, // 米
      };
    },
    // watch选项
    watch: {
      // 侦听属性 kilometers
      kilometers: {
        // 侦听器函数,当kilometers属性值发生变化时,就会调用这个函数
        handler(newValue, oldValue) {
          // newValue表示最新的kilometers值
          // oldValue 表示更新前 kilometers的值
          // this.meters = this.kilometers * 1000;
          this.meters = newValue * 1000;
        },
      },
      // 侦听属性 meters
      meters: {
        // 侦听器函数,当meters属性值发生变化时,就会调用这个函数
        handler(newValue, oldValue) {
          this.kilometers = newValue / 1000;
        },
      },
    },
  };
</script>

<template>
  <div>千米:<input type="text" v-model="kilometers" /></div>
  <div>米:<input type="text" v-model="meters" /></div>
</template>

# 1.2、侦听器:简化版-函数写法

TIP

如果侦听器除了handler回调函数,没有其它额外的配置项,则可以采用如下简写形式

watch:{
    // key方法相当于 对象写法中的handler方法
    // 当侦听的属性key的值发生了变化,就会调用key方法,来执行相关后续操作
    key(newValue,oldValue){
        //.....
    }
}

用简写形式,实现千米与米之间的相互转换

GIF2023-5-512-33-26

<script>
  export default {
    data() {
      return {
        kilometers: 1, // 千米
        meters: 1000, // 米
      };
    },
    // watch选项
    watch: {
      // 侦听属性 kilometers
      kilometers(newValue, oldValue) {
        this.meters = newValue * 1000;
      },
      // 侦听属性 meters
      meters(newValue, oldValue) {
        this.kilometers = newValue / 1000;
      },
    },
  };
</script>

<template>
  <div>千米:<input type="text" v-model="kilometers" /></div>
  <div>米:<input type="text" v-model="meters" /></div>
</template>

# 2、侦听器配置选项

TIP

watch侦听器侦听某个响应式属性时,允许我们添加以下相关配置选项

<script>
  export default {
    watch: {
      key: {
        // 侦听器回调
        handler(newValue, oldValue) {},
        immediate: true, // 侦听器回调函数立即执行
        deep: true, // 深层侦听器
        flush: "post", // 在Vue组件更新后调用侦听器回调
      },
    },
  };
</script>

# 2.1、即时回调的侦听器(immediate:true)

TIP

  • watch 默认是懒执行的,仅当数据源变化时,才会执行回调。
  • 但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调,我们可以添加immediate: true配置项实现。

代码演示

在前面:千米与米之间的相互转换案例代码的基础上,我们添加immediate: true配置,这样在初始化数据时,回调函数会强制立即执行。

<script>
export default {
  data() {
    return {
      kilometers: 1, // 千米
      meters: 0, // 米
    };
  },
  // watch选项
  watch: {
    // 侦听属性 kilometers
    kilometers: {
      // 侦听器函数,当kilometers属性值发生变化时,就会调用这个函数
      handler(newValue, oldValue) {
        // this.meters = this.kilometers * 1000;
        console.log("强制执行kilometers");
        this.meters = newValue * 1000;
      },

      immediate: true,
    },
    // 侦听属性 meters
    meters: {
      // 侦听器函数,当meters属性值发生变化时,就会调用这个函数
      handler(newValue, oldValue) {
        console.log("强制执行meters");
        this.kilometers = newValue / 1000;
      },
      immediate: true, // 侦听器回调函数立即执行
    },
  },
};
</script>

<template>
  <div>千米:<input type="text" v-model="kilometers" /></div>
  <div>米:<input type="text" v-model="meters" /></div>
</template>

image-20230505125243974

注:

我们发现,当页面初始渲染时,调用了侦听器的handler回调,在控制台输出了如上内容。

# 2.2、深层侦听器(deep:true)

TIP

  • watch 默认是浅层的:被侦听的属性(也包括计算属性),仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。
  • 如果想侦听所有嵌套的变更,你需要配置deep:true开启深层侦听器

代码演示

监听体温,根据体温变化,来做出相应建议

<script>
export default {
  data() {
    return {
      person: {
        username: "清心",
        temperature: 37.6,
      },
      info: "",
    };
  },
  watch: {
    person: {
      handler(newValue, oldValue) {
        if (newValue.temperature >= 38) {
          this.info = "发烧高,建议前往医院就医";
        } else if (newValue.temperature > 37) {
          this.info = "有点发烧,建议在家采用物理降温";
        } else {
          this.info = "体温正常";
        }
      },
      deep: true, // 开启深度侦听
      immediate: true, // 初始化时,强制执行侦听器的回调函数
    },
  },
};
</script>

<template>{{ person.username }}---{{ info }}</template>

注:

以上代码对person对象开启了深度侦听,当person.temperature的值发生改变时,就会被侦听到,handler方法就会被调用,对应的info属性的值也会发生改变。

如果把deep:true配置去掉,无论person.temperature的值如何改变,侦听器都侦听不到,handler方法也不会被调用,则info属性的值将不会有任何变化。

GIF2023-5-515-09-42

谨慎使用:

深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

# 2.3、侦听对象某个属性

TIP

如果为了侦听对象身上的某个属性而开启深度侦听,显然是非常消耗性能的。针对这种情况,我们可以只侦听对象的某个属性。

<script>
export default {
  data() {
    return {
      person: {
        username: "清心",
        temperature: 37,
      },
    };
  },
  watch: {
    // 侦听 person对象身上的temperature属性
    "person.temperature": {
      handler(newValue, oldValue) {},
    },
  },
};
</script>

上面案例可以改写成下面写法

<script>
export default {
  data() {
    return {
      person: {
        username: "清心",
        temperature: 37,
      },
      info: "",
    };
  },
  watch: {
    "person.temperature": {
      handler(newValue, oldValue) {
        if (newValue >= 38) {
          this.info = "发烧高,建议前往医院就医";
        } else if (newValue > 37) {
          this.info = "有点发烧,建议在家采用物理降温";
        } else {
          this.info = "体温正常";
        }
      },
      deep: true, // 开启深度侦听
      immediate: true, // 初始化时,强制执行侦听器的回调函数
    },
  },
};
</script>

<template>{{ person.username }}---{{ info }}</template>

# 2.4、回调的触发时机(flush 选项)

TIP

当我们侦听的响应式属性的值发生更改时,默认情况下,侦听器回调都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项

flush 选项的值 侦听器回调执行时机
pre 组件更新前执行
sync 组件更新和侦听器回调同步触发
post 组件更新后执行

代码演示

  • .box元素身上动态绑定了title属性的值为message变量的值
  • message的值发生变化时,在侦听器中获取.box元素的title属性
  • 如果添加flush:'post'选项,则获取的是title属性值为message更新后的值,否则为更新前的值。
<script>
  export default {
    data() {
      return {
        message: "Hello Vue!",
      };
    },
    watch: {
      message: {
        handler(newValue, oldValue) {
          const title = document.querySelector(".box").title;
          console.log(title);
        },
        flush: "post",
      },
    },
  };
</script>

<template>
  <div class="box" :title="message"></div>
  <input v-model="message" />
</template>

添加flush: 'post'选项,最终渲染效果如下:

GIF2023-6-1320-06-15

没有添加flush: 'post'选项,最终渲染效果如下:

GIF2023-6-1320-08-11

# 3、副作用清理

TIP

watch侦听器的回调函数实际上接受 3 个参数

watch: {
    key: {
        /**
       * newValue 属性更新后值
       * oldValue 属性更新前值
       * onCleanup 注册副作用清理的回调函数,该函数接受一个回调函数,
       * 回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,
       * 例如等待中的异步请求
       */
        handler(newValue, oldValue, onCleanup) {
            //	取消定时器
            function cancle(){}
            // 在下次handler被调用前,会执行cancle
            onCleanup(cancle)
        }
    }

代码演示

  • 监听某个 id 属性的变化,当 id 变化时,就会发请求获取内容
  • 如果 id 变化时,上一次请求的结果还没有回来,则把上一次请求取消,重新发送一个新请求。
<script>
  export default {
    data() {
      return {
        id: 0,
      };
    },
    watch: {
      id: {
        handler(newValue, oldValue, onCleanup) {
          // 模拟发请求
          let timer = setTimeout(() => {
            console.log("请求成功");
            timer = null;
          }, 2000);

          // 清除定时器, 模拟请除上一次请求
          function cancle() {
            // 如果上一次请求还没成功,则取消上一次请求
            if (timer) {
              clearTimeout(timer);
              console.log("取消上一次请求");
            }
          }
          // 注册清理定时器的回调函数
          // 当id值发生改变时,上一次请求还没有成功,就取消重新发一个请求
          onCleanup(cancle);
        },
      },
    },
  };
</script>

<template>
  <div>当前id:{{ id }}</div>
  <button @click="id++">发请求</button>
</template>

# 4、$watch()方法创建侦听器

TIP

我们还可以使用组件实例的 $watch() 方法 (opens new window)来命令式地创建一个侦听器。

$watch(string, handler, options);
  • string : 侦听的属性名,可以是组件的属性名的字符串,也可以是一个由点分隔的路径字符串,或一个 getter 函数(返回一个字符串,针对属性名需要经过复杂计算的情况)
  • handler :属性值改变时需要触发的回调函数。函数有 3 个参数:newValueoldValueonCleanup,与 watch 侦听器回调函数参数一致
  • options :一个对象,用来指定相关的配置。如:deepimmediateflush

# 4.1、$watch() 基本用法

TIP

利用$watch()方法命令式创建一个侦听器,用来监听person.temperature属性的变化。

  • 以下代码中的created函数为生命周期函数,组件实例处理完所有与状态相关的选项后调用。
  • 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始
<script>
export default {
  data() {
    return {
      person: {
        username: "清心",
        temperature: 37,
      },
      info: "",
    };
  },
  // 生命周期函
  created() {
    this.$watch(
      "person.temperature",
      function (newValue, oldValue) {
        if (newValue >= 38) {
          this.info = "发烧高,建议前往医院就医";
        } else if (newValue > 37) {
          this.info = "有点发烧,建议在家采用物理降温";
        } else {
          this.info = "体温正常";
        }
      },
      {
        immediate: true,
        deep: true,
      }
    );
  },
};
</script>

<template>{{ person.username }}---{{ info }}</template>

# 4.2、 停止侦听器

TIP

watch 选项或者 $watch() 实例方法声明的侦听器,会在宿主组件卸载时自动停止。因此,在大多数场景下,你无需关心怎么停止它。

在少数情况下,你的确需要在组件卸载之前就停止一个侦听器,这时可以调用 $watch() API 返回的函数

const unwatch = this.$watch(string, handler, options);
// 停止侦听器
unwatch();
<script>
  export default {
    data() {
      return {
        unwatch: null,
        person: {
          username: "清心",
          temperature: 37,
        },
        info: "",
      };
    },
    methods: {
      // 停止侦听器
      stop() {
        if (this.unwatch) this.unwatch();
      },
    },
    // 生命周期函
    created() {
      this.unwatch = this.$watch(
        "person.temperature",
        function (newValue, oldValue) {
          if (newValue >= 38) {
            this.info = "发烧高,建议前往医院就医";
          } else if (newValue > 37) {
            this.info = "有点发烧,建议在家采用物理降温";
          } else {
            this.info = "体温正常";
          }
        },
        {
          immediate: true,
          deep: true,
        }
      );
    },
  };
</script>

<template>
  <div>{{ person.username }}---{{ info }}</div>
  <button @click="stop">停止侦听器</button>
</template>

以上代码最终渲染效果如下:

GIF2023-6-1321-19-18

注:

在没有停止侦听器前,当体温变化时会被侦听到,当点击停止侦听器按扭后,无任体温如何变化,都不会被侦听到。

# 5、watch 与 computed 的对比

TIP

  • 在学习computed时,我们专门强调过,computed计算属性最好是只读的,并且get中不能有副作用也就是不要有异步请求或 DOM 相关的额外操作。
  • watch侦听器允许我们在某个属性发生变化时,可以执行相关的“副作用”,也就可以执行异步请求或 DOM 相关的额外操作。

以上是watchcompted两者最大的不同。如果我们想要在某个属性变化时,执行相关的副作用(DOM 操作或异步请求),则选用 watch 来实现。

有些情况watch能实现的,computed计算属性也能实现,且computed的实现方式更简洁,则选用computed

以下利用computed实现:根据体温变化,来做出相当建议的案例。

<script>
export default {
  data() {
    return {
      person: {
        username: "清心",
        temperature: 37,
      },
    };
  },
  computed: {
    info() {
      const temperature = this.person.temperature;
      if (temperature >= 38) {
        return "发烧高,建议前往医院就医";
      } else if (temperature > 37) {
        return "有点发烧,建议在家采用物理降温";
      } else {
        return "体温正常";
      }
    },
  },
};
</script>

<template>{{ person.username }}---{{ info }}</template>

我们把用computed实现同样效果代码与watch来对比,发现computed的实现方式更简单。

总结:

  • watch相比computed功能更强大,watch能实现computed不一定能实现,比如watch允许执行一些“副作用”,而computed是不建议的。
  • 不过computedwatch都能实现的功能,建议使用computed,因为computed实现更简洁

# 6、watch 侦听器总结

  • 侦听器的完整版:对象写法与相关配置
watch:{
    // 侦听属性 key
    key:{
        /**
           * newValue 属性更新后值
           * oldValue 属性更新前值
           * onCleanup 注册副作用清理的回调函数,该函数接受一个回调函数,
           * 回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,
           * 例如等待中的异步请求
       	   */
        handler(newValue, oldValue, onCleanup) {
            //	取消定时器
            function cancle(){}
            // 在下次handler被调用前,会执行cancle
            onCleanup(cancle)
        },
            // 强制立即执行回调
            immediate: true,
            // 深度侦听
            deep: true,
            // DOM更新后再调用回调函数
            flush: 'post'
    }
}
  • 侦听器的简写,当侦听器没有额外配置时,就可以采用如下简写形式
watch:{
    // 侦听属性key,简写形式
    key(newValue,oldValue, onCleanup){
        //.....
    }
}
  • $watch()方法创建侦听器
$watch(string, handler, options);
上次更新时间: 6/15/2023, 12:25:14 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X