shallowRef和shallowReactive
vue的响应系统默认是深度的,虽然让状态管理非常可观,但是数据量巨大时深度响应会导致不小的性能负担,因为每个属性访问都将触发代理的依赖追踪。
- shallowReactive:只处理对象最外层属性的响应式
- shallowRef:只处理基本数据类型的数据,对象和数组类型不进行响应式处理
什么情况下使用浅响应式
- 如果一个对象数据,结构比较深,但变化时知识外层属性变化,那么就用shallowReactive
- 如果一个对象数据,后续功能不会修改该对象中的属性。而是产生新的对象来替换,那么就用shallowRef
<template>
<h2>shallowRef</h2>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
<p>成绩:{{ obj.study.english.number }}</p>
<button @click="obj.name+='--'">姓名</button>
<button @click="obj.age+=1">年龄</button>
<button @click="obj.study.english.number+=5">成绩</button>
<hr>
<h2>challowReactive</h2>
<p>{{ str }}--{{ strobj.age }}---{{ shallow.age }}</p>
<button @click="str++">ref++</button>
<button @click="strobj.age++">strobj++</button>
<button @click="shallow={age:999}">shallow</button>
</template>
<script setup>
import { shallowReactive,shallowRef,reactive,ref } from 'vue';
// const obj = reactive({
const obj = shallowReactive({ //shallowReactive 只处理对象最外层属性的响应式
name:"凯撒",
age:18,
study:{
english:{
number:22 //修改时生效,因为只处理最外层响应式数据,嵌套的数据不会处理
}
}
})
//shallowRef 只处理基本数据类型的数据,对象和数组类型不进行响应式处理
const str = ref(0)
const strobj = ref({ //也会生效,因为ref在处理对象或数组类型的时候会借用ES6的proxy方法
age:20
})
const shallow = shallowRef({ //shallowRef在处理对象或数组类型的时候不会变成响应式
age:20
})
console.log('strobj',strobj); //proxy数据
console.log('shallow',shallow); //object数据 使用shallowRef不会将对象进行响应式处理
console.log(obj)
</script>
shallowRef和shallowReactive使用误区!!!
readonly和shallowReadOnly
readonly:接受一个对象(无论是响应式或者普通的)或是一个ref,返回一个原值的制度代理。是深度只读
shallowReadOnly:让一个响应式数据变成只读的,但是浅只读
应用场景:在某些特定的情况下,我们可能不希望对属性进行更新的操作,那就可以包专场一个只读代理对象来读取数据,而不能修改或删除
<template>
<h2>readOnly</h2>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
<p>成绩:{{ obj.study.english.number }}</p>
<button @click="obj.name+='--'">姓名</button>
<button @click="obj.age+=1">年龄</button>
<button @click="obj.study.english.number+=5">成绩</button>
<hr>
<h2>shallowReadOnly</h2>
<p>{{ str }}--{{ strobj.age }}</p>
<button @click="str++">ref++</button>
<button @click="strobj.age++">strobj++</button>
</template>
<script setup>
import { readonly,shallowReadonly,reactive,ref } from 'vue';
//readonly接收一个对象或一个ref,返回一个只读代理(深度)。发送修改就会报警告
//shallowreadonly让一个响应式数据变只读,但是浅只读
//使用场景:在特定情况下,我们不希望数据更新,这个时候就可以将数据包装返回一个只读代理,而不能删除或修改
let obj = reactive({
name:"凯撒",
age:18,
study:{
english:{
number:22
}
}
})
// obj = readonly(obj) //深度只读
obj = shallowReadonly(obj) //浅只读:嵌套的数据还是可修改的响应式数据
let str = ref(0)
let strobj = ref({ //也会生效,因为ref在处理对象或数组类型的时候会借用ES6的proxy方法
age:20
})
console.log('strobj',strobj); //proxy数据
console.log(obj)
</script>
响应数据判断和Raw
在vue2的模板中需要一个根节点<div></div>,但在Vue3中不需要根节点,内部会将多个标签包含在一个fragment虚拟元素中,减少标签层级,减少内存占用
- toRaw:将响应式对象(只针对对象类型)转化成普通数据(失去响应式)
- markraw:将数据标记为原始数据,永远不会成为响应式数据
- isRef:判断数据是否是ref包裹的数据
- isReactive:判断数据是否是reactive创建的响应式代理
- isReadonly:判断数据是否是readonly创建的只读代理
- isproxy:判断数据是否是reactive或readonly包裹的对象,readonly包裹的对象类型也会变成proxy数据
import {toRaw,markRaw,isProxy,isRef,isReactive,isReadonly, onMounted} from 'vue';
onMounted(()=>{
// ----------- 响应式数据判断 -------------
console.log(isReactive(obj)); //检查数据是否是reative创造的
console.log(isReadonly(strobj)); //检查数据是否是readonly创造的
console.log(isRef(str)); //检查数据是否是ref创造的
console.log(isProxy(obj)); //检查数据是否是reactive或readonl创造的
console.log(isProxy(strobj));
console.log(isReactive( str ));
console.log(isRef( str ));
console.log(strobj);
})
Teleport瞬移DOM
<Teleport>是一个内置组件,它可以将一个组件内部的一部分模板传送到该组件的DOM结构外层的位置去。
应用场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在DOM中应该被渲染在整个Vue应用外部的其他地方
注意:<Teleport>只改变了渲染的DOM结构,它不会影响组件间的逻辑关系。也减少说,如果<Teleport>包含了一个组件,那么该组件始终和这个使用了<Teleport>的组件保持逻辑上的父子关系。传入的props和触发的事件也会照常工作。这也就意味着来自父组件的注入也会按预期工作,子组件将在Vue Devtools中嵌套在父级组件下面,而不是放在实际内容移动到的地方
父组件
<template>
<div class="father">
<h2>父组件</h2>
<sum></sum>
</div>
</template>
<script setup>
import sum from './sun.vue'
</script>
<style scoped>
.father{
background: red;
padding: 10px;
}
</style>
子组件
<template>
<div class="sun">
<h3>子组件</h3>
<!-- 遮罩层 -->
<button @click="ble=true">{{ble}}</button>
<!-- <teleport to='' /> -->
<!-- 此组件可以将指定的dom元素瞬移指定的地方渲染 -->
<!-- 仅仅只是渲染,在代码层面他的父子关系还会保存(父子传参之类还是有效的) -->
<Teleport to="body"> <!-- 将元素渲染到body标签层 -->
<div class="box" v-if="ble">
<div class="msg">
<p>这是一个弹窗</p>
<button @click="ble=false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const ble = ref(false)
</script>
<style scoped>
.sun{
background: pink;
padding: 10px;
}
/* 这样写样式,从视觉上box节点确实是全屏覆盖了,但是有bug,
父组件设置了transfrom或position,效果就会局限与这个父组件不会全屏展示 */
/* 为了解决这个bug,可以使用vue3内置的一个瞬移api <teleport> */
.box{
position:fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0,0.3);
}
.msg{
position:absolute;
left: 0;
margin: auto;
top: 0;
right: 0;
bottom: 0;
width: 200px;
height: 160px;
background: white;
}
</style>
多个Teleport共享目标
代码:
<Teleport to="#model">
<div>A</div>
</Teleport>
<Teleport to="#model">
<div>B</div>
</Teleport>
渲染的结果为:
<div id="model">
<div>A</div>
<div>B</div>
</div>
Suspense(不确定的)
它允许我们的应用在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验。
- 好处:在HTML模板中写异步判断,成功,等待
- Suspense原理:通过插槽实现。一个default和一个fallback。default里面放置异步组件,fallback里面放置异步组件未渲染之前的一个样式。
<Suspense>
具有深度异步依赖的组件
<index/>
在#fallback插槽中显示"正在加载中"
<template #fallback>
Loading...
</template>
</Suspense>
扩展:引入组件的方式有两种静态引入和动态引入,Vue3中动态引入会有变化,请看下面代码
<template>
<div class="father">
<h2>父组件</h2>
<!-- <som></som> -->
<!-- <Suspense>
<template v-slot:default> -->
<!-- 在使用命名插槽中,v-slot:名称和#名称,是一样的 -->
<som></som> <!-- 如果组件内有异步请求,也会等待组件内的请求完成 -->
<!-- </template>
<template #fallback>
<p>Loading...</p>
</template>
</Suspense> -->
</div>
</template>
<script setup>
// 引入组件的方式有两种 静态引入和动态引入
// import som from './son.vue' //静态引入 vue3也支持此写法
//动态引入 vue2的写法
// const som = ()=>import('./son.vue')
//在vue3中不支持上面那种动态引入的写法,vue3的动态引入要从vue中导出defineAsyncComponen方法
import {defineAsyncComponent } from 'vue'
// defineAsyncComponent方法里面写一个回调函数,函数就是 ()=>import('./son.vue')
const som = defineAsyncComponent(()=>import('./son.vue'))
// 在静态引入中,引入的代码是这个组件的一部分,会等代码全部执行完毕才会执行渲染。
// 在动态引入中,组件的引入是异步的,
// 如果代码程序命令较多则执行缓慢,异步引入的组件则会出现短暂的消失,
// 为了解决这个异步组件加载未完成出现暂时缺失的情况,vue3为我们提供了一个api <suspense>
//<suspense>允许我们的程序在等待异步组件时渲染一些后备内容,可以让有个更平滑的体验
//<Suspense>是通过插槽来实现的,一个是default和一个fallback,两个插槽都止允许一个子节点。
//default里面放置异步加载的组件,fallback里面放置异步组件未渲染之前的样式
</script>
<template>
<div class="son">
<h6>孙组件</h6>
<p>{{ ss }}</p>
</div>
</template>
<script setup>
import {ref}from 'vue'
// await axios.get('') //steup简写语法里面,不需要自己写async,当出现await时会自动在setup内部出现
// .then(date=>{console.log(date);})
// .catch(er=>{console.log(err);})
const ss = ref('this is a async conponent')
await new Promise((resolve,reject)=>{ //等待 promise完成
setTimeout(() => {
resolve(ss.value='cahnge ss')
}, 3000)
})
//注意 setup语法糖是不支持写async的,因为setup语法糖的return是返回参数,不是返回一个promise对象,使用async必须要在suspense标签内才能使用
// 前面说了使用了await 关键字 async会自动添加到setup内部,但是带有async setup()的组件必须嵌套在<suspense>中才能呈现。
</script>