抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

这里是对于学习vue3的一些新特性的学习笔记的总结,巩固与复习一下最近学习的知识点吧


一:认识Vue3

1.性能提升

  • 打包大小减少41%
  • 初次渲染快55%, 更新渲染快133%
  • 内存减少54%
  • 使用Proxy代替defineProperty实现数据响应式
  • 重写虚拟DOM的实现和Tree-Shaking

2.新增特性

  • Composition (组合) API
  • setup
  • ref 和 reactive
  • computed 和 watch
  • 新的生命周期函数
  • provide与inject
  • 新组件
  • Fragment - 文档碎片
  • Teleport - 瞬移组件的位置
  • Suspense - 异步加载组件的loading界面
  • 其它API更新
  • 全局API的修改
  • 将原来的全局API转移到应用对象
  • 模板语法变化

3.创建vue3项目

3.1 使用 vue-cli 创建

1
2
3
4
5
6
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project

3.2 使用 vite 创建

  • 文档: https://v3.cn.vuejs.org/guide/installation.html
  • vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,
  • 它做到了本地快速开发启动, 在生产环境下基于 Rollup 打包。
  • 快速的冷启动,不需要等待打包操作;
  • 即时的热模块更新,替换性能和模块数量的解耦让更新飞起;
  • 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。
1
2
3
4
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev

二:核心Composition API

1. setup

  • 一个组件选项,在组件被创建之前,props 被解析之后执行。
  • 它是组合式 API的入口。
  • Vue3.0中的一个新的配置项,值为一个函数。组件中所用到的:数据、方法等等,均要配置再setup中。

setup带来的改变

1.解决了vue2的data和methods方法相距太远,无法组件之间复用

2.提供了script标签引入共同业务逻辑的代码块,顺序执行

3.script变成setup函数,默认暴露给模版

4.组件直接挂载,无需注册

5.自定义的指令也可以在模版中自动获得

6.this不再是这个活跃实例的引用

7.带来的大量全新api,比如defineProps,defineEmits,withDefault,toRef,toRefs

1.1 vue3.0 写法

  • setup是一个新的组件选项,作为组件中使用组合API的起点。
  • 从组件生命周期来看,它的执行在组件实例创建之前vue2.x的beforeCreate执行。
  • 这就意味着在setup函数中this 还不是组件实例,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
27
28
<template>
<div>
<h1>初始setup</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="sayHello">点击</button>
</div>
</template>

<script>

export default {
name: 'App',
setup(){
let name = '张三'
let age = 19
function sayHello() {
alert('初始setup函数')
}
return {
name,
age,
sayHello
}
}
}
</script>

1.2 单文件组件<script setup></script >

  • 每个 *.vue 文件最多可以包含一个 <script setup>。(不包括一般的 <script>)

  • 这个脚本块将被预处理为组件的 setup() 函数,这意味着它将为每一个组件实例都执行。<script setup> 中的顶层绑定都将自动暴露给模板。

  • 在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。

  • 能够使用纯 TypeScript 声明 props 和自定义事件。这个我下面是有说明的

  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。

  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

基本语法

顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用

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
<template>
<div>
<input type="text" v-model="keyWord">
<div>{{keyWord}}</div>
</div>
</template>

<script setup>
//实现自定义myRef函数
import { ref, customRef } from 'vue'
//使用vue3提供的ref API
//现在使用自定义API来实现div延时显示input输入的内容
// let keyWord = ref('hello')
//定义myRef
function myRef(value, delayTime) {
//封装的自定义函数需要返回值 返回customRef
// 两个参数 1.track:追踪 , 在get函数中需要调用 track()来追踪数据 2.trigger:触发,在set中更改数据后 调用trigger告诉vue3重新渲染模板,这时候再到get函数中渲染新值
return customRef((track, trigger) => {
let timer
//这里的返回值是语法需要返回的
return {
get() {
console.log(`有人读取了数据,返回${value}`)
track()
return value
},
set(newValue) {
//防抖
clearTimeout(timer)
console.log(`有人更改了数据,新数据是${newValue}`)
value = newValue
timer = setTimeout(() => {
trigger()
}, delayTime)
},
}
})
}
let keyWord = myRef('hello', 1000)
</script>

1.3 setup执行的时机

在beforeCreate之前执行一次,this是undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <template>
<h1>一个人的信息</h1>
<h3>姓名:{{ person.name }}</h3>
</template>

