Vue笔记-系列
Vue笔记[一]-初识
Vue笔记[二]-组件化
Vue笔记[三]-ToDoList
Vue笔记[四]-动画、Vuex
Vue笔记[五]-路由
Vue笔记[六]-Vue3
Vite、Pinia、Router
QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

Vue3

Vue3 是由尤雨溪99位贡献者开发的一款前端框架。于 2020/09/18 正式发布,耗时2年多、2600+次提交30+个RFC600+次PR,在这里,框架将授予你「声明式」、「组件化」的编程模型,导引「响应式」之力。你将扮演一位名为「CV工程师」的神秘角色在自由的编码中邂逅样式各异、功能独特的组件库们,和他们一起解决问题,完成多样的需求——同时,逐步发掘「组合式API」的真相。

注意:尽管 Vue3 兼容大部分 Vue2 写法,但组合式 API 写法下,最好不要混用

Vue-cli创建工程

Vue3 推荐使用 Vite 来创建工程,但刚学完 vue2 还是接着用 Vue-cli 把 Vue3 新的语法学完,再去学习 Vite 和 pinia 的使用吧

工程结构和 vue2 中的一样,但文件内有些变化:

main.js
1
2
3
4
5
6
7
8
9
10
// 引入的不再是Vue构造函数
// 而是createApp工厂函数,无需通过new去调用
import { createApp } from 'vue'
import App from './App.vue'

// 创建应用实例,类似vm,但比vm更轻
const app = createApp(App)
// 挂载
app.mount('#app')

单文件组件模板可以没有根元素

1
2
3
4
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

初识setup

setup 是 Vue3 中一个新的配置项,值为一个函数

组件中所有的数据、方法都要配置在 setup

setup函数有两种返回值
(1) 返回一个对象,对象中的所有属性都可以在组件模板中使用
(2) 返回一个渲染函数,会替换掉组件模板(基本用不到)

setup 执行是在创建实例之前,也就是 beforeCreate (之前)执行,所以 setup 函数中的 this 不是组件的实例,而是undefined,setup是同步

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>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="showName">点击</button>
</template>
<script>
export default {
name: 'App',
// setup配置项
setup(){
// 定义一些数据和方法
let name = 'chuckle';
let age = 20;
function showName(){
alert(name);
}
// 返回一个对象
return {
name,
age,
showName,
}
}
}
</script>

< script setup >

setup() 函数中手动暴露大量的状态和方法非常繁琐。可以通过构建工具来简化该操作。

<script> 标签加上 setup 属性,里面的代码会被编译成 setup() 函数的内容

与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="showName">点击</button>
</template>
<script setup>
// 定义一些数据和方法
let name = 'chuckle';
let age = 20;
function showName() {
alert(name);
}
</script>

setup带来的改变:

  1. 解决了vue2的data和methods方法相距太远,无法组件之间复用
  2. 提供了script标签引入共同业务逻辑的代码块,顺序执行
  3. script变成setup函数,默认暴露给模版
  4. 组件直接挂载,无需注册
  5. 自定义的指令也可以在模版中自动获得
  6. this不再是这个活跃实例的引用
  7. 带来的大量全新api,比如defineProps,defineEmits,withDefault,toRef,toRefs

响应式

对比探究 Vue3 中的响应式使用与实现

ref函数

setup() 返回的数据并没有自带响应式的效果,在 Vue3 中要实现响应式,需要使用 ref() 对数据进行处理

作用:ref() 定义响应式变量,将传入的值包装为一个带 value 属性的 ref 对象(引用实现对象),允许我们创建可以使用任何值类型响应式数据

JS 中需要 .value 才能获取或修改数据,模板中直接写 ref 对象名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="change">改变</button>
</template>
<script setup>
// Vue3 遵循按需引入的理念,所以许多 vue 中的东西需要引入后才能使用
import { ref } from 'vue';
// 定义一些数据和方法
let name = ref('chuckle');
let age = ref(20);
// 修改数据,响应式
function change() {
name.value = 'qx';
age.value = 19;
}
</script>

