一、复杂 data 处理方式

在模板中可以直接通过插值语法显示一些 data 中的数据

但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示

比如我们需要对多个 data 数据进行运算、三元运算符来决定结果、数据进行某种转化后显示

  • 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算
  • 在模板中放入太多的逻辑会让模板过重和难以维护
  • 并且如果多个地方都使用到,那么会有大量重复的代码

我们有没有什么方法可以将逻辑抽离出去呢?

  • 其中一种方式就是将逻辑抽取到一个 method 中,放到 methods 的 options 中
  • 但是,这种做法有一个直观的弊端,就是所有的 data 使用过程都会变成了一个方法的调用
  • 另外一种方式就是使用计算属性 computed

二、计算属性 computed

什么是计算属性呢? 官方并没有给出直接的概念解释

  • 而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性
  • 计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例

计算属性的用法:

  • 选项:computed
  • 类型:{ [key: string]: Function | { get: Function, set: Function } }

一个案例

对名字的拼接

1
2
data: function () { return { message: 'Hello World', firstName: 'x', lastName:
'y' } },

1、放到模板语法中 使用表达式

1
<h2>{{firstName + lastName}}</h2>
  • 缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算)
  • 缺点二:当有多次一样的逻辑时,存在重复的代码
  • 缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存

2、使用 methods

1
2
3
4
<h2>{{fullName()}}</h2>

methods: { fullName() { console.log('methods中的方法执行'); return
this.firstName + this.lastName } },
  • 缺点一:我们事实上想显示的是一个结果,但是都变成了一种方法的调用
  • 缺点二:多次使用方法的时候,没有缓存,也需要多次计算

3、使用 computed

1
2
3
4
<h2>{{fullName1}}</h2>

computed:{ fullName1(){ console.log('computed中的方法执行'); return
this.firstName + this.lastName } }
  • 计算属性看起来像是一个函数,但是我们在使用的时候不需要加()
  • 我们会发现无论是直观上,还是效果上计算属性都是更好的选择
  • 并且计算属性是有缓存的

computed vs methods

1
2
3
4
5
6
7
8
9
10
<h2>{{fullName()}}</h2>
<h2>{{fullName()}}</h2>
<h2>{{fullName()}}</h2>

<h2>{{fullName1}}</h2>
<h2>{{fullName1}}</h2>
<h2>{{fullName1}}</h2>

// 控制台打印 methods中的方法执行 methods中的方法执行 methods中的方法执行
computed中的方法执行

这是因为计算属性会基于它们的依赖关系进行缓存

  • 在数据不发生变化时,计算属性是不需要重新计算的
  • 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算

计算属性的 getter setter

计算属性在大多数情况下,只需要一个 getter 方法即可,所以我们会将计算属性直接写成一个函数

1
2
3
computed: { fullName1: { get() { console.log('computed中的方法执行'); return
this.firstName + this.lastName } } } // 相当于 // computed:{ // fullName() { //
return this.firstName + this.lastName // } // }

但是,如果我们确实想设置计算属性的值呢? 这个时候我们也可以给计算属性设置一个 setter 的方法

1
2
computed: { fullName1: { get() { console.log('computed中的方法执行'); return
this.firstName + this.lastName }, set(newValue) { console.log(newValue); } } }

三、侦听器

什么是侦听器呢?

  • 开发中我们在 data 返回的对象中定义了数据,这个数据通过插值语法等方式绑定到 template 中
  • 当数据变化时,template 会自动进行更新来显示最新的数据
  • 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器 watch 来完成了

侦听器的用法如下:

  • 选项:watch
  • 类型:{ [key: string]: string | Function | Object | Array}

举个例子

比如现在我们希望用户在 input 中输入一个问题

每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案

那么,我们就需要实时的去获取最新的数据变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<body>
<div id="app"></div>
<script src="../js/vue.js"></script>
<template id="my-app">
<h2>{{message}}</h2>
<input type="text" v-model='question'>
<button @click='queryQusetion'>查询答案</button>
</template>

<script>
const App = {
template: '#my-app',
data: function () {
return {
message: 'Hello World',
question: '输入'
}
},
methods: {
queryQusetion() {
console.log('1');
}
},
// question 要监听的data中属性的名称
// newValue 新值
// oldValue 旧值
watch: {
question(newValue, oldValue) {
console.log(`新值:${newValue} --- 旧值:${oldValue}`);

}
}
}

Vue.createApp(App).mount('#app')
</script>
</body>

配置选项

当点击按钮的时候会修改 info.name 的值; 这个时候我们使用 watch 来侦听 info,可以侦听到吗?答案是不可以

这是因为默认情况下,watch 只是在侦听 info 的引用变化,对于内部属性的变化是不会做出响应的

这个时候我们可以使用一个选项 deep 进行更深层的侦听

注意前面我们说过 watch 里面侦听的属性对应的也可以是一个 Object