<script>
import { defineComponent, reactive } from 'vue'

export default defineComponent({
beforeCreate() {
console.log('-----beforeCreate-----')
},
setup() {
console.log('-----setup-----', this)
let person = reactive({
name: '张三',
})
return {
person,
}
},
})
</script>

1.4 setup的参数

参数

参数1. props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性

参数2. context:上下文对象:
        attrs:值为对象,包含:组件外部传递过来,但没有在 props 配置中声明的属性,相当于Vue2.x的this.$attrs。
        slots:收到的插槽内容,相当于Vue2.x的this.$slots。
        emit:分发自定义事件的函数,相当于Vue2.x的 this.$emit。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)

// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)

// 触发事件 (方法,等同于 $emit)
console.log(context.emit)

// 暴露公共 property (函数)
console.log(context.expose)
}
}

2. ref

  • 作用: 定义一个数据的响应式
  • 语法: const xxx = ref(initValue):
    • 创建一个包含响应式数据的引用(reference)对象
    • js中操作数据: xxx.value
    • 模板中操作数据: 不需要.value
  • 一般用来定义一个基本类型的响应式数据

2.1 ref API

实现响应式数据

  • 接收的数据可以是:基本类型、也可以是对象类型
  • 基本类型的数据:响应式依然靠的是Object.defineProperty()的get和set完成的
  • 对象类型的数据: 内部”求助“了Vue3.0中的一个新的函数——reactive函数
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>
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeInfo">点击更改信息</button>
</div>
</template>

<script>
//响应式ref函数
import { ref } from "vue";
export default {

name: 'App',
setup(){
let name = ref('张三')
let age = ref(20)
function changeInfo() {
// console.log(name)
name.value = '李四'
age.value = 25
}
return {
name,
age,
changeInfo,
}
}
}
</script>

2.2 ref属性操作单个Dom

操作单个DOM或者组件的流程

  1. 定义一个响应式变量
  2. 把变量返回给模板使用
  3. 模板中绑定上述返回的数据
  4. 可以通过info变量操作DOM或者组件实例
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>
<h2>ref操作Dom和组件</h2>
<hr>
<!-- 3. 模板绑定返回的数据 -->
<div ref="info">Hello</div>
<button @click="handleClick">点击</button>
</template>

<script>
import {ref} from 'vue'
export default {
setup(){
//vue3通过ref操作dom
//1. 定义一个响应式变量
const info = ref(null)

const handleClick = () => {
//4. 此时可以通过info变量操作dom
console.log(info.value)
}
//2.将变量返回给模板使用
return {
info,
handleClick
}
}
}
</script>

image-20230424210701507

2.3 ref属性获取v-for遍历的DOM或者组件