ref() 传入值为基本数据类型时,其响应式本质和 vue2 一样,是通过 Object.defineProperty()gettersetter 进行数据劫持数据代理 实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 'chuckle', _value: 'chuckle'}
dep: Set(1) {ReactiveEffect}
__v_isRef: true
__v_isShallow: false
_rawValue: "chuckle"
_value: "chuckle"
// 熟悉的(...)数据代理
value: (...) // "chuckle"
[[Prototype]]: Object
constructor: class RefImpl
// 熟悉的(...)数据劫持,添加get和set
value: (...) //"chuckle"
// 熟悉的 getter 和 setter
get value: ƒ value()
set value: ƒ value(newVal)
[[Prototype]]: Object

ref() 传入值为对象类型时,会去调用 reactive() 处理 value,而 reactive() 返回一个 Proxy 类型的对象

暂时看不懂的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: {…}, _value: Proxy(Object)}
dep: undefined
__v_isRef: true
__v_isShallow: false
_rawValue: {a: 1, b: 2}
_value: Proxy(Object)
[[Handler]]: Object
[[Target]]: Object
[[IsRevoked]]: false
value: Proxy(Object)
[[Handler]]: Object
[[Target]]: Object
a: 1
b: 2
[[Prototype]]: Object
[[IsRevoked]]: false
[[Prototype]]: Object

reactive函数

reactive() 传入一个对象类型,返回该对象类型的代理对象(Proxy的实例对象,简称proxy对象)

reactive() 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的原始类型无效

proxy 对象直接用变量名访问数据,无需 .value

可以直接通过下标响应式地修改数组数据,这是通过 defineProperty 实现的响应式所做不到的

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
<template>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="change">改变</button>
<h3>{{ obj.a }}</h3>
<h3>{{ obj.b }}</h3>
<h3>{{ arr.toString() }}</h3>
</template>

<script setup>
import { ref, reactive } from 'vue';
// 定义一些数据和方法
let name = ref('chuckle');
let age = ref(20);
let obj = reactive({
a: 1,
b: 2,
});
let arr = [1,2,3];
// 修改数据,响应式
function change() {
name.value = 'qx';
age.value++;
// proxy对象无需.value,可以直接访问
obj.a = 10;
obj.b++;
// 可以直接通过下标响应式地修改数组
arr[0] = 10;
}
</script>

定义响应式数据时,通常基本数据类型用 ref(),引用数据类型用 reactive()

但由于 ref() 定义的响应式数据需要 .value 才能获取和修改数据值,所以也通常将基本数据类型扔进一个对象中,然后用 reactive(),可以省去 .value

Vue2的响应式原理

依靠 Object.defineProperty()

实现原理:

  1. 基本数据类型:通过 defineProperty 进行数据劫持与代理
  2. 对象类型:深度遍历对象,通过 defineProperty 进行数据劫持与代理
  3. 数组类型:通过重写更新数组的一系列方法(push等)

存在问题:

  1. 深度遍历对象存在性能与效率问题
  2. 对象新增、删除属性,无法响应式,需用 $set 和 $delete
  3. 直接通过下标修改数组,无法响应式

Vue3的响应式原理

依靠 ES6 中新的 API window.Proxy() 构造函数,解决了 Vue2 响应式中的问题

Proxy() 返回 proxy 对象,它可以代理对源数据任何属性任何操作

Proxy() 传入两个参数,第一个:要代理的源数据,第二个:一个配置对象,其中有 get、set 等方法,用于设置拦截
如果Proxy的第二个参数(配置对象)没有设置任何拦截,就等同于直接访问原对象

