7.1 [shallowRef 与 shallowReactive]
shallowRef
1,作用: 创建一个响应式数据,但只对顶层属性进行响应式处理。
2, 用法:
let myVar = shallowRef(initialValue)
3, 特点: 只跟踪引用值的变化,不关心值内部的属性变化。
笔记





shallowReactive
1, 作用: 创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的
2, 用法:
const myObj = shallowReactive({...})
3, 特点: 对象的顶层属性是响应式的,但嵌套对象属性不是。
总结:通过使用 shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式API创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能.
7.2 [readonly 与 shallowReadonly]
readonly
1, 作用: 用于创建一个对象的深只都副本
2, 用法:
准备数据
1, src/main.ts

2,src/App.vue

将 ref定义的数据换成shallowRef后,1和4可以执行,2和3 按钮失效了

我们先分析2,3为什么不好用了

shallowRef 是浅层次的响应,他只处理第一层的响应式,红色的person.value是第一层,再加 .name就是第二层,所以2,3会失效


红色为第一层,浅层次就可以


只要你使用了shallowRef,只有xxx.value可以修改,其他就不行了

如果项目中有多层次的对象,如果你使用ref,那么好多层次都需要处理响应式,实际场景中,我们只需要第一层数据就可以了,有时候我们只第一层的对象有没有替换,其他层次我们不关心,此时最佳选择使用shallowRef

除了第一层,你里面哪怕有3万个属性,跟我们也没有任何关系

我们只关心绿色的第一层的改变

我们只关心是否发生整体的改变,name改变,加1岁等我们都不关心,只关心32行有没有整体替换,因为服务器给我们承诺了,每次给我返回都是新的东西,我关注的就是新的东西,你觉不觉得,要是用shallowRef效率是不是能更高一点。这就是为什么会有shallowRef出现。
总结: 你想关注整体修改,请你不要用ref,请使用shallowRef,为什么用他?效率高。
追求准确,追求无误,其实是官方的文档,官方文档可能会说的比较复杂,名字比较多,大家容易晕。

reactive修改整个car,使用绿色两种方法都不行

reactive定义的数据不能直接修改

要想修改reactive定义的整个对象,必须使用 Object.assign(obj1,obj2)这个语法才可以实现

reactive定义深层次响应式

红色的第一层

粉色的为第二层

_shallowRef与shallowReactive 实现代码如下:
1, src/App.vue
<template>
<div class="app">
<h2>求和为: {{ sum }}</h2>
<h2>名字为: {{ person.name }}</h2>
<h2>年龄为: {{ person.age }}</h2>
<h2>汽车为: {{ car }}</h2>
<button @click="changeSum">求和sum+1</button>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
<span>|</span>
<button @click="changeBrand">修改品牌</button>
<button @click="changeColor">修改颜色</button>
<button @click="changeEngine">修改发动机</button>
<button @click="changeCar">修改整个车</button>
</div>
<!-- <div class="container-fluid wraper">
<h1 class="title">
vue3 组件间通信
</h1>
<hr>
<div class="row">
<div class="col-xs-3 col-md-3 col-lg-3 col-xl-3">
导航区
<RouterLink active-class="active" class="list-group-item" to="/props">1. props</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/event">2. 自定义事件</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/mitt">3. mitt</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/model">4. v-model</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/attrs">5. $attrs</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/ref-parent">6. $refs,$parent</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/provide-inject">7. provide,inject</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/pinia">8. pinia</RouterLink>
<RouterLink active-class="active" class="list-group-item" to="/slot">9. slot</RouterLink>
</div>
<div class="col-xs-9 col-md-9 col-lg-9 col-xl-9">
<div class="pannel-body">
占位一个展示区,此处将来要展示不同的组件内容
<RouterView></RouterView>
</div>
</div>
</div>
</div> -->
<!-- <Count />
<br>
<LoveTalk />
<br>
<Dog /> -->
<!-- <div class="app">
标题
<Header></Header>
导航区
<div class="navigate">
第一种: to的字符串写法
<RouterLink to="/home" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink to="/about" active-class="active">关于</RouterLink>
第二种: to的对象写法 path跳转
<RouterLink :to="{path:'/home'}" active-class="active">首页</RouterLink>
<RouterLink :to="{path:'/news'}" active-class="active">新闻</RouterLink>
<RouterLink :to="{path:'/about'}" active-class="active">关于</RouterLink>
第三种: to的对象写法 名字跳转
<RouterLink replace :to="{name:'home'}" active-class="active">首页</RouterLink>
<RouterLink replace :to="{name:'news'}" active-class="active">新闻</RouterLink>
<RouterLink replace :to="{name:'about'}" active-class="active">关于</RouterLink>
</div>
展示区
<div class="main-content">
占位符,此处将来要展示不同的组件内容
<RouterView></RouterView>
此处以后可能要展示各种组件内容,到底展示哪个组件,取决于路由的匹配结果
</div>
</div> -->
</template>
<script setup lang="ts" name="App">
import {ref,reactive,shallowRef,shallowReactive} from 'vue'
let sum = shallowRef(0)
let person = shallowRef({
name: '张三',
age: 18
})
// let car = reactive({
// brand: '奔驰',
// options: {
// color: '黑色',
// engine: 'v8'
// }
// })
let car = shallowReactive({
brand: '奔驰',
options: {
color: '蓝色',
engine: 'v11'
}
})
// 求和sum+1
function changeSum() {
sum.value += 1
}
// 修改名字
function changeName() {
person.value.name = '李四'
}
// 修改年龄
function changeAge() {
person.value.age +=1
}
// 修改整个人
function changePerson() {
person.value = {
name: 'tony',
age: 100
}
}
/*************************/
// 修改品牌
function changeBrand() {
car.brand = '宝马'
}
// 修改颜色
function changeColor() {
car.options.color = '红色'
}
// 修改发动机
function changeEngine() {
car.options.engine = 'v12'
}
// 修改整个车
function changeCar() {
Object.assign(car, {
brand: '宝马',
options: {
color: '红色',
engine: 'v12'
}
})
}
</script>
<!-- <script setup lang="ts" name="App">
import { RouterView,RouterLink } from 'vue-router';
</script> -->
<!-- <script setup lang="ts" name="App">
import Count from './components/Count.vue'
import LoveTalk from './components/LoveTalk.vue'
import Dog from './components/Dog.vue'
</script> -->
<!-- <script setup lang="ts" name="App">
import { RouterView,RouterLink } from 'vue-router';
import Header from '@/components/Header.vue';
</script> -->
<style scoped>
.app {
background-color: #ddd;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
}
button {
margin: 0 10px;
}
/*
.title {
text-align: center;
}
.list-group-item {
margin-bottom: 20px;
}
.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
*/
/*
.navigate {
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.navigate a {
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navigate a.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content {
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
*/
</style>

