Vue3快速上手

简介

Vue是一套用于构建用户界面的渐进式框架。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

Vue3发布于2020年9月19日,它在Vue2.x的基础上进行了一些优化,对TypeScript有了更好的支持。

vue-devtools

vue-devtools是一个Chrome浏览器的插件,是官方提供的一个Vue开发者工具,方便用户在开发Vue项目时调试。可以在官网安装,但操作较为复杂。访问极简插件网

https://chrome.zzzmh.cn

在线安装较为简单。

创建Vue3项目

可以使用vue-cli创建。

安装或升级vue cli,保证版本在4.5.0以上

1
2
3
vue --version

vue create <proj_name>

Vue常用指令介绍

Vue指令是带有v-前缀的特殊属性。他的职责是当表达式的值改变时,将其产生的连带影响响应式地作用于DOM。

v-text

v-text用于操作纯文本,它会替代显示对应的数据对象上的值。当绑定的数据对象上的值发生改变时,插值处的内容也会随之更新。

{{ }}会将数据解释为普通文本,而非HTML代码。

在多数场景时,v-text可以用{{ }}代替,但是{{ }}更加灵活。

1
<div v-text = "message"></div>

v-model和v-bind

v-model是Vue提供的一个特殊属性,实际上是语法糖,作用是双向绑定表单控件。只能运用于表单元素(input(radio、text、address、email……)、select、checkbox、textarea……)中。

  • 双向数据绑定:当数据发生改变,DOM会自动更新,当表单控件的值发生改变,数据也会自动得到更新。

v-bind是用于绑定属性的指令,可以简写为 : ,只能实现数据的单向绑定,从Model自动绑定到View。

v-once

只渲染元素和组件一次,在随后的渲染中,元素/组件所有的子节点将被视为静态内容并跳过。

v-cloak

这个指令保持在元素上直到关联组件实例结束编译。和CSS规则一起用时,这个指令可以隐藏未编译的Mustache标签(双大括号),直到组件实例准备完毕。

主要用于多页应用中,解决当网速较慢,Vue.js文件还没加载完时,绑定的数据会先闪一下{{ }}符号,然后再显示所绑定数据的问题。

v-for和key属性

v-for主要用于数据遍历,有如下四种使用方式:

  1. 迭代普通数组

    1
    2
    3
    <ul>
    <li v-for="(item, index) in list" :key="index">{{++ index}}.{{item}}</li>
    </ul>
  2. 迭代对象数组

    1
    2
    3
    4
    <ul>
    <li v-for="(item, index) in users" :key="index">
    {{index++}}.[{{item.title}}]{{item.name}}</li>
    </ul>
  3. 迭代对象中的属性

    1
    2
    3
    <p v-for="(val, key, index) in userInfo" :key="index">
    键是:{{key}},值是:{{value}},索引:{{index}}
    </p>
  4. 迭代数字

    1
    2
    3
    <p v-for="i in 7" :key="i">
    这是第{{i}}个p标签
    </p>

v-for 的默认方式是尝试就地更新元素而不移动它们。要强制其重新排序元素,你需要用特殊 attribute key 来提供一个排序提示。在vue2中key是必须的,vue3中可以不写。

v-on

事件绑定机制。v-on可以被缩写为@。

有时候需要在内联语句处理程序中访问原始DOM事件,可以使用特殊$event变量将其传递给方法。

1
2
3
<button @click="onBtnClick('事件1', $event)">
获取事件对象
</button>

存在多个事件参数时,$event通常放到最后。

  • 多事件处理

    在事件处理中可以使用逗号分隔多个事件处理程序。

    1
    2
    3
    <button @click="onOne(), onTwo()">
    执行多个事件
    </button>
  • 事件修饰符

    在事件处理程序中调用event.preventDefault( ) 阻止默认事件或event.stopPropagation( ) 阻止冒泡是十分常见的需求。v-on中存在事件修饰符,是由点开头的指令后缀来表示的。包括以下几类:

    1. stop:阻止冒泡
      js事件冒泡:在一个对象上触发某类事件,如果此对象定义了此事件的处理程序,那么此事件就会调用这个程序,如果没有定义或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直到它被处理或到达对象层级的最顶层。
    2. prevent:阻止默认事件
    3. capture:添加事件侦听器时使用事件捕获模式
    4. self:只有事件在该元素本身(比如不是子元素)触发时触发回调
    5. once:事件只触发一次
    6. passive:会告诉浏览器你不想阻止事件的默认行为