尝试使用 Proxy() 实现代理数据操作:

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
// 源数据
let obj = {
name: 'chuckle',
age: 20
}
// 通过Proxy实例代理对源数据的增删改查操作
const agent = new Proxy(obj, {
// 对操作的拦截配置
// 获取属性值
get(target, propName){
console.log(`读取了${propName}属性`);
// 返回源数据的值
return target[propName]
},
// 修改属性值、新增属性
set(target, propName, value){
console.log(`修改或新增了${propName}属性,去响应式地更新视图`);
// 实际地修改源数据的值
target[propName] = value
},
// 删除属性
deleteProperty(target, propName){
console.log(`删除了${propName}属性,去响应式地更新视图`);
// 实际地删除源数据地属性,返回删除成功与失败地布尔值
return delete target[propName]
}
});

注意:后续在 proxy 对象上添加新属性也是响应式的

虽然上面已经模仿实现了”响应式”,但 Vue3 中还用到了 window.Reflect 对象

Proxy() 用于代理(代理对数据的操作),Reflect 用于反射(实际对数据的操作)

Reflect 用法:

  1. Reflect.get(target, prop) 获取某个对象(target)中某个属性(prop)的值
  2. Reflect.set(target, prop, value) 修改某个对象中某个属性的值为value
  3. Reflect.deleteProperty(target, prop) 删除某个对象中的某个属性

Reflect 身上还有很多方法,ECMA 也在将原本 Object 中的方法移植到 Reflect 中

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
// 源数据
let obj = {
name: 'chuckle',
age: 20
}
// 通过Proxy代理对源数据的增删改查操作
const agent = new Proxy(obj, {
// 对操作的拦截配置
// 获取属性值
get(target, propName){
console.log(`读取了${propName}属性`);
// 返回源数据的值
return Reflect.get(target, propName)
},
// 修改属性值、新增属性
set(target, propName, value){
console.log(`修改或新增了${propName}属性,去响应式地更新视图`);
// 实际地修改源数据的值
return Reflect.set(target, propName, value)
},
// 删除属性
deleteProperty(target, propName){
console.log(`删除了${propName}属性,去响应式地更新视图`);
// 实际地删除源数据地属性,返回删除成功与失败地布尔值
return Reflect.deleteProperty(target, propName)
}
});

MDN 文档:ProxyReflect

总结 Vue3 响应式的实现原理:

  1. 通过Proxy(代理):拦截对象中任意属性的变化, 包括增删改查等。
  2. 通过Reflect(反射):对源对象的属性进行实际操作。

props声明

和 Vue2 一样,一个组件需要显式声明它所接受的 props

<script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明

1
2
3
4
5
<script setup>
import { defineProps } from 'vue';
const props = defineProps(['foo'])
console.log(props.foo)
</script>

没有使用 <script setup> 的组件中,prop 可以使用 props 选项来声明:

1
2
3
4
5
6
7
export default {
props: ['foo'], // 声明props
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}

props 也是一个 proxy 对象,也能实现响应式

注意:所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,不应该在子组件中去更改一个 prop。

context上下文

contextsetup(props, context) 的第二个参数,代表上下文

context 中有一些属性和方法

  1. attrs 没有接收的 prop 都会出现在里面
  2. emit() 触发父组件绑定来的自定义事件
  3. slots 没有接收的 slot 都会出现在里面

<script setup> 中 useContext() 已经废弃

需要使用独立的 API 获取原本 context 中的属性和方法:

  1. useAttrs() 没有接收的 prop 都会出现在里面
  2. defineEmits() 接收父组件绑定的自定义事件
  3. useSlots() 获取父组件中插槽传递的所有虚拟Dom对象,按插槽名字区分

需要引入后使用:

1
import { useAttrs,defineEmits, useSlots } from 'vue';

1、useAttrs()

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父组件 -->
<student :a="1" b="giggles"></student>

<!-- 子组件 -->
<script setup>
// 接收a
const props = defineProps(['a'])
console.log(props)
// 没接收的b会在里面
const attrs = useAttrs();
console.log(attrs) // 一个proxy对象
</script>

