Vue3魔法手册 作者 张天禹 016_vue3中一些特定用法介绍

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

相关推荐
henry1010102 小时前
DeepSeek生成的网页小游戏 - 迷你高尔夫
前端·javascript·游戏·html
薛一半2 小时前
React的组件
前端·javascript·react.js
广州华水科技3 小时前
水库的单北斗GNSS变形监测系统是什么?主要有哪些应用?
前端
薛一半3 小时前
React三大属性之props
前端·javascript·react.js
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue连锁门店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Wect3 小时前
LeetCode 105. 从前序与中序遍历序列构造二叉树:题解与思路解析
前端·算法·typescript
麦麦大数据4 小时前
F071_vue+flask基于YOLOv8的实时目标检测与追踪系统
vue.js·yolo·目标检测·flask·vue·视频检测
烤麻辣烫4 小时前
正则表达式快速掌握
前端·javascript·学习·正则表达式·html
长城20244 小时前
HTML5中可以省略属性值的11个属性总结
前端·html·html5·属性值·省略属性值