在Vue中使用样式

使用class样式

这里的class需要使用v-bind做数据绑定。

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
52
53
54
55
56
57
<!-- 1.数组 -->
<div :class="['red', 'default']">
待我长发及腰,东风笑别菡涛。 参商一面将报,百里关山人笑。
</div>
<!-- 2.在数组中使用三元表达式 -->
<div :class="['default', isActive ? 'active' : '']">
凛冬月光妖娆,似媚故国人廖。连里塞外相邀,重阳茱萸早消。
</div>
<!-- 3.在数组中使用 对象来代替三元表达式,提高代码的可读性 -->
<div :class="['default', 'italic', { active: isActive }]">
待我长发及腰,北方佳丽可好。似曾相识含苞,风花雪月明了。
</div>
<!-- 4.直接使用对象 -->
<div :class="{ default: true, italic: true, active: true }">
心有茂霜无慌,南柯一梦黄粱。相得益彰君郎,红灯澜烛入帐。
</div>
<div :class="classObj">
待我长发及腰,伊人归来可好,我已万国来朝,不见阮郎一笑。
</div>
<!-- 5. class和:class共用 -->
<div class="red bold" :class="classObj">年年月月花相似</div>

<script>
export default {
data() {
return {
isActive: false,
classObj: { default: true, italic: true, active: true },
}
}
}
</script>

<style>
.red {
color: red;
}

.default {
font-size: 14px;
line-height: 24px;
height: 24px;
}

.active {
background: green;
color: white;
}

.italic {
font-style: italic;
}

.bold {
font-weight: bold;
}
</style>

使用内联样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div :style="{ color: 'blue', 'font-size': '24px' }">
若我会见到你,事隔经年。我如何贺你,以眼泪,以沉默。
</div>
<div :style="styleObj">
最美的爱情,不是天荒,也不是地老,只是永远在一起。
</div>
<div :style="[styleBase, styleOrange]">曾经沧海难为水,除却巫山不是云</div>

<script>
export default {
data () {
return {
styleObj: { color: 'green', 'font-size': '18px' },
styleBase: { 'font-size': '18px' },
styleOrange: { color: 'orange', background: '#000000' }
}
}
}
</script>

条件判断

v-if

v-if指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回true值时被渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<p v-if="flag">柳暗花明又一村</p>
</template>

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

因为v-if是一个指令,所以必须将它绑定到一个元素上,但如果想切换多个元素呢?可以将一个<template>当做不可见的包裹元素,并在上面使用v-if。

1
2
3
4
5
<template v-if="flag">
<h1>长恨歌</h1>
<p>在天愿作比翼鸟</p>
<p>在地愿为连理枝</p>
</template>

v-if…v-else

1
2
3
4
5
6
<div v-if="Math.random() > 0.5">
我买大
</div>
<div v-else>
我押小
</div>

v-else-if

1
2
3
4
5
6
7
8
9
10
11
12
<div v-if="type === 'red'">

</div>
<div v-else-if="type === 'orange'">

</div>
<div v-else-if="type === 'yellow'">

</div>
<div v-else>
其它
</div>

v-show

其用法大概与v-if一样,不同的是带有v-show的元素始终会被渲染并保留在DOM中。v-show只是简单地切换元素的CSS property display。

v-show不支持<template>,也不支持v-else。

v-if有更高的切换开销,v-show有更高的初始渲染开销。

未完待续…

在模板中使用JavaScript表达式

对于所有的数据绑定,Vue都提供了完全的JavaScript表达式支持。

但每个绑定都只能包含单个表达式。

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
<template>
<!-- JavaScript 表达式 -->
<div>{{ age + 1 }}</div>
<div>{{ isSuccess ? '兼济天下' : '独善其身' }}</div>
<div>{{ message.split('').reverse().join('') }}</div>
<div v-bind:id="'list-' + id">{{'list-' + id}}</div>

<!-- 以下直接回报错 -->
<!-- 这是语句,不是表达式 -->
<!-- {{ var age = 1 }} -->
<!-- 流控制也不会生效,请使用三元表达式 -->
<!-- {{ if (isSuccess) { return message } }} -->