065_readonly与shallowReadonly
7.2 [readonly 与 shallowReadonly]
readonly
1, 作用: 用于创建一个对象的深只读副本。
2, 用法:
const original = reactive({...});
const readOnlyCopy = readonly(original);
3, 特点:
a, 对象的所有嵌套属性都将变为只读。
b, 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
4, 应用场景:
a, 创建不可变的状态快照。
b, 保护全局状态或配置不被修改.

shallowReadonly
1, 作用: 与 readonly 类似,但只作用于对象的顶层属性。
2, 用法:
const original = reactive({...})
const shallowReadOnlyCopy = shallowReadonly(original)
3, 特点:
a, 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。
b, 适用与只需保护对象顶层属性的场景.
笔记



准备数据

readonly他能根据可变的sum缔造一个不可变的sum

要求 readonly()里面的参数必须为响应式数据,要么是ref定义的,或者是reactive所定义的数据。

绿色为sum1的结构,数据,方法, 红色为 sum2 的结构,数据,方法

不能为sum2赋值因为他是只读属性

点击 sum1,sum2也会跟着一起变化,因为sum2是根据sum1生成,他有一个关联过程。

绿色这句代码会维护着关联关系,他不是一锤子买卖。

sum1要变化sum2也会跟着变化

点击 '点我sum2+1' 数据不会方式改变,控制台有警告信息 告诉我们sum2只是一个可读属性,表示sum2不能修改,只可读取。这个有什么作用,这个作用大着呢?

你定义了一个数据,你不想让人修改数据,只有你自己才能更改,或者说你这个数据很重要,别人也不敢随便动你的数据
sum1你能操作,你的同事也能操作,由于这个数据太重要了,操作的时候,有没有不小心就给sum1改了,有可能sum1太重要了,你同事不小心给sum1给改了,而且还给改错了

你同事可以拿到你的sum1数据,然后复制一份sum1,形成了一个不可改变的sum2.

你同事就可以随便玩sum2

就算你同事写出sum2.value = ???,也没有关系,编辑器和控制台都会给你同事警告。就是你同事没有看到,也不要仅,因为压根就不能改。其实就是对数据的一种保护。

你同事写的数据很重要,你要是可以修改,有可能用户0元购,用户不花钱就把东西给买走了,你说这个数据重要不重要,你写他的功能,你就readonly()一下,你就可以使用这个数据,这就是readonly。