2、defineEmits()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 父组件 -->
<student @emitTest="emitTest"></student>
<script setup>
function emitTest(value) {
console.log(value);
}
</script>

<!-- 子组件 -->
<script setup>
// 接收自定义事件
const emit = defineEmits(['emitTest']);
// 触发自定义事件
emit('emitTest', name)
</script>

3、useSlots()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 父组件 -->
<student>
<template #slotTest>
<h2>插槽</h2>
</template>
</student>

<!-- 子组件 -->
<slot name="slotTest"></slot>
<script setup>
const slots = useSlots();
console.log(slots)
</script>

defineExpose函数

子组件通过 defineExpose() 向父组件暴露数据

当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 父组件 -->
<student ref="stu"></student>
<script setup>
// 接收子组件
let stu = ref(null);
onMounted(() => {
console.log(stu.value.name); // chuckle
console.log(stu.value.city); // 北京
})
</script>

<!-- 子组件 -->
<script setup>
let name = ref("chuckle");
let city = ref("北京");
// 暴露数据
defineExpose({
name,
city
})
</script>

computed计算属性

computed() 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { computed } from 'vue';
let person = reactive({
firstName: '张',
lastName: '三'
})
let name = computed(()=>{
return person.firstName + person.lastName;
})

// 可读写的:
let refObj = computed({
get(){},
set(value){}
})

watch函数

watch() 监视一个或多个响应式数据源,并在数据源变化时调用所给的回调函数

返回值是一个用来停止监听的的函数

基本使用:

1
2
3
4
5
6
watch(refObj, (newValue, oldValue)=>{
console.log(newValue, oldValue);
}, {
// 配置对象
deep: true,
})

1、可以同时监视多个响应式数据,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

1
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {})

2、当监听一个 reactive 定义的 proxy 响应式对象时,侦听器会强制启用深层模式,但回调函数收到的新值旧值参数将是同一个对象

1
2
3
4
5
6
7
8
9
10
11
let person = reactive({
firstName: '张',
lastName: '三'
})
watch(person, (newValue, oldValue)=>{
console.log(newValue,oldValue);
// Proxy(Object){firstName: '张一', lastName: '三'}
//Proxy(Object){firstName: '张一', lastName: '三'}
console.log(newValue===oldValue); // true
})

3、监听 proxy 对象中某一个基本数据类型的属性,新值旧值可以正常获取,但监听对象需写成函数返回值形式

1
2
3
4
5
6
let person = reactive({
a: { b: 1, c: 2 }
})
watch(()=>person.a.b, (newValue, oldValue)=>{
console.log(newValue, oldValue);
})

4、监听 proxy 对象中某一个对象类型的属性,新值旧值是同一个对象

1
2
3
4
5
6
let person = reactive({
a: { b: 1, c: 2 }
})
watch(person.a, (newValue, oldValue)=>{
console.log(newValue, oldValue);
})

5、若监视一个使用 ref() 定义的对象类型响应式数据,则需要 deep: true 开启深度监视,或去监视其 .value(此时value是一个proxy对象,会自动开启深度监视)

1
2
3
4
5
6
7
8
9
10
11
12
13
let person = ref({
a: { b: 1, c: 2 }
})
watch(person, (newValue, oldValue)=>{
console.log(newValue, oldValue);
},{
deep: true, // 直接监视ref对象需要手动开启深度监视
})
// 也可以去监视其.value
watch(person.value, (newValue, oldValue)=>{
console.log(newValue, oldValue);
})

watchEffect函数

watchEffect() 立即运行一个回调函数,同时响应式地追踪其依赖,并在依赖更改重新执行回调

返回值是一个用来停止监听的的函数

1
2
3
const count = ref(0)
watchEffect(() => console.log(count.value)) // 立即执行回调,输出 0
count.value++ // 重新执行回调,输出 1

和计算属性有些像,计算属性重在最后的返回值,而watchEffect重在逻辑过程,而返回值确定

Vue3生命周期

