84 其他Composition API
1 shallowReavtive和shallowRef
shallow中文译为浅层次的,
引入shallowReavtive和shallowRef
想要使用shallowReavtive和shallowRef,也是需要先引入的
import {shallowReactive,shallowRef} from "vue";
shallowReavtive和reavtive
shallowReavtive定义对象类型数据,只考虑第一层响应式,里面的不考虑
如果使用shallowReavtive定义对象,由于shallowReavtive是浅层次的,只能处理第一层属性,嵌套对象属性是不处理的
一级属性name和age都能修改,但是嵌套属性sal是不能修改的
所以shallowReavtive和reavtive区别就是:shallowReavtive只能处理浅层气的数据,但是reavite可以处理多层次的数据
shallowRef和ref
如果定义普通数据类型,nameshallowRef和ref是没有区别的
但是如果定义的是对象类型,那么shallowRef就无效了
下面把ref换成shallowRef
发现没用了
由此可见,ref可以处理普通类型数据和对象类型数据,但是shallowRef只能处理普通类型的数据。
总结
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
2 readonly 与 shallowReadonly
readonly估计大家都知道,在开发中遇到这个词汇比较多,译为只读,shallowReadonly译为浅层次只读
引入read only 与 shallowReadonly
想要实现必先引入:
import {readonly,shallowReadonly} from "vue";
readonly限制reavtive类型
如果响应式对象里面的属性不想被修改,想要进行保护,那么这个时候就可以使用readonly了:
这个时候再修改,就不会修改成功,并且控制台还会有警告
除了readonly,还有一个类似的属性叫shallowReadonly
shallowReadonly限制reavtive类型
shallowReadonly只考虑第一层数据,不会考虑多层数据
这样只能修改多层次的sal属性,单层次的name和age还是无法修改的
上面说的例子都是reavtive修饰的数据,接下来换成ref修饰的数据,是否还能剩下呢
readonly限制ref类型
把sum使用readonly修饰
也是只能只读的
总结
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
3 toRaw 与 markRaw
raw中文译为原始的意思,那么toRaw就是把xx变成原始,markRaw就是把xx标记为原始
下面简单介绍下这两个API的作用
引入toRaw 与 markRaw
先要使用就得先引入:
import {toRaw,markRaw} from "vue";
toRaw处理reactive属性
可以使用toRaw使得reactive响应式数据变成原始数据:
发现person对象已经变成了原始数据了
toRaw处理ref属性
既然toRwa可以处理reactive属性,那么是否可以处理ref属性呢,我们也可以验证下。
也是可以的,但是不同的版本处理不同,可能有的低点的vue3版本在这里就不是一个引用对象,而是一个undefined
markRaw禁止属性改变
标记一个对象,使其永远不会再成为响应式对象。
markRaw可以让对象变成最原始的,它不同于readonly,readonly是改都改不了,markRaw可以让数据改变,但是vue不再监视
现在是可以正常修改属性的
使用markRaw修改属性,再次修改已经修改不了了
总结
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
4 customRef
custom译为自定义,创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
虽然是自定义,但是也不是完全都是自己写的,可以借助customRef。
下面通过案例演示一下customRef的使用,这个案例也是vue官网提供的防抖效果案例
自定义ref函数
ref其实也是一个函数,我们可以自己创建
这样一个最基本的自定义ref函数就完成了,接下来就可以引入customRef编写逻辑了
引入customRef
想要使用,就得引入customRef
import {customRef } from 'vue'
使用customRef
customRef其实也是一个函数,且必须要返回出来这个customRef函数
但是这么写确报错了:
这是一个底层的错误,因为少了参数,它的参数是一个函数
customRef的参数和返回值
想要解决上面的错误,需要给customRef进行传参,vue设置的customRef参数是一个函数,这个函数里面就是自定义的逻辑
但是又报错了,这次的错误是没有get,其实是因为自定义函数没有返回值,vue要求为自定义函数的返回值必须是一个对象
但是又双叒叕报错了!
我们需要写两个特殊的属性:一个是get,它是一个函数,一个是set,它的值也是一个函数!
先不说逻辑怎么样,现在至少不报错了
初始化自定义ref页面数据
get
虽然不报错了,但是没有定义的数据,这是因为因为get没有返回值
有人读取数据,get函数就会被执行
我们给get返回数据即可
页面正常了,但是修改不了h3标签的数据,这是因为set没有返回值
set
和get是相反的,有人修改数据,set函数就会被执行,它有一个参数,就是修改后的最新数据
但是这样页面h4的数据还是不会变,这时候就要用到customRef的两个参数了:track,trigger
trigger
trigger有触发器的意思,它可以通知去调用get
track
track有追踪的意思,追踪属性的改变。如果不追踪,那么就永远返回属性最新的值
track要在return之前调用
这样整个自定义的customReff的流程就写完了,数据也可以正常修改了
这时候离官方案例就差一步了,就是延迟加载值,可以使用定时器实现
自定义逻辑:延迟加载(防抖效果)
但是这样不够完美,如果输入的频率大于定时器,那么会造成页面的抖动
可以定义一个变量,每次开启定时器的时候,都把id交给这个变量,然后每次进入set的时候,就清除对应的定时器id,重新开启新的定时器
当然可以再次优化,就是把定时器的事件换成变量,由ref传参:
全部代码如下:
<template> <input type="text" v-model="keyWord"> <h3>{{keyWord}}</h3> </template> <script> import { ref,customRef } from 'vue' export default { name: 'App', setup() { // 定义一个自定义Ref函数,名称为myRef function myRef(value,timerNum){ let timer // 这个return返回的是自定义的ref return customRef((track,trigger)=>{ // 这个return返回的是自定义ref里面的逻辑 return{ get(){ // 通知Vue追踪数据的变化 track() return value }, set(newValue){ clearTimeout(timer) timer= setTimeout(() => { value=newValue // 通知vue去重新解析模板 trigger() }, timerNum); } } }) } // let keyWord=ref('hello') // 使用vue提供的ref let keyWord=myRef('hello',500) // 使用程序员自定义的ref return {keyWord} } } </script>
总结
- 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
- 实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
5 provide 与 inject
provide与inject是一种组件中的通信方式。适应于祖孙间通信,祖孙组件也叫跨级组件,因为祖孙组件之间隔了一个父组件
provide译为提供,一般是提供数据
inject译为注入,注入数据
通过provide把数据给祖组件了,然后通过inject可以得到数据,他俩都是函数,可以直接调用,使用起来特别简单
provide 与 inject主要是实现祖与后代组件间通信,如果父子之间通信,可以使用props
下面就通过代码演示一下祖孙间的provide与inject数据交互
创建祖孙组件
首先遇到做点准备工作,创建一个祖孙组件
这里我以App作为祖组件,Child作为子组件,Son作为孙组件,并且都给了点样式
接下来就是一个套娃的效果,其实就是App中引入Child组件并使用,Child组件引入Son组件并使用:
然后一个简单的套娃就出现了
现在我想要把App祖组件传给Son孙组件,可以先给APP准备点数据:
这样数据就造好了,接下来就可以传递给Son孙组件了
引入provide
import {provide} from 'vue'
使用provide
provide都是函数,直接调用即可,需要传递两个参数:
参数1:数据变量
参数2:真正要传递的数据
到这里数据以及提供过去了,provide的代码也就编写完了,接下来是引入inject并使用
引入inject
import {inject} from 'vue'
使用inject
inject只有一个参数,就是provide传递数据的参数名称
数据可以正常接收,并且是响应式的
这样就完成了使用provide和inject祖孙组件的数据交互
总结
- 作用:实现祖与后代组件间通信
- 套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 - 具体写法:
- 1 祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
2 后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
6 响应式数据的判断
有的时候我们的数据背重重加工,变得比较复杂混乱,这时候如果想要判断它是否是响应式数据,可以借助以下几个API
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
引入API
还是一样的想要用就得引入:
import {isRef,isReactive,isReadonly,isProxy } from 'vue'
代码实现
简单写点数据判断是不是响应式的
当然都是响应式的