这里是对于学习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 --version
vue create my-project
|
3.2 使用 vite 创建
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>
import { ref, customRef } from 'vue'
function myRef(value, delayTime) { 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) { console.log(context.attrs)
console.log(context.slots)
console.log(context.emit)
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>
import { ref } from "vue"; export default {
name: 'App', setup(){ let name = ref('张三') let age = ref(20) function changeInfo() { name.value = '李四' age.value = 25 } return { name, age, changeInfo, } } } </script>
|
2.2 ref属性操作单个Dom
操作单个DOM或者组件的流程
- 定义一个响应式变量
- 把变量返回给模板使用
- 模板中绑定上述返回的数据
- 可以通过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(){ const info = ref(null)
const handleClick = () => { console.log(info.value) } return { info, handleClick } } } </script>
|
2.3 ref属性获取v-for遍历的DOM或者组件
ref批量操作元素的流程
- 定义一个函数
- 把该函数绑定到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 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() { const fruits = ref([ { id: 1, name: 'apple', }, { id: 2, name: 'orange', }, { id: 3, name: 'pear', }, ]) const arr = [] const setFruits = (el) => { arr.push(el) }
const handleClick = () => { console.log(arr) } return { fruits, handleClick, setFruits } }, } </script>
|
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>
|
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) { console.log(`监听到了属性值${prop}被读取`) return Reflect.get(target, prop) }, set (target, prop, value) { console.log(`监听到了属性值${prop}发生变化`) return Reflect.set(target, prop, value) }, deleteProperty (target, prop) { console.log(`监听到了属性值${prop}被删除`) return Reflect.deleteProperty(target, prop) } })
proxy.name proxy.name = '李四' delete proxy.name
|
4. 计算属性Computed
computed
和watch
都是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>
|
上述代码,利用计算属性和方法完成相同的拼串功能,并在模板中各展示了数边,并让其调用一次就在控制台打印输出一次是计算属性调用还是方法调用,那么结果便是,计算属性调用只在控制台输出了一次,而方法调用则是在调几次打印几次。这就是计算属性的缓存,只要其依赖的值不发生相关的变化,那么无论你计算属性调用了好多次,但最终就只是执行一次。
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('张三') watch(userAge, (newValue, oldValue) => { console.log( '修改了年龄 ' + 'newValue: ' + newValue + ' oldValue: ' + oldValue ) }) watch(userName, (newValue, oldValue) => { console.log( '修改了姓名 ' + 'newValue: ' + newValue + ' oldValue:' + oldValue ) })
return { userAge, userName } }, } </script>
|
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('张三') watch([userAge, userName], (newValue, oldValue) => { console.log( '数据被修改了 ' + 'newValue: ' + newValue + ' oldValue: ' + oldValue ) })
return { userAge, userName } }, } </script>
|
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', }, }) watch( userInfo, (newValue, oldValue) => { console.log('数据被修改了 ', newValue, oldValue) }, { deep: false } )
return { userInfo, } }, } </script>
|
5.4 watch监视reactive定义的一个相应式数据的某个数据
这里借助5.3的代码监视userInfo的年龄属性
不能使用userInfo.userAge对其进行监视
会报warning
1 2 3 4 5 6 7 8
| watch( userInfo.userAge, (newValue, oldValue) => { console.log('数据被修改了 ', newValue, oldValue) }, { deep: false } )
|
正确写法
将被监视的数据作为函数的返回值使用
1 2 3 4 5 6 7 8
| watch( () => {return userInfo.userAge}, (newValue, oldValue) => { console.log('数据被修改了 ', newValue, oldValue) }, { deep: false } )
|
5.5 watch监视reactive定义的一个相应式数据的多个数据
用一个数组包裹, 数组里面写多个函数来返回所响应的数据
1 2 3 4 5 6 7 8
| watch( [() => userInfo.userAge, () => userInfo.userName], (newValue, oldValue) => { console.log('数据被修改了 ', newValue, oldValue) }, { deep: false } )
|
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', }, }) watchEffect(() => { console.log(`${userInfo.userAge}值修改了`) })
return { userInfo, } }, } </script>
|
当我为触发点击事件时候,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函数的使用
- 创建hooks文件夹,将需要抽离出来的功能模块全部放入hooks文件中统一管理
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) { point.x = event.pageX point.y = event.pageY }
onMounted(() => { window.addEventListener('click', savePoint) })
onBeforeUnmount(() => { window.removeEventListener('click', savePoint) })
return point }
|
- 引入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'
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
函数接受一个包含 get
和 set
方法的对象作为参数,并返回一个具有 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>
import { ref, customRef } from 'vue'
function myRef(value, delayTime) { 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秒钟显示。
2. provide和inject
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
祖先组件
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(){ let info = inject('student') return { ...toRefs(info) } } } </script>
<style> .GrandSon { background-color: red; padding: 10px; } </style>
|
完结!
这是vue3知识点的整理,之后等完成项目实际中遇到某些问题,学习到新的vue3知识再进行新的整理吧!