readonly他不仅修饰ref,也可以修饰reactive
reactive定义的对象如何整体修改?需要使用Object.assign('obj1',{obj2})这个方法,就是让obj2去覆盖obj1,从而达到整体修改的目的,切记,只有这样才可以真正实现整体修改对象的目的,具体示例代码如下
<script setup lang="ts" name="App">
import { reactive } from 'vue'
let car = reactive({
brand: '奔驰',
options: {
color: '红色',
price: 100
}
})
function changeCar() {
Object.assign(car, {
brand: '宝马',
options: {
color: '红色',
price: 80
}
})
}
</script>
当使用了shallowReactive,第一层就不能修改了,说明他是浅层制限制制度

红色的第一层结束后在点第二层,还是浅层的吗?不是

readonly,是限制所有层次都为只读
shallowReadonly 只限制第一层为只读,后面不管
他们两个有什么作用,一句话,保护数据,你想怎么保护?看你要保护到什么层度,完事。项目开发中readonly用得比较多一点,避免数据随便去改。

_readonly与shallowReadonly 实现代码如下
src/App.vue
<template>
<div class="app">
<h2>当前sum1为: {{ sum1 }}</h2>
<h2>当前sum2为: {{ sum2 }}</h2>
<h2>当前汽车为: {{ car1 }}</h2>
<h2>当前汽车为: {{ car2 }}</h2>
<button @click="changeSum1">点我sum1+1</button>
<button @click="changeSum2">点我sum2+1</button>
<button @click="changeBrand2">修改品牌(car2)</button>
<button @click="changeColor2">修改颜色(car2)</button>
<button @click="changePrice2">修改价格(car2)</button>
<button @click="changeCar">修改整个汽车</button>
</div>
</template>
<script setup lang="ts" name="App">
import { ref,reactive,readonly,shallowReadonly } from 'vue'
let sum1 = ref(0)
let sum2 = readonly(sum1)
let car1 = reactive({
brand: '奔驰',
options: {
color: '红色',
price: 100
}
})
// let car2 = readonly(car1)
let car2 = shallowReadonly(car1)
function changeSum1() {
sum1.value++
}
function changeSum2() {
sum2.value += 1 // 报错,因为sum2是只读的 不能修改
}
/**********/
function changeBrand2() {
car2.brand = '宝马'
}
function changeColor2() {
car2.options.color = '蓝色'
}
function changePrice2() {
car2.options.price += 10
}
function changeCar() {
Object.assign(car1, {
brand: '宝马',
options: {
color: '红色',
price: 80
}
})
}
</script>
<style scoped>
.app {
background-color: #ddd;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
}
button {
margin: 0 10px;
}
/*
.title {
text-align: center;
}
.list-group-item {
margin-bottom: 20px;
}
.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
*/
/*
.navigate {
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.navigate a {
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navigate a.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content {
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
*/
</style>

066_toRaw与markRaw
7.3 [toRaw 与 markRaw]
toRaw
1, 作用: 用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式,不会触发视图更新, 用于解决过度响应式或性能问题.
官网描述: 这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法,不建议保存对原始对象的持久引用,请谨慎使用。
何时使用?--在需要将响应式对象传递给非Vue的库或外部系统时,使用toRaw可以确保他们收到的是普通对象。
2, 编码:
import {reactive,toRow,markRaw,isReactive} from 'vue'
/*toRaw*/
// 响应式对象
let person = reactive({name:'tony',age:18})
// 原始对象
let rawPerson = toRaw(person)
/*markRaw*/
let citysd = markRaw([
{id:'zg01',name:'北京'},
{id:'zg02',name:'上海'},
{id:'zg03',name:'天津'},
{id:'zg04',name:'重庆'},
])
// 根据原始对象citysd去创建响应式对象citys2--创建失败,因为citys被markRaw标记了
let citys2 = reactive(citys)
console.log(isReactive(person))
console.log(isReactive(rawPerson))
console.log(isReactive(citys))
console.log(isReactive(citys2))
markRaw
1, 作用: 标记一个对象,使其永远不会变成响应式的。
例如: 使用 mockjs时,为了防止误把mockjs变为响应式对象,可以使用 markRaw 去标记mockjs
2, 编码:
/* markRaw */
let citys = markRaw([
{id:'zg01',name:'北京'},
{id:'zg02',name:'上海'},
{id:'zg03',name:'天津'},
{id:'zg04',name:'重庆'},
])
let citys2 = reactive(citys)
笔记






准备数据

有些时候,我们并不想用响应式数据,我们想用响应式或数据对应的最原始大数据。该如何处理呢?

代理对象-响应式数据,如何将他变成单纯的数据呢?

下面蓝色的数据就是单纯的数据

下面红色框里面的都是响应式数据

响应式数据最大的特点是: 你只要敢更改数据,页面就会更新


toRow() 这个方法,可以直接将响应式对象数据变成原始的对象数据。

person是响应式数据, person2是原始数据 代码-效果



红色框里面是响应式数据,你只要敢改页面就会更新