</template>

<script>
export default {
data(){
return {
age:16,
isSuccess:true,
message:'心清可品茶',
id:1,
}
}
}
</script>

计算属性

在模板中使用表达式非常便利,但它们只应该用于简单的操作。模板是为了描述视图的结构,在模板中加入大量的逻辑会让模板变得过重而难以维护。这就是为什么Vue将绑定表达式限制为一个表达式的原因。如果需要多于一个表达式的逻辑,应该使用计算属性。

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
<template>
<div v-text="reversedMessage"></div>
<button @click="setMsg()">改变数据</button>
</template>

<script>
export default {
data() {
return {
message: "僧游云隐寺",
userName: "",
nickName: "",
};
},
computed: {
// 计算属性的 getter
reversedMessage: function() {
// `this` 指向 vm 实例
return this.message
.split("")
.reverse()
.join("");
},
methods: {
setMsg() {
this.message = "晴晴雨雨时时好好奇奇";
},
},
};
</script>

可以像绑定普通property那样在模板中绑定计算属性。当vm.message发生改变时,所有依赖vm.reversedMessage的绑定也会更新。计算属性的getter函数是没有副作用的。

计算属性默认只有getter,不过在需要的时候也可以提供setter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
export default {
computed: {
fullName: {
// getter
get: function() {
return this.userName + "-" + this.nickName;
},
// setter
set: function(newValue) {
const names = newValue.split(" ");
this.userName = names[0];
this.nickName = names[names.length - 1];
},
},
},
};
</script>

watch

在Vue中,使用watch可监听响应数据的变化。虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。它的用法大体有三种

常规用法

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
<template>
<input type="text" v-model="name" />
<button @click="name = '小B'">换人</button>
<div>{{ msg }}</div>
</template>

<script>
export default {
data() {
return {
name: "小A",
msg: "",
}
},
watch: {
/**
* newVal:新值
* oldVal:旧值
*/
name(newVal, oldVal) {
this.msg = "之前是" + oldVal;
this.msg += "现在是" + newVal;
},
},
};
</script>

立即执行

常规用法中,当值第一次绑定时,不会执行监听函数,只有值改变时才会执行。

如果需要在最初绑定值的时候也执行函数,需要用到immediate属性。

1
2
3
4
5
6
7
name: {
handler(newVal, oldVal) {
this.msg = "之前是" + oldVal + "在偷窥小龙女;";
this.msg += "现在是" + newVal + "在偷窥小龙女";
},
immediate: true,
},

深度监听

当需要监听复杂数据类型(对象)的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中声明过或者父组件传递过来的props中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

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
<template>
<input type="text" v-model="user.skill" />
<button @click="user.skill = 'Python'">换语言</button>
<div>{{remark}}</div>
</template>

<script>
export default {
data() {
return {
user: {
skill: "TypeScript",
},
remark:''
};
},
watch: {
user: {
//只有一个参数的情况下表示newVal
handler(obj) {
this.remark=obj.skill;
},
deep: true,
immediate: true,
},
},
};
</script>

computed与watch的区别

计算属性computed有以下特性:

  1. 支持缓存,只有依赖数据发生改变才会重新计算。
  2. 不支持异步。
  3. 如果一个属性是由其他属性计算而来,这个属性依赖其他属性,是一个多对一或者一对一的关系,一般用computed。
  4. 如果computed属性的属性值是函数,那么默认走get方法,当数据变化时,调用set方法。

侦听属性watch有以下特性:

  1. 不支持缓存,数据变化时会直接触发相应操作。
  2. 支持异步。
  3. 监听的函数接受两个参数,第一个参数是最新的值;第二个参数是输入之前的值。
  4. 当一个属性发生变化时,会引起一系列值的变化;是一个一对多的关系。
  5. 监听数据必须是data中声明过或者父组件传递过来props中的数据。

自定义组件使用v-model实现双向数据绑定

默认情况下,组件上的v-model把value作为prop属性值,把update:value作为事件名称,我们可以通过向v-model传递参数来修改这些名词。

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<p>{{ skill }}</p>
<Child v-model:skill="skill"></Child>
</template>

<script>
import Child from '../components/ChildCompo';
export default {
components: {
Child,
},
data() {
return {
skill: 'Vue',
}
},
}
</script>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h4>{{ skill }}</h4>
<input type="text" :value="skill" @input="$emit('update:skill', $event.target.value)" />
</template>

<script>
export default {
props: {
skill: {
type: String
}
}
}
</script>

自定义组件slots

Vue实现了一套内容分发的API,这套API的设计灵感来自Web Components规范草案,将<slot>元素作为承载分发内容的出口。slot(插槽)就是子组件中提供给父组件使用的一个占位符,用<slot></slot>表示,父组件可以在这个占位符中填充任何模板代码,填充内容会替换子组件的<slot></slot>标签。

  1. 自定义一个按钮子组件,这里我们使用匿名插槽:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <template>
    <button class="primary">
    <slot>默认值</slot>
    </button>
    </template>

    <script setup lang="ts">
    </script>

    <style scoped>
    .primary {
    padding: 5px 10px;
    background: lightblue;
    color: #fff;
    border: none;
    }
    </style>
  2. 父组件调用这个子组件,在父组件中给这个占位符填充内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <b-btn>登录</b-btn>
    <b-btn><i>@</i>注册</b-btn>
    <b-btn></b-btn>
    </template>

    <script>
    import MyButton from '@/components/MyButton.vue';
    export default {
    components: {
    'b-btn': MyButton
    }
    }
    </script>

    <style scoped>
    button {
    margin: 5px;
    }
    </style>
  3. 当我们需要在自定义组件中定义多个插槽时,可以给slot指定不同的名称,也就是具名插槽

    1
    2
    3
    <slot name="icon"></slot>

    <template v-slot:icon></template>
  4. slot-scope作用域插槽

    带参数的插槽,子组件给父组件提供参数,该参数仅限插槽内使用。

    1
    2
    3
    <slot name="other" :data="list"></slot>

    <template v-slot:other="list"></template>

非prop的attribute继承

一个非prop的attribute是指传向一个组件,但是该组件并没有相应的props或emits定义的attribute,常见的示例包括class、style、id属性。

attribute继承

当组件返回单个根节点时,非prop的attribute将自动添加到根节点的attribute中。也就是说,根节点会自动继承非prop的attribute。

禁用attribute继承

如果不希望组件的根元素继承attribute,可以在组件的选项中设置inheritAttrs: false。

禁用attribute继承的常见场景是需要将attribute应用于根节点之外的其他元素,通过组件的选项中设置inheritAttrs: false,你可以访问组价的$attrs属性,该属性包括组件props和emits属性中未包含的所有属性(例如:class、style、v-on监听器等)

1
2
3
4
5
<script>
export default {
inheritAttrs: false
}
</script>

注意,如果子组件当中存在多个根节点,不显式绑定$attrs的话,将会发出运行时警告。

$ref 操作DOM

ref用来给DOM元素或子组件注册引用信息,引用信息会根据父组件的$refs对象进行注册。通俗的理解就是通过vm.$ref父组件可以直接引用子组件对象,自然也可以直接调用子组件当中的属性和方法。

通过ref=”名称”可以给需要操作的目标添加事件,或者获取自定义的data属性,ref常用场景包括:页面加载后,文本框自动聚焦。

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
<template>
<div @click="getInfo" ref="divObj" data-name="Vue">
<span>点击获取数据</span>
</div>
<input type="text" ref="input" />
</template>

<script>
export default {
data() {

},
mounted() {
this.$nextTick(() => {
this.$refs.input.focus();
this.$refs.divObj.onmouseover = () => {
console.log("鼠标进入了", this.$refs.divObj);
}
})
},
methods: {
getInfo() {
console.log(this.$refs.divObj.dataset.name);
}
}
}
</script>

组件传值

父组件传子组件

定义子组件:

1
2
3
4
5
6
7
8
9
10
<template>
<h3>JS的超集是: {{ parentName }},</h3>
<h3>它的数据类型有: {{ dataType }}</h3>
</template>

<script>
export default {
props: ["parentName", "dataType"],
};
</script>

props的类型可以是一个字符串数组,但如果希望每个prop都有指定的值类型,可以以对象形式列出prop:

1
2
3
4
props: {
parentName: String,
dataType: Array
}

当父组件没有设置需要传递过来的属性值时,还可以通过default给props中的属性指定一个默认值,此时props中的属性值是一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
props: {
parentName: {
type: String,
default: ""
},
dataType: {
type: Array,
default: () => {
return [];
},
},
},

type可以是以下原生构造函数中的一个:String、Number、Boolean、Array、Object、Date、Function、Symbol。

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<SonComp :parentName="name" :dataType="dtype"></SonComp>
</template>

<script>
import SonComp from '@/components/SonComp.vue'
export default {
components: {
SonComp
},
data(){
return {
name: "TypeScript",
dtype: ["String", "Integer", "Array"]
}
}
}
</script>

一定要使用props属性来定义父组件传递过来的数据。子组件中默认无法访问到父组件中的data上的数据和methods中的方法。

子组件中的data数据并不是通过父组件传递过来的,而是自身私有的,data上的数据都是可读可写的。

组件中的所有props中的数据都是通过父组件传递给子组件的,props中的数据都是只读的,无法重新赋值。把父组件传递过来的属性先在props中定义一下,这样才能使用这个数据。

子组件传父组件

原理:父组件将方法的引用传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据在调用方法的时候当做参数传递进去。

父组件:

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
<template>
<SonComp @func="show"></SonComp>
<div class="parent">
<span v-if="dataForm.name">
语言:{{ dataForm.name }}, 名字长度:{{ dataForm.length }}
</span>
</div>
</template>

<script>
import SonComp from './SonComp.vue';
export default {
components: {
SonComp
},
data() {
return {
dataForm: {}
}
},
methods: {
show(data) {
this.dataForm = data;
}
}
}
</script>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="lang"><input type="button" value="换语言" @click="myclick"></div>
</template>

<script>
export default {
data() {
return {
langMsg: { name: "Python", length: 6 }
}
},
methods: {
myclick() {
this.$emit("func", this.langMsg)
}
}
};
</script>

子组件内部通过this.$emit(“方法名”, 要传递的数据)方式,来调用父组件传递过来的方法,同时把数据传递给父组件使用。

组件之间的通信还可以通过使用Vuex、EventBus、localStorage、sessionStorage、cookie等。

$root 和 $parent 的使用

通常不建议通过子组件访问父组件的属性和方法,因为这样会破坏组件的封闭性。但是是可以做到的,可通过vm.$parent来引用父组件对象,然后就可以调用父组件的属性和方法了。

通过调用vm.$root,子组件可以访问根组件对象。

注:vm表示View Model(视图模型),其实就是new Vue( )对象。

this.$parent可以级联调用,如:this.$parent.$parent

this.$nextTick

this.$nextTick将回调延迟到下次DOM更新循环后执行。在修改数据后立刻使用它,然后等待DOM更新。

在Vue中,当修改了data中的某个值,并不会立即反映到DOM页面中。Vue将对data的更改放入watcher的一个队列中,只有在当前任务空闲的时候才会执行。当执行到$nextTick时,这也是一个异步事件,会被放到一个队列当中,新添加的事件会被放在队列末尾,这样就一定能获取到DOM的更新数据。

axios介绍

axios是一个基于Promise的HTTP库,可以用在浏览器和Node.js中。它有以下几个特性:

  • 从浏览器中创建XMLHttpRequests。
  • 从Node.js中创建HTTP请求。
  • 支持Promise API。
  • 拦截请求和响应。
  • 转换请求数据和响应数据。
  • 取消请求。
  • 自动转换JSON数据。
  • 客户端支持防御XSRF。

例如,执行GET请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
import axios from "axios";

axios.get("/user", {
params: {
ID: 1127
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})

在实际工作中,通常把axios的操作进行统一封装,因为我们发送的所有请求可能都要带上token进行权限验证。可以添加统一的请求过滤器,把token带上,还可以添加响应过滤器。当接受到不同的响应码时,统一进行处理。

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
import axios from "axios";

const service = axios.create({
baseURL: '/api',
timeout: 5000
})

service.interceptors.request.use(
config => {
if (localStorage.getItem("$token")) {
config.headers['X-Token'] = localStorage.getItem("$token")
}
return config
},
error => {
Promise.reject(error)
}
)

service.interceptors.response.use(
response => {
const res = response.data
if (res.Status !== '200') {
this.$message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
if (res.Status === '401' || res.Status === '402') {
//退出登录
}
return Promise.reject('error')
} else {
return response.data
}
},
error => {
console.log('err' + error)
this.$message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject('error')
}
)
export default service

除了axios,还有一个fetch也可以用于http请求封装,但是实际工作中fetch用的较少。

跨域请求

浏览器具有安全限制,不允许AJAX访问协议不同、域名不同、端口号不同的数据接口,因为浏览器认为这种访问不安全,而在实际工作中,无可避免会遇到跨域问题。目前实现跨域的最常用方式有如下几种:

  • JSONP
  • 代理
  • 后端接口跨域支持

JSONP实现原理

可以通过动态创建script标签的形式,把script标签的src属性指向数据接口的地址,因为script标签不存在跨域限制。(根据其原理可知JSONP只支持Get请求,所以实际开发中并不常用。)

代理

axios支持代理配置,可以通过设置代理来防止跨域问题。通常在代码中对axios进行全局代理配置,当发布到生产服务器时,在通过Nginx进行请求转发,这样前端代码和后端接口就可以部署在不同的服务器上,也不会产生跨域问题。

开发环境的代理配置:

在vue.config.js中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
devServer: {
port: 8888,
proxy: {
"/api": {
//本地服务接口地址
target: "http://10.200.1.200:8888", //开发环境下后端接口地址
ws: true,
pathRewrite: {
"^/api": "/",
}
}
}
}
}

这样的话,每一个请求后台的路径前都要加一个/api。

生产环境需要修改Nginx代理配置文件nginx.conf。

后端接口跨域支持

后端程序员通过过滤器对接口请求进行配置,从而准许接口能够被跨域访问。

extend、mixin和extends

Vue.extend只是创建一个构造器,它是为了创建可复用的组件,参数是一个包含组件选项的对象。当我们调用Vue.component(‘a’, {…})时会自动调用Vue.extent。

Vue.component是用来注册或获取全局组件的方法,其作用是通过Vue.extend生成的扩展实例构造器注册(命名)为一个组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
// Vue.extend
// 创建构造器
var Profile = Vue.extend({
template: "<p>{{firstName}}-{{lastName}}-{{alias}}</p>",
data: function() {
return {
userName: "文泰来",
nickName: "奔雷手",
skill: "奔雷掌",
};
},
});
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount("#myApp");
//注册人一个全局组件
Vue.component('global-component', Profile);
</script>

extends允许声明扩展另一个组件,而无需使用Vue.extend。主要是为了便于扩展单文件组件。

mixins选项接受一个混入对象的数组。这些混入实例对象可以像正常的实例对象一样包含选项。

代码执行优先级:extend > extends > mixins。

Composition API

Vue3与Vue2之间最大的不同之处在于新增了Composition API。

Vue3集成TypeScript

在所有.vue文件中,script标签中添加lang=”ts”属性就可以在<script>中使用TS的语法了。若要使用scss,则需要安装相关的loader:

1
yarn sass-loader node-sass-D

setup

setup函数是处于生命周期函数beforeCreate之前的函数,新的option、所有的组合API函数都可以在此使用,并且只在初始化时执行一次。函数如果返回对象,对象中的属性或方法,模块中都可以直接使用。

setup细节

setup执行时机:

  1. 在beforeCreate之前执行(一次),此时组件对象还没有创建。
  2. 在setup中,this是undefined,不能通过this来访问data/computed/methods/props。
  3. 在所有的composition API相关回调函数中都不可以访问。

setup的返回值:

  1. 一般是一个对象,为模板提供数据,模板中可以直接使用此对象中所有方法/属性。
  2. 返回对象中的属性会与data函数返回对象的属性合并成组件对象的属性。
  3. 返回对象中的方法会与methods中的方法合并成组件对象的方法。
  4. 如果有重名,setup优先。

注意:一般不要混合使用setup、methods、data,因为methods中可以访问setup提供的属性和方法,但在setup中不能访问methods和data。

setup不能是一个async函数,因为返回值会是一个promise,模板看不到return对象中的属性数据。

setup的参数:

  1. setup(props, context) / setup(props, {attrs, slots, emit})
  2. props:包含props配置声明且传入了的所有属性的对象。
  3. attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs。
  4. slots:包含所有传入的插槽内容的对象,相当于this.$slots。
  5. emit:用来分发自定义事件的函数,相当于this.$emit。

props和attrs的区别

  1. props要先声明才能取值,attrs不用先声明。
  2. props声明过的属性,attrs里不会再出现。
  3. props不包含事件,attrs包含事件。
  4. props支持string以外的类型,attrs只有string类型。

ref

作用:定义一个数据的响应式

1
const xx = ref(initValue)

创建一个包含响应式数据的引用对象,js中操作数据要xx.value,模板中操作不需要,一般用来定义一个基本类型的响应式数据。

响应式数据:数据变化页面跟着渲染变化。

reactive

作用:定义多个数据的响应式(对象)

1
const proxy = reactive(obj)

响应式转换是深层的,它会影响对象内部所有嵌套对象的属性。

ref和reactive的区别

ref用来处理基本类型数据,reactive用来处理对象。ref内部通过给value添加getter/setter来实现对数据的劫持,reactive内部通过使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据。

计算属性和监视

computed函数:与computed配置功能一致,可以只有getter,也可以同时有getter和setter。

watch函数:与watch配置功能一致,监视指定的一个或多个响应式数据,一旦数据变化,就自动执行监视回调。默认初始不执行回调,但可以通过配置immediate为true来指定初始时立即执行第一次,通过配置deep为true,可以指定深度监视。

watchEffect函数:不用指定要监视的数据,回调函数中使用了哪些响应式数据就监视哪些。默认初始时执行第一次。

watchEffect与watch的区别:

  1. watchEffect不需要手动传入依赖
  2. watchEffect每次初始化时会执行一次回调函数来自动获取依赖
  3. watchEffect无法获取到原值,只能得到变化后的值

组件生命周期

vue2.png

自定义hook函数

使用Vue3组合API封装的可复用的功能函数,优势是复用功能代码的来源清楚,更清晰易懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ref, onUnmounted, onMounted } from 'vue';

