Vue3相关技术
Vue3快速上手
简介
Vue是一套用于构建用户界面的渐进式框架。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Vue3发布于2020年9月19日,它在Vue2.x的基础上进行了一些优化,对TypeScript有了更好的支持。
vue-devtools
vue-devtools是一个Chrome浏览器的插件,是官方提供的一个Vue开发者工具,方便用户在开发Vue项目时调试。可以在官网安装,但操作较为复杂。访问极简插件网
在线安装较为简单。
创建Vue3项目
可以使用vue-cli创建。
安装或升级vue cli,保证版本在4.5.0以上
1 | vue --version |
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
2
3<ul>
<li v-for="(item, index) in list" :key="index">{{++ index}}.{{item}}</li>
</ul>迭代对象数组
1
2
3
4<ul>
<li v-for="(item, index) in users" :key="index">
{{index++}}.[{{item.title}}]{{item.name}}</li>
</ul>迭代对象中的属性
1
2
3<p v-for="(val, key, index) in userInfo" :key="index">
键是:{{key}},值是:{{value}},索引:{{index}}
</p>迭代数字
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 | <button @click="onBtnClick('事件1', $event)"> |
存在多个事件参数时,$event通常放到最后。
多事件处理
在事件处理中可以使用逗号分隔多个事件处理程序。
1
2
3<button @click="onOne(), onTwo()">
执行多个事件
</button>事件修饰符
在事件处理程序中调用event.preventDefault( ) 阻止默认事件或event.stopPropagation( ) 阻止冒泡是十分常见的需求。v-on中存在事件修饰符,是由点开头的指令后缀来表示的。包括以下几类:
- stop:阻止冒泡
js事件冒泡:在一个对象上触发某类事件,如果此对象定义了此事件的处理程序,那么此事件就会调用这个程序,如果没有定义或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直到它被处理或到达对象层级的最顶层。 - prevent:阻止默认事件
- capture:添加事件侦听器时使用事件捕获模式
- self:只有事件在该元素本身(比如不是子元素)触发时触发回调
- once:事件只触发一次
- passive:会告诉浏览器你不想阻止事件的默认行为
- stop:阻止冒泡
在Vue中使用样式
使用class样式
这里的class需要使用v-bind做数据绑定。
1 | <!-- 1.数组 --> |
使用内联样式
1 | <div :style="{ color: 'blue', 'font-size': '24px' }"> |
条件判断
v-if
v-if指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回true值时被渲染。
1 | <template> |
因为v-if是一个指令,所以必须将它绑定到一个元素上,但如果想切换多个元素呢?可以将一个<template>当做不可见的包裹元素,并在上面使用v-if。
1 | <template v-if="flag"> |
v-if…v-else
1 | <div v-if="Math.random() > 0.5"> |
v-else-if
1 | <div v-if="type === 'red'"> |
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 | <template> |
计算属性
在模板中使用表达式非常便利,但它们只应该用于简单的操作。模板是为了描述视图的结构,在模板中加入大量的逻辑会让模板变得过重而难以维护。这就是为什么Vue将绑定表达式限制为一个表达式的原因。如果需要多于一个表达式的逻辑,应该使用计算属性。
1 | <template> |
可以像绑定普通property那样在模板中绑定计算属性。当vm.message发生改变时,所有依赖vm.reversedMessage的绑定也会更新。计算属性的getter函数是没有副作用的。
计算属性默认只有getter,不过在需要的时候也可以提供setter。
1 | <script> |
watch
在Vue中,使用watch可监听响应数据的变化。虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。它的用法大体有三种
常规用法
1 | <template> |
立即执行
常规用法中,当值第一次绑定时,不会执行监听函数,只有值改变时才会执行。
如果需要在最初绑定值的时候也执行函数,需要用到immediate属性。
1 | name: { |
深度监听
当需要监听复杂数据类型(对象)的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中声明过或者父组件传递过来的props中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
1 | <template> |
computed与watch的区别
计算属性computed有以下特性:
- 支持缓存,只有依赖数据发生改变才会重新计算。
- 不支持异步。
- 如果一个属性是由其他属性计算而来,这个属性依赖其他属性,是一个多对一或者一对一的关系,一般用computed。
- 如果computed属性的属性值是函数,那么默认走get方法,当数据变化时,调用set方法。
侦听属性watch有以下特性:
- 不支持缓存,数据变化时会直接触发相应操作。
- 支持异步。
- 监听的函数接受两个参数,第一个参数是最新的值;第二个参数是输入之前的值。
- 当一个属性发生变化时,会引起一系列值的变化;是一个一对多的关系。
- 监听数据必须是data中声明过或者父组件传递过来props中的数据。
自定义组件使用v-model实现双向数据绑定
默认情况下,组件上的v-model把value作为prop属性值,把update:value作为事件名称,我们可以通过向v-model传递参数来修改这些名词。
父组件:
1 | <template> |
子组件:
1 | <template> |
自定义组件slots
Vue实现了一套内容分发的API,这套API的设计灵感来自Web Components规范草案,将<slot>元素作为承载分发内容的出口。slot(插槽)就是子组件中提供给父组件使用的一个占位符,用<slot></slot>表示,父组件可以在这个占位符中填充任何模板代码,填充内容会替换子组件的<slot></slot>标签。
自定义一个按钮子组件,这里我们使用匿名插槽:
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>父组件调用这个子组件,在父组件中给这个占位符填充内容:
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>当我们需要在自定义组件中定义多个插槽时,可以给slot指定不同的名称,也就是具名插槽
1
2
3<slot name="icon"></slot>
<template v-slot:icon></template>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 | <script> |
注意,如果子组件当中存在多个根节点,不显式绑定$attrs的话,将会发出运行时警告。
$ref 操作DOM
ref用来给DOM元素或子组件注册引用信息,引用信息会根据父组件的$refs对象进行注册。通俗的理解就是通过vm.$ref父组件可以直接引用子组件对象,自然也可以直接调用子组件当中的属性和方法。
通过ref=”名称”可以给需要操作的目标添加事件,或者获取自定义的data属性,ref常用场景包括:页面加载后,文本框自动聚焦。
1 | <template> |
组件传值
父组件传子组件
定义子组件:
1 | <template> |
props的类型可以是一个字符串数组,但如果希望每个prop都有指定的值类型,可以以对象形式列出prop:
1 | props: { |
当父组件没有设置需要传递过来的属性值时,还可以通过default给props中的属性指定一个默认值,此时props中的属性值是一个对象。
1 | props: { |
type可以是以下原生构造函数中的一个:String、Number、Boolean、Array、Object、Date、Function、Symbol。
父组件:
1 | <template> |
一定要使用props属性来定义父组件传递过来的数据。子组件中默认无法访问到父组件中的data上的数据和methods中的方法。
子组件中的data数据并不是通过父组件传递过来的,而是自身私有的,data上的数据都是可读可写的。
组件中的所有props中的数据都是通过父组件传递给子组件的,props中的数据都是只读的,无法重新赋值。把父组件传递过来的属性先在props中定义一下,这样才能使用这个数据。
子组件传父组件
原理:父组件将方法的引用传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据在调用方法的时候当做参数传递进去。
父组件:
1 | <template> |
子组件:
1 | <template> |
子组件内部通过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 | import axios from "axios"; |
在实际工作中,通常把axios的操作进行统一封装,因为我们发送的所有请求可能都要带上token进行权限验证。可以添加统一的请求过滤器,把token带上,还可以添加响应过滤器。当接受到不同的响应码时,统一进行处理。
1 | import axios from "axios"; |
除了axios,还有一个fetch也可以用于http请求封装,但是实际工作中fetch用的较少。
跨域请求
浏览器具有安全限制,不允许AJAX访问协议不同、域名不同、端口号不同的数据接口,因为浏览器认为这种访问不安全,而在实际工作中,无可避免会遇到跨域问题。目前实现跨域的最常用方式有如下几种:
- JSONP
- 代理
- 后端接口跨域支持
JSONP实现原理
可以通过动态创建script标签的形式,把script标签的src属性指向数据接口的地址,因为script标签不存在跨域限制。(根据其原理可知JSONP只支持Get请求,所以实际开发中并不常用。)
代理
axios支持代理配置,可以通过设置代理来防止跨域问题。通常在代码中对axios进行全局代理配置,当发布到生产服务器时,在通过Nginx进行请求转发,这样前端代码和后端接口就可以部署在不同的服务器上,也不会产生跨域问题。
开发环境的代理配置:
在vue.config.js中添加如下配置
1 | module.exports = { |
这样的话,每一个请求后台的路径前都要加一个/api。
生产环境需要修改Nginx代理配置文件nginx.conf。
后端接口跨域支持
后端程序员通过过滤器对接口请求进行配置,从而准许接口能够被跨域访问。
extend、mixin和extends
Vue.extend只是创建一个构造器,它是为了创建可复用的组件,参数是一个包含组件选项的对象。当我们调用Vue.component(‘a’, {…})时会自动调用Vue.extent。
Vue.component是用来注册或获取全局组件的方法,其作用是通过Vue.extend生成的扩展实例构造器注册(命名)为一个组件。
1 | <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执行时机:
- 在beforeCreate之前执行(一次),此时组件对象还没有创建。
- 在setup中,this是undefined,不能通过this来访问data/computed/methods/props。
- 在所有的composition API相关回调函数中都不可以访问。
setup的返回值:
- 一般是一个对象,为模板提供数据,模板中可以直接使用此对象中所有方法/属性。
- 返回对象中的属性会与data函数返回对象的属性合并成组件对象的属性。
- 返回对象中的方法会与methods中的方法合并成组件对象的方法。
- 如果有重名,setup优先。
注意:一般不要混合使用setup、methods、data,因为methods中可以访问setup提供的属性和方法,但在setup中不能访问methods和data。
setup不能是一个async函数,因为返回值会是一个promise,模板看不到return对象中的属性数据。
setup的参数:
- setup(props, context) / setup(props, {attrs, slots, emit})
- props:包含props配置声明且传入了的所有属性的对象。
- attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs。
- slots:包含所有传入的插槽内容的对象,相当于this.$slots。
- emit:用来分发自定义事件的函数,相当于this.$emit。
props和attrs的区别
- props要先声明才能取值,attrs不用先声明。
- props声明过的属性,attrs里不会再出现。
- props不包含事件,attrs包含事件。
- 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的区别:
- watchEffect不需要手动传入依赖
- watchEffect每次初始化时会执行一次回调函数来自动获取依赖
- watchEffect无法获取到原值,只能得到变化后的值
组件生命周期
自定义hook函数
使用Vue3组合API封装的可复用的功能函数,优势是复用功能代码的来源清楚,更清晰易懂。
1 | import { ref, onUnmounted, onMounted } from 'vue'; |
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref。
当从合成函数返回响应式对象时,toRefs非常有用。
ref获取元素
利用ref函数可以获取组件中的标签元素。
在Vue3中,我们声明一个ref的同名响应式属性并在setup中返回,这样这个响应式属性就是实际的DOM或者组件,注意取值时是inputRef.value。
shallowReactive和shallowRef
shallowReactive:只处理了对象内最外层属性的响应式(浅响应式)
shallowRef:只处理value的响应式,不进行对象的reactive处理。
什么时候使用浅响应式:
- 一般情况使用ref和reactive即可
- 如果一个对象数据比较深,但变化时只是外层属性变化,可以使用shallowReactive
- 如果一个对象数据会被新产生的对象替换,可以使用shallowRef
readonly和shallowReadonly
readonly
- 深度只读数据
- 获取一个对象或ref的只读代理
- 任何嵌套property也是只读的
shallowReadonly:浅只读数据,只有自身的property只读。
toRaw和markRaw
toRaw:返回由reactive或readonly方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
markRaw:标记一个对象,使其永远不会转换为代理,而是返回对象本身。
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或Vue组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
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 | <template> |
引入异步组件:
1 | <template> |
全新的全局API
createAPP()
调用createApp会返回一个应用实例,应用实例会暴露当前全局API的子集。经验法则是:把所有全局改变Vue行为的API都移动到应用实例上。
可以在createApp之后链式调用其他方法。createApp函数接受一个根组件选项对象作为第一个参数:
1 | const app = Vue.createApp({ |
使用第二个参数,可以将根props传递给应用程序:
1 | const app = Vue.createApp({ |
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).[方法名]
- component
- config
- directive
- mount
- unmount
- use
v-if与v-for优先级
在3.x版本中,v-if总是优先于v-for生效