蓝色框里面是最原始的数据

原始数据的age,name是直接给的,就像我们正常的一般对象

响应式数据他不是的,他有target

所以如果在差值语法这个位置你写的{{person2.name}}呈现数据是没有问题,但是他呈现的数据是不会改变了

当你点击 '修改年龄'时,他就不会改变了,因为他就是一个正常的一般对象

你将 name,age 给他改变,vue也不知道数据改变了,也不会帮你更新页面

toRaw的作用就是用于获取响应式对象的原始对象,响应式对象就是person,原始对象就是person2,toRow返回的不是响应式的数据,他的变化不会触发视图的更新

我们平时写数据的时候,我们都希望是响应式的,在vue的世界里面,谁不希望数据是响应式的呢?你只要一改数据,页面就更新,这多好啊,你改数据,页面就更新,正常来讲是好事,但是有些场景,你改了数据,页面我就不想改变,那怎么办呢?你就得把一个响应式脱掉他的响应式功能,变成一个原始对象

明明需要响应式的,结果你写了一个person2,把响应式变成了原始对象

toRaw一般如何使用
我们来说一个真实的使用场景:
有这样一个函数,他不仅能展示人,他随这个人进行一些修改,比如 p.age+=10 p.name='~',大家想一想,showPerson()他不仅接收了一个人,而且还修改人身上的一些属性,他为什么要改,他改完之后,他还要用呢?所以这个场景我们就一句话:

当有一天,你去调用showPerson()的时候,你是不是要传一个参数进去,比如传一个person参数,showPerson(person)他会怎么样?

这个person到showPerson(person)这个方法中就会被改了

页面会跟着更新

你要是这样 showPerson(toRaw(person)) 去使用

这个人我给你传进来了你可以随便改

改完之后,你可以打印

你改来改去一直是toRaw原始的person

你影响到了这个橙色的person了吗?当然没有,就是这样一个使用场景

当你想把这个响应式对象交给别人去使用的时候,你自己还心知肚明,那个使用你数据的人肯定会改

但是你并不希望他修改你的数据

但是你并不希望他的修改响应到页面上,你就可以给他toRaw()把数据给他传递过去。

我们之所以在这里接收了person2,只有一个目的,想让大家知道person与person2的区别

使用toRaw的总结:
比如 axios发送的请求,loadash给我们处理的处理函数,loadash去给你出来这些东西时,你希望loadash页面更新吗?不希望的,所以此时就可以使用toRaw()去包裹一下响应式对象,随后你就可以交给非vue库或者外部系统去使用,比如外部系统想得到一个纯粹的对象,你给的是响应式对象,这样就会存在不必要的性能开销,使用toRaw()包裹一下可以确保他们收到的是普通对象.

markRaw
这个红色的数据不是响应式的

你写的所有绿色代码,都是在setup这个函数里面

你定义了一台车,没等模板用你就完成了三次加,最后整个setup变成103


此时整个setup已经执行完了

模板开始解析,等他用时,模板已经是103了


setup是特别靠前的一个钩子

代码-效果


红色是原始汽车,蓝色是响应式汽车

你成功的拿到绿色的car

制造出两个粉色的car2,粉色的car2直接变成响应式数据了

你可以在页面上去使用car2

你拿原始的红色的car升级粉色的响应式car2


有时候我们不希望把car随便就做成功为响应式,那怎么办?

当你像27行这样单纯去定义一个car时,就意味着car可以做为一个源头,去打造一个全新的响应式对象,但是你若对他进行一些限制,比如 markRaw

就得对他进行限制,从一开始我就定义好,car永远不可能像28行这样使用

告诉别人我的markRaw出现了,以后car他就是一个原始的对象,能把他做成一个响应式吗?不能,此时,你发现你的28行完全不起作用了.

现在打印的car2是不是普通的原始对象?是的

你打开看他们都是最原始的对象,数据都是直接给的,car2已经不是响应式数据了。

我们再去页面点击 '点我价格+10',此时页面没有发生任何改变

因为这个car使用了markRaw注标记了,他永远都不可能变成响应式数据了

28行已经失效了,当car打了markRaw标签,该数据就失去了响应式

mockjs库,当后端接口还未出现时,我们可以借助mockjs去模拟后端的接口数据。

mockjs 使用步骤
1, 安装 mockjs
npm i mockjs
2, 引入
import mockjs from 'mockjs'
3, 使用
console.log(mockjs)

mockjs是一个对象

此时你已经将mockjs变成了响应式对象数据了


你对蓝色框里面数据做一个小小修改,都会比vue所发现,这就有一个效率问题。所以我们该如何处理mockjs数据呢?

以后我们就直接使用 mockJs 就可以了

此时的mockJs永远都不可能变成响应式对象