还有另外一个属性,是希望一开始的就会立即执行一次:

这个时候我们使用 immediate 选项; 这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template id="my-app">
<h2>{{ info.name }}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
<button @click="changeInfoNbaName">改变info.nba.name</button>
</template>

<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
info: { name: "why", age: 18, nba: { name: "kobe" } },
};
},
watch: {
// 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
// info(newInfo, oldInfo) {
// console.log("newValue:", newInfo, "oldValue:", oldInfo);
// }

// 深度侦听/立即执行(一定会执行一次)
info: {
handler: function (newInfo, oldInfo) {
console.log(
"newValue:",
newInfo.nba.name,
"oldValue:",
oldInfo.nba.name
);
},
deep: true, // 深度侦听
// immediate: true // 立即执行
},
},
methods: {
changeInfo() {
this.info = { name: "kobe" };
},
changeInfoName() {
this.info.name = "kobe";
},
changeInfoNbaName() {
this.info.nba.name = "james";
},
},
};

Vue.createApp(App).mount("#app");
</script>

侦听器 watch 的其他方式

1、字符串方法别名

2、传入回调数组

另外一个是 Vue3 文档中没有提到的,但是 Vue2 文档中有提到的是侦听对象的属性:

1
2
watch:{ "info.name":function(newValue,oldValue) { console.log(newValue,oldValue)
} }

只会监听到 info.name 的变化

还有另外一种方式就是使用 $watch 的 API

我们可以在 created 的生命周期(后续会讲到)中,使用 this.$watchs 来侦听

  • 第一个参数是要侦听的源
  • 第二个参数是侦听的回调函数 callback
  • 第三个参数是额外的其他选项,比如 deep、immediate
1
2
create() { this.$watch('info',(newValue,oldValue) => {
console.log(newValue,oldValue); },{deep:true,immdiate:true}) }

四、v-model

表单提交是开发中非常常见的功能,也是和用户交互的重要手段:

比如用户在登录、注册时需要提交账号密码

比如用户在检索、创建、更新信息时,需要提交一些数据

这些都要求我们可以在代码逻辑中获取到用户提交的数据

我们通常会使用 v-model 指令来完成

  • v-model 指令可以在表单 input、textarea 以及 select 元素上创建双向数据绑定
  • 它会根据控件类型自动选取正确的方法来更新元素
  • 尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理

v-model 原理

官方有说到,v-model 的原理其实是背后有两个操作:

v-bind 绑定 value 属性的值

v-on 绑定 input 事件监听到函数中,函数会获取最新的值赋值到绑定的属性中

1
<input type="text" :value="message" @input="message = $event.target.value">

v-model 绑定 textarea

1
2
3
4
5
6
<!-- 1.绑定textarea -->
<label for="intro">
自我介绍
<textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
</label>
<h2>intro: {{intro}}</h2>

v-model 绑定 checkbox

单个勾选框:

  • v-model 即为布尔值。 p 此时 input 的 value 并不影响 v-model 的值

多个复选框:

  • 当是多个复选框时,因为可以选中多个,所以对应的 data 中属性是一个数组。 当选中某一个时,就会将 input 的 value 添加到数组中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 2.checkbox -->
<!-- 2.1.单选框 -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>isAgree: {{isAgree}}</h2>

<!-- 2.2.多选框 -->
<span>你的爱好: </span>
<label for="basketball">
<input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
<input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
<input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>

v-model 绑定 redio

1
2
3
4
5
6
7
8
9
<!-- 3.radio -->
<span>你的爱好: </span>
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
<h2>gender: {{gender}}</h2>

v-model 绑定 select

和 checkbox 一样,select 也分单选和多选两种情况

单选:

  • 只能选中一个值
  • v-model 绑定的是一个值
  • 当我们选中 option 中的一个时,会将它对应的 value 赋值到 fruit 中

多选:

  • 可以选中多个值
  • v-model 绑定的是一个数组
  • 当选中多个值时,就会将选中的 option 对应的 value 添加到数组 fruit 中
1
2
3
4
5
6
7
8
<!-- 4.select -->
<span>喜欢的水果: </span>
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit: {{fruit}}</h2>

v-model 的值绑定

目前前面的案例中大部分的值都是在 template 中固定好的

比如 gender 的两个输入框值 male、female

比如 hobbies 的三个输入框值 basketball、football、tennis

在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到 data 返回的对象中, 再通过 v-bind 来进行值的绑定,这个过程就是值绑定

v-model 修饰符

lazy

默认情况下,v-model 在进行双向绑定时,绑定的是 input 事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步

如果我们在 v-model 后跟上 lazy 修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车) 才会触发

number

输入框的值绑定到 message 中永远是 string 类型的

如果我们希望转换为数字类型,那么可以使用 .number 修饰符

另外,js 中进行逻辑判断时,如果是一个 string 类型,在可以转化的情况下会进行隐式转换的

trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符