ref批量操作元素的流程

  1. 定义一个函数
  2. 把该函数绑定到ref上(必须动态绑定
  3. 在函数中可以通过参数得到单个元素,这个元素一般可以放到数组中
  4. 通过上述数组即可操作批量的元素
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
<template>
<h2>ref操作Dom和组件</h2>
<hr>
<!-- 3. 模板绑定返回的数据 -->
<ul>
<li :ref="setFruits" v-for="item in fruits" :key="item.id">{{item.name}}</li>
</ul>
<button @click="handleClick">点击</button>
</template>

<script>
import { ref } from 'vue'
export default {
setup() {
//1. 定义一个响应式变量
const fruits = ref([
{
id: 1,
name: 'apple',
},
{
id: 2,
name: 'orange',
},
{
id: 3,
name: 'pear',
},
])
//定义操作dom函数
const arr = []
const setFruits = (el) => {
//el表示单个dom元素
arr.push(el)
}

const handleClick = () => {
//4.此时可以通过arr数组操作dom
console.log(arr)
}
//2.将变量返回给模板使用
return {
fruits,
handleClick,
setFruits
}
},
}
</script>

image-20230424211717533

3. reactive

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

const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象

响应式转换是“深层的”:会影响对象内部所有嵌套的属性

内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

3.1 reactive和ref的区别

从定义数据角度对比
    ref用来定义:基本类型数据。
    reactive用来定义:对象(或数组)类型数据。
    备注:ref也可以用来定义对象(或数组)类型数据。它内部会自动通过reactive转为代理对象。

ref也能用于定义引用数据类型,如下述代码,不过做响应式时对数据进行修改需要.value进行更改,否则响应式效果将失效。ref定义对象时,底层会通过reactive转换成具有深层次的响应式对象,所以ref本质上是reactive的再封装

从使用角度对比
    ref定义的数据:操作数据需要.value,读取数据时模版中直接读取不需要.value。
    reactive定义的数据:操作数据与读取数据均不需要.value。

从原理角度对比
    ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
    reactive通过使用Proxy来实现响应式(数据劫持),并通过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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<h3>ref和reactive的区别</h3>
<hr>
<div>student: {{student.name}} -- {{student.age}} -- {{student.school}}</div>
<button @click="changeStudent">点击改变信息</button>
<hr>
<div>student: {{student2.name}} -- {{student2.age}} -- {{student2.school}}</div>
<button @click="changeStudent2">点击改变信息2</button>

</template>

<script>
import { ref, reactive } from "vue";
export default {
setup(){
const student = ref({
name: '小李',
age: 19,
school: 'xupt'
})
const student2 = reactive({
name: 'Bob',
age: 20,
school: 'xupt'
})
const changeStudent = () => {
student.value.name = 'xxx'
student.value.age = 100
}
const changeStudent2 = () => {
student2.name = '张三',
student2.age = 30
}
return {
student,
student2,
changeStudent,
changeStudent2
}
}
}
</script>

3.2 reactive无法对基本数据类型做出响应式

reactive只能用于定义引用数据类型的原因在于内部是通过ES6的Proxy实现响应式的,而Proxy不适用于基本数据类型

返回对象的响应式副本,只能代理对象,不能代理普通值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>{{name}}</div>
</template>

<script>
import { ref, reactive } from "vue";
export default {
setup(){
const name = reactive('张三')
return {
name
}
}
}
</script>

image-20230424215539210

3.3 reactive响应式原理-Proxy

  • 通过 Proxy(代理) 拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
  • 通过 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
29
30
31
32
//数据源
let student = {
name: '张三',
age: 18,
shcool: 'xupt'
}

let proxy = new Proxy(data, {
// 拦截读取属性值
get (target, prop) { // target:原对象; prop:读取的属性
console.log(`监听到了属性值${prop}被读取`)
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) { // target:原对象; prop:要修改/增加的属性 value:值
//target[prop] = value
console.log(`监听到了属性值${prop}发生变化`)
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
//return delete target[prop]
console.log(`监听到了属性值${prop}被删除`)
return Reflect.deleteProperty(target, prop)
}
})

// proxy 就是源数据 data 的响应式代理对象
proxy.name // 触发监听
proxy.name = '李四' // 触发监听
delete proxy.name // 触发监听

image-20230424221141126

4. 计算属性Computed

computedwatch都是vue框架中的用于监听数据变化的属性,都能在数据改变时,针对改变的相关数据做成相应的变化。

4.1 computed的使用

作为组合式API之一,大体用法与vue2用法一致,只不过不再是作为配置函数使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>{{sumName}}</div>
</template>

<script>
import { computed, reactive} from "vue";
export default {
setup(){
const allName = reactive({
userName1: '张三',
userName2: '李四'
})

const sumName = computed(() => {
return allName.userName1 + '---' + allName.userName2
})
return {
sumName
}
}
}
</script>

4.2 计算属性的缓存特点

上方代码是利用computed计算属性完成将两者姓名拼串在一起,当然这种利用方法也能完成,那为什么不用方法去代替计算属性去解决所有类似问题呢?

原因是计算属性是基于缓存来实现的,无论你后端调用十次还是几百次,只要计算属性依赖的相关值没有发生变化,那计算属性就可以通过缓存读取。

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
<template>
<!-- 利用计算属性完成拼串 -->
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<div>{{sumName}}</div>
<!-- 利用方法完成拼串 -->
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
<div>{{sumAllName()}}</div>
</template>

<script>
import { computed, reactive} from "vue";
export default {
setup(){
const allName = reactive({
userName1: '张三',
userName2: '李四'
})

const sumName = computed(() => {
console.log('计算属性调用')
return allName.userName1 + '---' + allName.userName2
})

const sumAllName = () => {
console.log('方法调用')
return allName.userName1 + '111' + allName.userName2

}
return {
sumName,
sumAllName
}
}
}
</script>

image-20230425175008898

上述代码,利用计算属性和方法完成相同的拼串功能,并在模板中各展示了数边,并让其调用一次就在控制台打印输出一次是计算属性调用还是方法调用,那么结果便是,计算属性调用只在控制台输出了一次,而方法调用则是在调几次打印几次。这就是计算属性的缓存,只要其依赖的值不发生相关的变化,那么无论你计算属性调用了好多次,但最终就只是执行一次。

5. 监视属性watch

当被监视的属性变化时,回调函数自动调用,进行相关操作,所谓监视属性,就是监测指定的属性是否发生变化,如果发生了,则进行一系列操作,如果没发生变化,则监视属性也不会被触发。

Vue3中的watch属性和Vue2的基本一致,作用一致。Vue2中watch属性时作为一个配置项来使用,在Vue3中watch作为函数来使用。 虽然在Vue3中可以使用Vue2的语法,但是watch属性在Vue3中不推荐写成配置项的方式,因为在Vue3中提倡的就是组合式API的思想。

第一个参数是侦听器的源。这个来源可以是以下几种:

一个函数, 一个返回值
一个ref
一个响应式对象(reactive定义的)
…或是由以上类型的值组成的数组
第二个参数是一个回调函数, 这个回调函数可接收三个参数: 新值(newValue)、旧值(oldValue)、以及一个副作用清理的回调函数

第三个参数是一个可选的参数是一个对象构成, 如:deep(深度监视)、immediate(创建时立即监听一次)等等…

5.1 watch监视ref定义的一个响应式数据

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
27
28
29
30
31
32
33
<template>
<div>{{userAge}}</div>
<button @click="userAge++">点击年龄++</button>
<hr>
<div>{{userName}}</div>
<button @click="userName = '李四'">点击更改姓名</button>
</template>

<script>
import { ref, reactive, watch } from 'vue'
export default {
setup() {
const userAge = ref(18)
const userName = ref('张三')
//1.监视ref定义的一个响应式数据
watch(userAge, (newValue, oldValue) => {
console.log(
'修改了年龄 ' + 'newValue: ' + newValue + ' oldValue: ' + oldValue
)
})
watch(userName, (newValue, oldValue) => {
console.log(
'修改了姓名 ' + 'newValue: ' + newValue + ' oldValue:' + oldValue
)
})

return {
userAge,
userName
}
},
}
</script>

image-20230425180950027

5.2 watch监视ref定义的多个响应式数据

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
27
28
<template>
<div>{{userAge}}</div>
<button @click="userAge++">点击年龄++</button>
<hr>
<div>{{userName}}</div>
<button @click="userName = '李四'">点击更改姓名</button>
</template>

<script>
import { ref, reactive, watch } from 'vue'
export default {
setup() {
const userAge = ref(18)
const userName = ref('张三')
//2.监视ref定义的多个响应式数据
watch([userAge, userName], (newValue, oldValue) => {
console.log(
'数据被修改了 ' + 'newValue: ' + newValue + ' oldValue: ' + oldValue
)
})

return {
userAge,
userName
}
},
}
</script>

image-20230425181412374

5.3 watch监视reactive定义的一个响应式数据的全部属性

这里会出现问题

1. 可以捕获newValue新值,但无法捕获oldValue旧的值

2. 强制开启深度监视,即使手动将深度监视关闭也无效

打印的结果发现oldValue和newValue一致

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
<template>
<div>{{userInfo.userAge}}</div>
<button @click="userInfo.userAge++">点击年龄++</button>
<hr>
<div>{{userInfo.userName}}</div>
<button @click="userInfo.userName = '李四'">点击更改姓名</button>
<hr>
<!-- 深度监视是否关闭 -->
<div>{{userInfo.userSchool.school}}</div>
<button @click="userInfo.userSchool.school = '西柚'">点击更改学校,看深度监视是否关闭</button>
</template>

<script>
import { ref, reactive, watch } from 'vue'
export default {
setup() {
let userInfo = reactive({
userAge: 19,
userName: '张三',
userSchool: {
area: '陕西',
school: 'xupt',
},
})
//3.监视reactive定义的一个响应式数据的全部属性
watch(
userInfo,
(newValue, oldValue) => {
console.log('数据被修改了 ', newValue, oldValue)
},
{ deep: false }
)

return {
userInfo,
}
},
}
</script>

image-20230425182926424

5.4 watch监视reactive定义的一个相应式数据的某个数据

这里借助5.3的代码监视userInfo的年龄属性

不能使用userInfo.userAge对其进行监视会报warning

1
2
3
4
5
6
7
8
//4.监视reactive定义的一个响应式数据的某个属性
watch(
userInfo.userAge,
(newValue, oldValue) => {
console.log('数据被修改了 ', newValue, oldValue)
},
{ deep: false }
)

image-20230425183449131

正确写法

将被监视的数据作为函数的返回值使用

1
2
3
4
5
6
7
8
//4.监视reactive定义的一个响应式数据的某个属性
watch(
() => {return userInfo.userAge},
(newValue, oldValue) => {
console.log('数据被修改了 ', newValue, oldValue)
},
{ deep: false }
)

image-20230425183716034

5.5 watch监视reactive定义的一个相应式数据的多个数据

用一个数组包裹, 数组里面写多个函数来返回所响应的数据

1
2
3
4
5
6
7
8
//5.监视reactive定义的一个响应式数据的多个属性
watch(
[() => userInfo.userAge, () => userInfo.userName],
(newValue, oldValue) => {
console.log('数据被修改了 ', newValue, oldValue)
},
{ deep: false }
)

image-20230425184234529

6. watchEffect函数

传入的一个函数,当依赖项变化的时候,重新执行改函数。

6.1 watchEffect函数的特性

与watch相似都可以监听一个数据源。
但是watchEffect会在初始化的时候调用一次,与watch的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
<template>
<div>{{userInfo.userAge}}</div>
<button @click="userInfo.userAge++">点击年龄++</button>
<hr>
</template>

<script>
import { ref, reactive, watch, watchEffect } from 'vue'
export default {
setup() {
let userInfo = reactive({
userAge: 19,
userName: '张三',
userSchool: {
area: '陕西',
school: 'xupt',
},
})
//5.监视reactive定义的一个响应式数据的多个属性
watchEffect(() => {
console.log(`${userInfo.userAge}值修改了`)
})

return {
userInfo,
}
},
}
</script>

image-20230425185130152

当我为触发点击事件时候,watchEffect函数已经被调用了,初始化会执行一次

6.2 watch与watchEffect的区别

watch的套路是:既要指明监视的属性,也要指明监视的回调

watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性

watchEffect有点像computed:

但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

7. Vue3生命周期

vue3声明周期钩子

setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method

onBeforeMount() : 组件挂载到节点上之前执行的函数。

onMounted() : 组件挂载完成后执行的函数。

onBeforeUpdate(): 组件更新之前执行的函数。

onUpdated(): 组件更新完成之后执行的函数。

onBeforeUnmount(): 组件卸载之前执行的函数。

onUnmounted(): 组件卸载完成后执行的函数

onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。

onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。

onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数

8. 自定义hook函数

8.1 什么是hook?

vue3 借鉴 react hooks 开发出了 Composition API ,所以也就意味着 Composition API 也能进行自定义封装 hooks。

vue3 中的 hooks 就是函数的一种写法,就是将文件的一些单独功能的js代码进行抽离出来,放到单独的js文件中,或者说是一些可以复用的公共方法/功能。其实 hooks 和 vue2 中的 mixin 有点类似,但是相对 mixins 而言, hooks 更清楚复用功能代码的来源, 更清晰易懂。

在学习es6我们学到了class类与对象进行模块化管理,在vue2中我们可以用mixin混合进行模块化管理,那么在Vue3中我们可以用到hook函数

8.2 为什么使用hook函数?

当我们写项目的时候,或多或少都会遇到多个地方用到同一模块的功能,那么我们是不会选择复制粘贴过去,后续当对应功能数据接口发生改变,我们是需要花费很大精力去一一修改,这就需要将其封装抽离出来作为模块暴露出来,哪需要就引入使用即可。

8.3 hook函数的使用

  1. 创建hooks文件夹,将需要抽离出来的功能模块全部放入hooks文件中统一管理

image-20230425191637313

2.创建对应的js文件,并将其内容暴露。在这个js中,我们也将需要用到的组合式api引入,例如reactive, 生命周期钩子等等。封装抽离,这就更体现了了组合式api

下方代码实现的是点击屏幕将鼠标的当前坐标渲染到模板上

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";
export default function usePoint(){
const point = reactive({
x: 0,
y: 0,
})

function savePoint(event) {
// console.log(event.pageX)
point.x = event.pageX
point.y = event.pageY
}

//挂载时候
onMounted(() => {
window.addEventListener('click', savePoint)
})

//卸载时候
onBeforeUnmount(() => {
window.removeEventListener('click', savePoint)
})

return point
}
  1. 引入hook文件,接受暴露的数据
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
<template>
<div>
<h1>年龄:{{number}}</h1>
<button @click="number++">自增</button>
<hr>
<!-- 获取鼠标当前的坐标 -->
<button @click="isShow = !isShow">隐藏/显示坐标</button>
<h1 v-if="isShow">点击显示鼠标当前的坐标: x:{{point.x}}, y:{{point.y}}</h1>

</div>
</template>

<script>
import { ref, watch} from 'vue'
//引入usePoint文件
import usePoint from './hook/usePoint'
export default {
name: 'App',
setup() {
let number = ref(0)
//接受对外暴露的数据
let isShow = ref(false)
const point = usePoint()
return {
number,
point,
isShow,
usePoint
}
},
}
</script>

三:其余Composition API

1. customRef

customRef 是 Vue 3 中的一个新特性,它允许你创建自定义的响应式引用。

在 Vue 3 中,我们可以使用 ref 函数来创建一个响应式数据。但是有些情况下,我们可能需要创建一些更加复杂的响应式数据,这时候就可以使用 customRef 来实现。

customRef 函数接受一个包含 getset 方法的对象作为参数,并返回一个具有 value 属性的对象。当 customRef 创建的对象中的 value 发生变化时,会触发更新视图的操作。

使用 customRef 可以实现一些高级的响应式逻辑,比如:

  • 缓存计算结果,只在依赖项变化时重新计算
  • 实现惰性更新,只在需要时才更新值
  • 自定义 setter 行为,比如对值进行校验或转换

需要注意的是,由于 customRef 提供了更高度的自定义能力,如果不小心使用不当,可能会导致性能问题。因此,在使用 customRef 时需要谨慎考虑其是否真正必要。

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
<template>
<div>
<input type="text" v-model="keyWord">
<div>{{keyWord}}</div>
</div>
</template>

<script setup>
//实现自定义myRef函数
import { ref, customRef } from 'vue'
//使用vue3提供的ref API
//现在使用自定义API来实现div延时显示input输入的内容
// let keyWord = ref('hello')
//定义myRef
function myRef(value, delayTime) {
//封装的自定义函数需要返回值 返回customRef
// 两个参数 1.track:追踪 , 在get函数中需要调用 track()来追踪数据 2.trigger:触发,在set中更改数据后 调用trigger告诉vue3重新渲染模板,这时候再到get函数中渲染新值
return customRef((track, trigger) => {
let timer
//这里的返回值是语法需要返回的
return {
get() {
console.log(`有人读取了数据,返回${value}`)
track()
return value
},
set(newValue) {
//防抖
clearTimeout(timer)
console.log(`有人更改了数据,新数据是${newValue}`)
value = newValue
timer = setTimeout(() => {
trigger()
}, delayTime)
},
}
})
}
let keyWord = myRef('hello', 1000)
</script>

上述代码,是实现自定义在文本框中输入数据,下方文本延迟1秒钟显示。

image-20230425193516165

2. provide和inject

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

image-20230425193708989

祖先组件

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
<template>
<div class="App">
<h3>祖组件 {{age}} -- {{school}}</h3>
<Child/>
</div>
</template>

<script>
import { provide, reactive, toRefs } from "vue";
import Child from './component/Child.vue'
export default {
name: 'App',
components: { Child },
setup(){
let student = reactive({
age: 18,
school: 'xupt'
})
//提供数据
provide('student', student)
return {
...toRefs(student)
}
}
}
</script>

<style>
.App {
background-color: gray;
padding: 10px;
}
</style>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="child">
<h3>父组件</h3>
<GrandSon/>
</div>
</template>

<script>
import GrandSon from '../component/GrandSon.vue'
export default {
name: 'Child',
components: { GrandSon },
}
</script>

<style>
.child {
background-color: aqua;
padding: 10px;
}
</style>

孙组件

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 class="GrandSon">
<h3>孙组件 {{age}} -- {{school}}</h3>
</div>
</template>

<script>
import { inject, toRefs } from "vue";
export default {
name: 'GrandSon',
setup(){
//注射数据,祖孙传递数据
//父子组件传递用props传递数据
let info = inject('student')
return {
...toRefs(info)
}
}
}
</script>

<style>
.GrandSon {
background-color: red;
padding: 10px;
}
</style>

image-20230425193914512


完结!

这是vue3知识点的整理,之后等完成项目实际中遇到某些问题,学习到新的vue3知识再进行新的整理吧!

评论