_toRaw与markRaw 实现代码如下
1, src/App.vue
<template>
<div class="app">
<h2>姓名: {{ person.name }}</h2>
<h2>年龄: {{ person.age }}</h2>
<button @click="person.age += 1">修改年龄</button>
<hr>
<h2>{{ car2 }}</h2>
<button @click="car2.price+=10">点我价格+10</button>
</div>
</template>
<script setup lang="ts" name="App">
import { reactive,toRaw,markRaw } from 'vue'
import mockjs from 'mockjs'
/* toRaw */
let person = reactive({
name: 'tony',
age: 18
})
// 用于获取响应式对象的原始对象
let rawPerson = toRaw(person)
// console.log('响应式数据',person)
// console.log('原始数据',rawPerson)
/* markRaw */
// let car = {brand:'奔驰',price:100}
// 标记为原始对象,不会被转为响应式数据,后续也不会转为响应式数据
let car = markRaw({brand:'奔驰',price:100})
let car2 = reactive(car)
console.log(car)
console.log(car2)
// 测试mockjs
// console.log(mockjs)
let mockJs = markRaw(mockjs)
let mockjs2 = reactive(mockJs)
console.log(mockjs2) // mockjs对象转为响应式数据
</script>
<style scoped>
.app {
background-color: #ddd;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
}
button {
margin: 0 10px;
}
/*
.title {
text-align: center;
}
.list-group-item {
margin-bottom: 20px;
}
.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
*/
/*
.navigate {
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.navigate a {
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navigate a.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content {
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
*/
</style>

067_customRef
7.4 [customRef]
作用: 创建一个自定义的 ref, 并对其依赖跟踪和更新触发进行逻辑控制。
实现防抖效果 (useSumRef.ts)
import {customRef} from 'vue'
export default function(initValue:string,delay:number) {
let msg = customRef((track.trigger) => {
let timer: number
return {
get() {
track() //告诉vue数据msg很重要,要对msg持续关注,一旦有变化就更新
}
set() {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() // 通知msg数据变化了
},delay);
}
}
})
}
笔记


vue已经提供了ref,我们为什么还要自定义ref呢?

v-model就是双向绑定,你在input输入框中输入123,由于是双向绑定,setup里面的msg就跟着改变,页面上用到数据的地方也在更新,没有问题。ref定义的是响应式数据。


ref定义的是响应式数据,响应式数据有什么特点,你只要敢改,页面就会跟着变

这个是借着v-model双向绑定进行修改的

使用 v-model 你只要在输入框中输入6,立马页面就跟着改变了

vue里面给你的ref,数据一变,我页面立马更新

需求: 你在输入框中输入6,过1秒钟后页面才更新,如何去实现?vue里面数据一变,我得等1秒再改变,vue里面的ref做不到,所以此处使用ref不太合适, 又比如: 数据一变,我就发个请求,ref也办不到。vue里面数据一变,他直接对标页面去。那么谁可以呢?自定义ref就可以.
自定义ref最基本的骨架为:
let msg = customRef(() => {
return {
// get何时调用?--msg被读取时
get() {},
// set何时调用?--msg被修改时
set() {}
}
})
以上代码解释为:
customRef(传入一个回调函数,回调函数里面需要返回一个对象,对象里面有基本的get() {}函数和基本的set() {} 函数)

因为msg被读取两次,所以打印了两次get,如下图所示


23行的value难道你就打印吗?

控制台可热闹啦

模板里面你用的是msg,只不过你 initValue里面存储的是'您好'

在get里面返回 initValue,在set里面修改了 initValue=value(input框输入值)

自定义 ref
<script setup lang="ts" name="App">
import { ref,customRef } from 'vue'
// 使用Vue提供默认的ref定义响应式数据,数据一变,页面自动更新
// let msg = ref('您好')
// 使用 Vue 提供的 customRef 方法自定义响应式数据
let initValue = '您好'
// 自定义响应式数据,需要传入一个工厂函数,返回一个对象,该对象有两个方法
// 这个对象的get和set方法分别对应数据的读取和修改
// 这两个方法都需要返回一个值,get需要返回数据的当前值
// set需要接收一个新的数据值作为参数,以便更新数据
// 这两个方法还可以接收一个额外的参数,这个参数是一个函数
// 这个函数有两个作用:一个是标记当前数据为追踪状态(track),另一个是手动触发更新(trigger)
// 这两个方法在自定义响应式数据时可以不写,因为Vue会自动帮你处理
// 但是如果你想手动控制数据的更新时机或者做一些额外的操作,就可以使用它们
// 例如,我们可以使用它们来控制数据的更新时机
// 或者做一些额外的操作,比如记录日志等
// track(跟踪)和trigger(触发)这两个参数是可选的,如果不传,Vue会自动帮你处理
let msg = customRef((track,trigger) => {
return {
// get何时调用?--msg被读取时
get() {
track() // 告诉Vue这个值是需要被追踪的,以便后续可以触发更新
return initValue
},
// set何时调用?--msg被修改时
set(value: string) {
console.log('set', value);
initValue = value;
trigger() // 手动通知Vue组件需要更新了
// 这里可以自定义数据更新的逻辑
}
}
})
</script>