export function useMousePosition() {
const x = ref(-1)
const y = ref(-1)
const updatePosition = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}

onMounted(() => {
document.addEventListener('click', updatePosition)
})

onUnmounted(() => {
document.removeEventListener('click', updatePosition)
})
return { x, y }
}

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref。

当从合成函数返回响应式对象时,toRefs非常有用。

ref获取元素

利用ref函数可以获取组件中的标签元素。

在Vue3中,我们声明一个ref的同名响应式属性并在setup中返回,这样这个响应式属性就是实际的DOM或者组件,注意取值时是inputRef.value。

shallowReactive和shallowRef

shallowReactive:只处理了对象内最外层属性的响应式(浅响应式)

shallowRef:只处理value的响应式,不进行对象的reactive处理。

什么时候使用浅响应式:

  1. 一般情况使用ref和reactive即可
  2. 如果一个对象数据比较深,但变化时只是外层属性变化,可以使用shallowReactive
  3. 如果一个对象数据会被新产生的对象替换,可以使用shallowRef

readonly和shallowReadonly

readonly

  1. 深度只读数据
  2. 获取一个对象或ref的只读代理
  3. 任何嵌套property也是只读的

shallowReadonly:浅只读数据,只有自身的property只读。

toRaw和markRaw

toRaw:返回由reactive或readonly方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。