Vue3 生命周期钩子名称改为了 on 开头,但作用和 Vue2 中的差不多,但也添加了一些新钩子,有需要在文档中查看用法

Vue3 生命周期图:

自定义hook

hook 本质是一个函数。
自定义hook,就是将 setup 中使用过的组合式API进行封装,写在一个单独的 JS 文件中并暴露出去,实现代码复用,命名通常以 use 开头,集中放在 hooks 文件夹中

作用:在 Vue3 中通过组合式函数实现 JS 代码复用,而避免使用 mixin 混入

组合式函数:是一个利用 Vue 的组合式 API 来封装和复用有状态相关逻辑函数

案例:写一个实时返回鼠标坐标的 hook,让任何组件引入即可使用

/hooks/useMouse.js
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
import { reactive, onMounted, onBeforeUnmount } from "vue"
// 暴露出该hook函数
export default function (){
// 状态
let coordinate = reactive({
x: 0,
y: 0,
})
// 修改状态
function monitorMouse(event){
coordinate.x = event.pageX;
coordinate.y = event.pageY;
}
// 组件挂载完开始监听
onMounted(()=>{
window.addEventListener('mousemove', monitorMouse)
})
// 组件卸载前结束监听
onBeforeUnmount(()=>{
window.removeEventListener('mousemove', monitorMouse)
})
// 将状态返回出去给组件使用
return coordinate
}

组件中展示鼠标坐标,非常方便,引入hook并执行,获取状态,然后使用状态

1
2
3
4
5
6
7
8
9
10
11
<template>
<!-- 使用hook函数返回的状态 -->
<span>X:{{ coordinate.x }} Y:{{ coordinate.y }}</span>
</template>
<script setup>
// 引入hook
import useMouse from '@/hooks/useMouse';
// 执行hook函数获取数据
let coordinate = useMouse();
</script>

toRef与toRefs

toRef() 可以将值、refs 或 getters 规范化为 refs
也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然

1
let refObj = toRef(proxyObj, 'propName');

为什么要用 toRef()
当直接解构 proxy 响应式对象时,对于属性值是基本数据类型的属性,传的是值,而非引用地址,解构后的变量会失去响应式的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h3>{{ a }}</h3>
<h3>{{ b }}</h3>
</template>
<script setup>
let obj = reactive({
a: {
b: 1,
c: 2,
}
})
// 解构obj
let a = obj.a; // 传的地址,响应式仍在
console.log(a); // Proxy(Object) {b: 1, c: 2}
let b = obj.a.b; // 传的数值,响应式丢失
console.log(b); // 1
</script>

使用 toRef() 可以不用重新将解构的值通过 ref() 创建一个独立ref 对象,而是直接用源对象中的属性,通过引用关系,创建一个对应的 ref 对象,与其源属性保持同步

1
2
3
4
5
6
7
let obj = reactive({
a: { b: 1, c: 2 }
})
// 创建一个对应的 ref
let b = toRef(person.a, 'b');
console.log(b);
// ObjectRefImpl {_object: Proxy(Object), _key: 'b', _defaultValue: undefined, __v_isRef: true}

toRefs() 功能与 toRef() 差不多,但可以批量处理某个 proxy 对象中的所有属性(浅层),返回一个对象,对象中存着源对象属性对应的 ref 对象

1
let refArr = toRefs(proxyObj);

总结:toRef()创健一个 ref 对像,其 value 值指向另一个对象中的某个属性

其它组合式API

介绍较不常用的组合式API

响应式数据的判断

1、isRef() 检查某个值是否为 ref 对象
2、unref() 如果参数是 ref,则返回其 value 值,否则返回参数本身
2、isProxy() 检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理
3、isReactive() 检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理

浅层响应式

shallowReactive() reactive() 的浅层作用形式,只对浅层属性进行响应式处理

shallowRef() ref() 的浅层作用形式,只有浅层的 value 是响应式的

只读API

readonly()​ 深层的只读,接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理