track() 告诉Vue数据msg很重要,你要对msg进行持续关注,一旦msg变化就去更新
trigger() 通知 Vue 一下,数据msg变化了

红色框里面的内容: 其实就是你自己亲自去实现 ref

公司是不会这样使用ref的
公司会将 自定义ref 封装成 hooks

以后当公司有人要用延迟1秒ref的时候,那个人需要将 黄色代码重新写一遍,

自定义 ref 就是在原生的 ref 基础上,加上自己的一些逻辑,这就是我们所说的自定义ref.

聊的时候就是 track()和trigger()

_customRef 实现代码如下
1, src/App.vue
<template>
<div class="app">
<h2>{{ msg }}</h2>
<input type="text" v-model="msg">
</div>
</template>
<script setup lang="ts" name="App">
import useMsgRef from '@/useMsgRef';
import { ref,customRef } from 'vue'
// 使用Vue提供默认的ref定义响应式数据,数据一变,页面自动更新
// let msg = ref('您好')
// 使用 useMsgRef 自定义定义一个响应式数据且有延迟效果
let {msg} = useMsgRef('尚硅谷', 2000)
</script>
<style scoped>
.app {
background-color: #ddd;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
}
button {
margin: 0 10px;
}
/*
.title {
text-align: center;
}
.list-group-item {
margin-bottom: 20px;
}
.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
*/
/*
.navigate {
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.navigate a {
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navigate a.active {
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content {
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
*/
</style>
2, src/useMsgRef.ts
import { customRef } from 'vue'
export default function(initValue: string, delay: number) {
// 使用 Vue 提供的 customRef 方法自定义响应式数据
let timer: number // 定时器编号 定义一个定时器,用于延迟更新数据
// 自定义响应式数据,需要传入一个工厂函数,返回一个对象,该对象有两个方法
// 这个对象的get和set方法分别对应数据的读取和修改
// 这两个方法都需要返回一个值,get需要返回数据的当前值
// set需要接收一个新的数据值作为参数,以便更新数据
// 这两个方法还可以接收一个额外的参数,这个参数是一个函数
// 这个函数有两个作用:一个是标记当前数据为追踪状态(track),另一个是手动触发更新(trigger)
// 这两个方法在自定义响应式数据时可以不写,因为Vue会自动帮你处理
// 但是如果你想手动控制数据的更新时机或者做一些额外的操作,就可以使用它们
// 例如,我们可以使用它们来控制数据的更新时机
// 或者做一些额外的操作,比如记录日志等
// track(跟踪)和trigger(触发)这两个参数是可选的,如果不传,Vue会自动帮你处理
let msg = customRef((track,trigger) => {
return {
// get何时调用?--msg被读取时
get() {
// 告诉Vue数据msg很重要,你要对msg进行追踪,以便后续可以触发更新
track() // 告诉Vue这个值是需要被追踪的,以便后续可以触发更新
return initValue
},
// set何时调用?--msg被修改时
set(value: string) {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
// 手动触发更新,告诉Vue数据已经更新了,需要重新渲染页面
trigger() // 手动触发更新
},delay);
}
}
})
return {msg}
}

068_Teleport
8.1 [Teleport]
什么是Teleport?--Teleport是一种能够将我们的组件html结构移动到指定位置的技术。
<template to='body'>
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
<p>我是弹窗中的一些内容</p>
<button @click="isShow = false">关闭弹窗</button>
</div>
</template>
笔记


8.2 [Suspense]
1, 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
2, 使用步骤:
a, 异步引入组件
b, 使用 Suspense 包裹组件,并配置好 default 与 fallback
import { defineAsyncComponent,Suspense } from 'vue'
const Child = defineAsyncComponent(()=>import('./Child.vue'))
准备数据
1, src/App.vue

2, src/Modal.vue

3, 父与子

src/App.vue
<template>
<div class="outer">
<h2>我是app组件</h2>
</div>
</template>
<script setup lang="ts" name="App">
</script>
<style>
.outer {
background-color: #ddd;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
}
</style>