markRaw:标记一个对象,使其永远不会转换为代理,而是返回对象本身。

应用场景:

  1. 有些值不应被设置为响应式的,例如复杂的第三方类实例或Vue组件对象。
  2. 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。

toRef、unRef、customRef

为原响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的。

ref:拷贝了一份新的数据值单独操作,更新时互不影响。

unRef:如果参数是ref属性,则返回它的值,否则返回本身。

customRef:用于创建一个自定义的ref对象,可以显式的控制其依赖跟踪和触发响应。它接受两个参数,分别是用于追踪的track和用于触发响应的trigger,并返回一个带有get和set属性的对象。

provide和inject,响应式数据判断

Vue的$parent属性可以让子组件访问父组件,但孙组件访问祖先组件就比较困难。provide和inject提供依赖注入。provide和inject是成对出现的,在主组件中通过provide提供数据和方法,在下级组件中通过inject调用主组件提供的数据和方法。

响应式数据判断:isRef、isReactive、isReadonly、isProxy

Vue3新组件和新API

Fragment(片段)

在Vue2中,组件必须有一个根标签。在3中可以没有。内部会将多个标签包含在一个虚拟的Fragment元素中,好处是减少标签层级,减小内存占用。Fragment是虚拟的,不会在DOM树中出现。

Teleport(瞬移)