shallowReadonly() 浅层的只读

toRaw、markRaw

toRaw() 根据一个 Vue 创建的 proxy 代理对象返回其原始对象的引用地址

markRaw() 将一个对象标记为不可被转为 proxy 代理。返回该对象本身。可以让某些数据追加到 proxy 对象上后没有响应式功能。

自定义ref

customRef() 创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象

track() 追踪数据变化,trigger() 重新解析模板

作用:用于防抖、延迟更新视图等需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { customRef } from 'vue'
function myRef(value){
return customRef((track, trigger)=>{
return {
get() {
track() // 追踪数据变化
return value
},
set(newValue) {
value = newValue
trigger() // 重新解析模板
}
}
})
}

依赖注入

provide() 祖先组件提供一个值,可以被后代组件注入。接受两个参数:第一个参数是要注入的 key(名字),第二个参数是要注入的值

inject() 注入一个由祖先组件或整个应用 (app.provide()) 提供的值。

通过这两个 API 可以实现祖先与后代组件的通信,非常方便

1
2
3
4
5
6
7
8
9
10
11
12
13
// 祖先组件
import { provide } from 'vue';
let person = reactive({
firstName: '张',
lastName: '三',
})
provide('person',person)

// 后代组件
import { inject } from 'vue';
let person = inject('person');
console.log(person); // 获取成功

注意:provide()inject() 必须在组件的 setup() 阶段同步调用

Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。

Fragment组件

在 Vue2 中,组件模板必须有一个根标签

在 Vue3 中,若模板中没有根标签,会将模板包含在一个 fragment 虚拟元素中,不会被实际渲染出来

Teleport组件

<teleport to=""> 可以将组件模板、插槽移动到指定的任意元素中(尾插),to 中写 css 选择器

一个简单的弹窗组件:

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
<template>
<div>
<button @click="showPopUp">点击显示弹窗</button>
<teleport to='body'>
<div class="popup-mask" v-show="popup.isShow">
<div class="popup">
<h3>弹窗内容</h3>
<button @click="showPopUp">关闭弹窗</button>
</div>
</div>
</teleport>
</div>
</template>

<script setup>
import { reactive } from 'vue';
let popup = reactive({
isShow: false,
})
function showPopUp() {
popup.isShow = !popup.isShow;
}
</script>

<style lang="less" scoped>
.popup-mask {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
}
.popup {
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 300px;
background: rgb(60, 60, 60);
text-align: center;
}
</style>

Suspense组件(实验)

作用:异步地动态加载子组件,父组件加载完就直接显示,不等待异步的子组件

将要异步加载的子组件使用 <Suspense> 包裹

<Suspense> 提供两个有名插槽,default 最终要加载的子组件,fallback 子组件还未加载出来时展示的模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<!-- default插槽 -->
<template v-slot:default>
<Child/>
</template>
<!-- fallback插槽 -->
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>

除了 <Suspense> 还可以使用 defineAsyncComponent() 异步引入组件

1
2
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

异步引入组件后:
(1) 该组件的 setup() 就可以是 async 函数,可以用 await,可以返回 promise 对象。
(2) <script setup> 中也可以使用顶层 await。结果代码会被编译成 async setup()

Vue3的其它变化

1、全局 API 的转移:Vue.xxx 调整到应用实例 app

2.x 全局 API(Vue 3.x 实例 API (app)
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除,因为不再有生产提示
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

2、Vue动画中过度类名的更改:

Vue2 写法
1
2
3
4
5
6
7
8
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3 写法
1
2
3
4
5
6
7
8
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}

3、keyCode 不再作为 v-on 的修饰符,同时也不再支持 config.keyCodes

4、移除 v-on.native 修饰符

5、移除过滤器 filter

6、父组件给子组件绑定自定义事件,子组件中需要接收才能使用,
(1) defineEmits() 接收父组件绑定的自定义事件
(2) 或者选项式中的 emits 配置项

THE END