安装 sass-loader node-sass
npm install sass-loader node-sass -D
备份
1, src/App.vue
<template>
<div class="outer">
<h2>我是App组件</h2>
<img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
<hr>
<Modal />
</div>
</template>
<script setup lang="ts" name="App">
import Modal from '@/Modal.vue';
</script>
<style scoped>
.outer {
width: 400px;
height: 400px;
background-color: #ddd;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
}
img {
width: 180px;
}
</style>
2, src/Modal.vue
<template>
<div class="modal">
<h2>我是弹窗的标题</h2>
</div>
</template>
<script setup lang="ts" name="Modal">
</script>
<style scoped>
.modal {
background-color: skyblue;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 5px;
width: 200px;
height: 200px;
}
</style>



UI组件库的弹窗不是参考父容器,而是参考整个窗口
绿色的是窗口,粉色的是弹窗

水平位置的正中央


只要在父容器添加fitter滤镜,position定位就失效
有滤镜 fitter 代码-效果 参考父容器


无滤镜 fitter 代码-效果 参考整个视图


红色和蓝色是包裹关系

红色的要参考蓝色的定位

有时候滤镜是必须有,我们不参考父容器,只参考视图,这个问题需要借助Teleport传送门来解决,to就是下面绿色框里面的数据你把他塞到哪里去,比如 to='body'

有滤镜 fitter,通过 teleport 解决 参考视窗失效问题 代码-效果



modal里面的div从app的div里面逃出来了

没有使用 teleport 传送门,APP里面包含modal 即视窗效果失效

加了filtter 滤镜,没有使用 teleport 传送门,弹窗就会参考父容器 即参考视窗失效

加了teleport传送门,你可以让弹窗到你指定的任意容器里面

最牛的是绿色的逻辑不用扔出去

只是把红色的结构扔出去了

to="#app" 或者 to="body"
_Teleport 实现代码如下:
1, src/App.vue
<template>
<div class="outer">
<h2>我是App组件</h2>
<img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
<br>
<Modal />
</div>
</template>
<script setup lang="ts" name="App">
import Modal from './Modal.vue';
</script>
<style>
.outer {
background-color: #ddd;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
width: 400px;
height: 400px;
/* 突出网站的颜色,提高饱和度 */
filter: saturate(200%);
}
img {
width: 300px;
}
</style>
2, src/Modal.vue
<template>
<button @click="isShow = true">展示弹窗</button>
<teleport to='body'>
<div class="mod" v-show="isShow">
<h2>我是弹窗的标题</h2>
<p>我是弹窗的内容</p>
<button @click="isShow = false">关闭弹窗</button>
</div>
</teleport>
</template>
<script setup lang="ts" name="Modal">
import { ref } from 'vue'
let isShow = ref(false)
</script>
<style scoped>
.mod {
width: 200px;
height: 180px;
background-color: skyblue;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 5px;
text-align: center;
position: fixed;
top: 50px;
left: 50%;
margin-left: -100px;
}
</style>

069_Suspense
官网: https://cn.vuejs.org
8.2 [Suspense]
1, 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
2, 使用步骤:
a, 异步引入组件
b, 使用 Suspense 包裹组件,并配置好 default 与 fallback
import { defineAsyncComponent,Suspense } from 'vue'
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
<div class="app">
<h3>我啊App组件</h3>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<h3>加载中...</h3>
</template>
</Suspense>
</div>
</template>
笔记




草料文本二维码生成器
https://cli.im/text
路过图床 https://imgchr.com/
https://api.uomg.com/
api.uomg.com
https://cli.im/text
https://imgchr.com/
https://imgchr.com/i/pZbgh3q
<a href="https://imgchr.com/i/pZbgh3q"><img src="https://s41.ax1x.com/2026/02/12/pZbgh3q.jpg" alt="pZbgh3q.jpg" border="0" /></a>
https://s41.ax1x.com/2026/02/12/pZbgh3q.jpg
import axios from 'axios'
axios.get('https://dog.ceo/api/breed/pembroke/images/random').then((res) => {
console.log(res.data.message)
})
每次返回一张图片地址







你有异步任务,就需要用Suspense

网络请求

异步任务做完了,红色才出现

绿色的异步任务没有做完他出现

使用场景

你的子组件里面包含异步任务,并且你的子组件还要打印他,还要用他

在 setup 里面你写了异步任务,如果你想让他网速慢时也能呈现一些东西,

此时就可以用Suspense,牢记有两对数据
1, 先呈现 加载中....
2, 后呈现Child组件