Vue3.x中组件模板属于该组件自身,有时候我们想把模板的内容移动到当前组件之外的DOM中,这时就可以用Teleport。

Suspense(不确定的)

Suspense允许我们的应用程序在等待异步组件时渲染一些后备内容,创建一个平滑的用户体验。每当我们希望组件等待数据获取时(通常在异步API调用中),我们都可以制作异步组件,主要用于解决异步加载组件问题。

异步组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<h2>AsyncComponent子级组件</h2>
<h3>{{ msg }}</h3>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'AsyncComponent',
setup() {
return new Promise((resolve, reject) => {
//模拟异步加载数据
setTimeout(() => {
resolve({
msg: '终于等到你'
})
}, 2000)
}).then((res: any) => {
return { msg: res.msg }
});
},
})
</script>

引入异步组件:

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
<template>
<div v-if="errMsg"> {{ errMsg }} </div>
<Suspense>
<template v-slot:default>
<!--异步组件-->
<AsyncComponent></AsyncComponent>
</template>
<template v-slot:fallback>
<!--loading的内容-->
<h2>数据加载中.....</h2>
</template>
</Suspense>
</template>

<script lang="ts">
import { onErrorCaptured, ref } from "vue";
import { defineComponent, defineAsyncComponent } from "vue";
// Vue3中的动态引入组件的写法
const AsyncComponent = defineAsyncComponent(
() => import("../components/AsyncComponent.vue")
);
// 静态引入组件
// import AsyncComponent from '../components/AsyncComponent.vue';
export default defineComponent({
components: {
AsyncComponent,
},
setup() {
let errMsg = ref("");
onErrorCaptured((ex) => {
errMsg.value = "报错了" + ex;
return true;
});
return { errMsg };
},
});
</script>

全新的全局API

createAPP()

调用createApp会返回一个应用实例,应用实例会暴露当前全局API的子集。经验法则是:把所有全局改变Vue行为的API都移动到应用实例上。

可以在createApp之后链式调用其他方法。createApp函数接受一个根组件选项对象作为第一个参数:

1
2
3
4
5
6
7
8
9
10
const app = Vue.createApp({
data() {
return {

}
},
methods: {...},
computed: {...}
...
})

使用第二个参数,可以将根props传递给应用程序:

1
2
3
4
5
6
const app = Vue.createApp({
{
props: ['username']
},
{username: 'XXX'}
})

Vue3优先使用Proxy

Vue3使用ES6的Proxy作为其观察者机制,取代之前的Object.defineProperty。

defineComponent和defineAsyncComponent

defineComponent用于定义组件,只返回传递给它的对象,只是对setup进行封装,返回options的对象。其最重要的功能是在TS下给予了组件正确的参数类型推断。

defineAsyncComponent用于定义异步组件。

nextTick()

Vue实现响应式并不是数据发生变化后DOM立即变化,而是按一定的策略进行DOM的更新。JS事件循环:JS的任务队列分为同步任务和异步任务,所有的同步任务都是在主线程中执行的。异步任务可能会在macrotask(宏任务)或者microtask(微任务)里面。

微任务是指在当前任务执行结束后立即执行的任务,它可以看作是在当前任务的“尾巴”添加的任务。常见的微任务包括 Promise 回调和 process.nextTick。 宏任务是指需要排队等待 JavaScript 引擎空闲时才能执行的任务。常见的宏任务包括 setTimeout、setInterval、I/O 操作、DOM 事件等。 JavaScript 引擎会先执行当前任务中的所有微任务,然后再执行宏任务队列中的第一个任务。这个过程会不断重复,直到宏任务队列中的任务被全部执行完毕。

将原来的全局API转移到应用对象

以下对象都由原来的Vue.[方法名]改为了createApp(App).[方法名]

  1. component
  2. config
  3. directive
  4. mount
  5. unmount
  6. use

v-if与v-for优先级

在3.x版本中,v-if总是优先于v-for生效