_Suspense 实现代码如下
1, src/App.vue
<template>
<div class="app">
<h2>我是App组件</h2>
<Suspense>
<template #default>
<Child />
</template>
<template #fallback>
<h2>加载中...</h2>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts" name="App">
import { Suspense } from 'vue';
import Child from './Child.vue';
</script>
<style>
.app {
background-color: #ddd;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>
<!-- <template>
<div class="outer">
<h2>我是App组件</h2>
<img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
<br>
<Modal />
</div>
</template>
<script setup lang="ts" name="App">
import Modal from './Modal.vue';
</script>
<style>
.outer {
background-color: #ddd;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
width: 400px;
height: 400px;
/* 突出网站的颜色,提高饱和度 */
filter: saturate(200%);
}
img {
width: 300px;
}
</style> -->
2, src/Child.vue
<template>
<div class="child">
<h2>Child子组件</h2>
<h3>当前求和为: {{ sum }}</h3>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from 'vue'
import axios from 'axios';
let sum = ref(0)
let {data:{message}} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
console.log(message)
// axios.get('https://dog.ceo/api/breed/pembroke/images/random').then((res) => {
// console.log(res.data.message)
// })
</script>
<style scoped>
.child {
background-color: skyblue;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>

070_全局API转移到应用对象
8.3 [全局API转移到应用对象]
1, app.component 注册全局组件
2, app.config 全局配置
3, app.directive 全局指令
4, app.mount 挂载
5, app.unmount 卸载
6, app.use 安装插件
笔记


8.4 [其他]
1, 过渡类名 v-enter修改为 v-enter-from, 过渡类名 v-leave 修改为 v-leave-from
2, keyCode 作为 v-on 修饰符的支持
3, v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync
4, v-if 和 v-for 在同一个元素身上使用时的优先级发生了变化。
5, 移除了 $on,$off和$once实例方法。
6, 移除了过滤器 filter
7, 移除了$children 实例 propert.
代码-效果



declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
_全局API转移到应用对象 实现代码如下
1, src/App.vue
<template>
<div class="app">
<h2>我是App组件 {{ x }}</h2>
<Hello />
<Child />
<!-- <Suspense>
<template #default>
<Child />
</template>
<template #fallback>
<h2>加载中...</h2>
</template>
</Suspense> -->
</div>
</template>
<script setup lang="ts" name="App">
import { Suspense } from 'vue';
import Child from './Child.vue';
import Hello from './Hello.vue';
</script>
<style>
.app {
background-color: #ddd;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>
<!-- <template>
<div class="outer">
<h2>我是App组件</h2>
<img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
<br>
<Modal />
</div>
</template>
<script setup lang="ts" name="App">
import Modal from './Modal.vue';
</script>
<style>
.outer {
background-color: #ddd;
border-radius: 10px;
padding: 5px;
box-shadow: 0 0 10px;
width: 400px;
height: 400px;
/* 突出网站的颜色,提高饱和度 */
filter: saturate(200%);
}
img {
width: 300px;
}
</style> -->
2, src/main.ts
// 引入 createApp 用于创建应用
import {createApp} from 'vue'
// 引入 App根组件
import App from './App.vue'
// 引入 Hello 组件
import Hello from './Hello.vue'
// 第一步 引入 pinia
// import {createPinia} from 'pinia'
// 引入路由器
// import router from './router'
// 引入 emitter
// import emitter from '@/utils/emitter.ts'
// 创建一个应用
const app = createApp(App)
// 注册全局 Hello 组件
app.component('Hello', Hello)
app.config.globalProperties.x = 100
declare module 'vue' {
interface ComponentCustomProperties {
x: number
// $http: typeof axios
// $translate: (key: string) => string
}
}
// app.config.globalProperties.$emitter = emitter
// 第二步 创建 pinia
// const pinia = createPinia()
// 第三步 安装 pinia 就是将 pinia 挂载到应用上
// app.use(pinia)
// app.use(createPinia()) // 也可以这样写,等同于上面的写法 简写写法
// 使用路由器
// app.use(router)
// 全局指令
app.directive('beauty',(element,{value})=>{
element.innerText += value
element.style.color = 'red'
element.style.backgroundColor = 'yellow'
})
// 挂载整个应用到 app 容器中
app.mount('#app')
// 2秒钟后卸载app
// setTimeout(()=>{
// app.unmount()
// },2000)
3, src/Child.vue
<template>
<div class="child">
<h2>Child子组件 {{ x }}</h2>
<h3>当前求和为: {{ sum }}</h3>
<h4 v-beauty="sum">好开心</h4>
<Hello />
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from 'vue'
import axios from 'axios';
import Hello from './Hello.vue';
let sum = ref(1)
// let {data:{message}} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
// console.log(message)
</script>
<style scoped>
.child {
background-color: skyblue;
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 10px;
}
</style>
4, src/Hello.vue
<script setup lang="ts" name="Hello">
</script>
<template>
<h2 style="color: red;">您好 {{ x }}</h2>
</template>
<style scoped>
</style>

071_Vue3的非兼容性改变
072_pinia
src/pages/08_pinia/Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
</div>
</template>
<script setup lang="ts" name="Child1">
</script>
<style scoped>
.child1 {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

src/pages/08_pinia/Father.vue



