红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 前端学习笔记
  3. 正文

Vue3 学习笔记 8:其它 API

2025年10月3日 22点热度 0人点赞 0条评论

ShallowRef

一个简单示例:

<template>
    <div>
        <h2>count:{{ count }}</h2>
        <h2>person:{{ person }}</h2>
        <button @click="count++">count++</button>
        <button @click="person.name='李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="changePerson">修改整个人</button>
    </div>
</template>
<script setup lang='ts' name='ShallowAPI'>
import { ref } from 'vue'
const count = ref(0)
const person = ref({
    name: '张三',
    age: 18
})
function changePerson() {
    person.value = {
        name: 'hahaha',
        age: 18
    }
}
</script>

如果使用shallowRef代替ref定义响应式数据:

import { ref,shallowRef } from 'vue'
const count = shallowRef(0)
const person = shallowRef({
    name: '张三',
    age: 18
})

就会发现只有直接修改.value的count++和修改整个人按钮是能正常工作的,另外两个按钮不能生效。这是因为与ref不同,ref会处理所包含数据的所有层次的响应式,而shallowRef仅会处理最顶层(.value)的响应式。

使用shallowRef的好处是因为不用处理多层次的响应式,如果对象包含多个层次的属性,使用shallowRef可以提升性能。因此如果不关心对象深层次的响应式(比如对象是通过服务端接口获取),就可以使用shallowRef以提升性能。

ShallowReactive

作用与ShallowRef类似,是reactive的替代,同样只处理浅层(第一层属性)响应式。

示例:

<template>
    <div>
        <h2>person:{{ person }}</h2>
        <button @click="person.name='李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="person.address.city='北京'">修改城市</button>
    </div>
</template>
<script setup lang='ts' name='ShallowReactive'>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
</script>

使用shallowReactive定义响应式数据:

import { shallowReactive } from 'vue'
const person = shallowReactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})

此时修改城市的按钮失效,原因与ShallowRef相同,这里不再赘述。

Readonly

readonly用于创建一个响应式数据的只读副本。

示例:

<template>
    <div>
        <h2>person:{{ person }}</h2>
        <button @click="person.name='李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="person.address.city='北京'">修改城市</button>
    </div>
</template>
<script setup lang='ts'>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
</script>

使用readonly创建一个只读副本:

<template>
    <div>
        <h2>person:{{ person }}</h2>
        <h2>personReadonly:{{ personReadonly }}</h2>
        <button @click="person.name = '李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="person.address.city = '北京'">修改城市</button>
        <br></br>
        <button @click="personReadonly.name = '李四'">修改副本名字</button>
        <button @click="personReadonly.age++">修改副本年龄</button>
        <button @click="personReadonly.address.city = '北京'">修改副本城市</button>
    </div>
</template>
<script setup lang='ts'>
import { reactive, readonly } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
const personReadonly = readonly(person)
</script>

测试可以发现对副本的修改操作都是不起作用的,并且浏览器控制台会有相应的警告信息:

[Vue warn] Set operation on key "age" failed: target is readonly

需要注意的是,原始的响应式数据与只读副本是有关联的,所以原始数据变化后只读副本的数据同样会改变。

如果你需要确保一个响应式数据不会被其它程序意外修改,可以使用readonly保护数据。

ShallowReadonly

与shallowRef类似,shallowReadonly是readonly的替代,不过只会限制响应式数据的顶层属性是只读的,深层属性不做限制。

实例:

<template>
    <div>
        <h2>person:{{ person }}</h2>
        <h2>personReadonly:{{ personReadonly }}</h2>
        <button @click="person.name = '李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="person.address.city = '北京'">修改城市</button>
        <br></br>
        <button @click="personReadonly.name = '李四'">修改副本名字</button>
        <button @click="personReadonly.age++">修改副本年龄</button>
        <button @click="personReadonly.address.city = '北京'">修改副本城市</button>
    </div>
</template>
<script setup lang='ts'>
import { reactive, shallowReadonly } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
const personReadonly = shallowReadonly(person)
</script>

这里的修改副本名字和修改副本年龄都不起作用,但修改副本城市正常工作,原因是这是一个深层属性。

ToRaw

使用toRaw可以获取响应式数据的原始数据(非响应式)。

示例:

import { reactive, toRaw } from 'vue';
const person = reactive({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
const rawPerson = toRaw(person)
console.log("rawPerson", rawPerson)
console.log("person", person)

可以通过浏览器控制台观察到,toRaw获取到的是响应式数据对应的原始数据(普通对象,非响应式)。

toRaw的用途是如果需要将数据传给第三方库(比如 Axios),可以使用toRaw传递一个普通对象,以确保响应式数据不会被第三方库意外修改进而导致视图发生变化所引起的 bug。

MarkRaw

示例:

<template>
    <div>
        <h2>person:{{ person }}</h2>
        <button @click="person.name = '李四'">修改名字</button>
        <button @click="person.age++">修改年龄</button>
        <button @click="person.address.city = '北京'">修改城市</button>
    </div>
</template>
<script setup lang='ts'>
import { reactive } from 'vue';
const rawPerson = {
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
}
const person = reactive(rawPerson)
</script>

示例中使用reactive创建为普通对象rawPerson创建了一个响应式数据person。可以利用这个响应式数据完成视图和数据的双向交互。

可以使用markRaw标记一个普通对象,让这个普通对象永远无法用于创建响应式数据:

import { reactive, markRaw } from 'vue';
const rawPerson = markRaw({
    name: '张三',
    age: 18,
    address: {
        city: '上海',
        street: '上海路'
    }
})
const person = reactive(rawPerson)
console.log("person", person)

此时所有按钮都会失效,因为reactive(rawPerson)创建的对象是普通对象,而非响应式对象,这是因为原始对象rawPerson被markRaw标记了。

markRaw的使用场景是如果项目中引入了第三方库的对象,如果这些对象被意外包裹成响应式的,就会导致不必要的性能开销,可以使用markRaw标记这些对象,以避免这种问题的发生。

CustomRef

使用customRef可以实现自定义的ref。,

示例:

<template>
    <div>
        <h2>{{ msg }}</h2>
        <input v-model="msg">
    </div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
const msg = ref('hello world')
</script>

示例使用ref定义了一个响应式数据,并且在视图中实现了双向绑定。

使用customRef代替ref定义响应式数据:

import { customRef } from 'vue';
const msg = customRef((track, trigger) => {
    let value = 'hello world'
    return {
        get() {
            track()
            console.log('get')
            return value
        },
        set(newValue) {
            console.log('set')
            value = newValue
            trigger()
        }
    } 
})

customRef接收一个匿名函数作为参数,该函数返回一个包含 Getter 和 Setter 方法的对象。Getter 在响应式数据读取时触发,Setter 在响应式数据修改时触发。此外,匿名函数接收两个参数track和trigger,track函数一般在 Getter 开始时调用,其用途是告诉 vue 这是一个特殊数据,需要持续监视其变化,值改变后视图中需要同步改变。trigger函数一般在 Setter 结束时调用,其用途是告诉 vue 数据已经发生改变。

此外,自定义响应式数据的真实数据在这里保存在匿名函数的局部变量value中,Getter 直接返回该值即可,Setter 接收的参数是响应式数据修改后的值,用该值作为value的新值即可。

使用customRef可以在原始ref的基础上进行一些自定义的功能,比如修改数据后视图不立即变化,而是延迟数秒后再改变:

import { customRef } from 'vue';
const msg = customRef((track, trigger) => {
    let value = 'hello world'
    let timer: number
    return {
        get() {
            track()
            return value
        },
        set(newValue) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                value = newValue
                trigger()
            }, 1000);
        }
    }
})

为了方便使用,通常会将自定义 Ref 封装在 hooks 中:

import { customRef } from "vue";
export function useMsgRef(initValue: string, delay: number) {
  const msg = customRef((track, trigger) => {
    let value = initValue;
    let timer: number;
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      },
    };
  });
  return { msg };
}

使用 hooks:

import { useMsgRef } from '@/hooks/useMsgRef';
const { msg } = useMsgRef('hello world', 2000);

Teleport

父组件:

<template>
    <div class="outer">
        <h2>我是App组件</h2>
        <img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
        <br>
        <Dialog />
    </div>
</template>
<script setup lang='ts'>
import Dialog from './Dialog.vue';
</script>
<style scoped>
.outer {
    background-color: #ddd;
    border-radius: 10px;
    padding: 5px;
    box-shadow: 0 0 10px;
    width: 400px;
    height: 400px;
    filter: saturate(200%);
}
​
img {
    width: 270px;
}
</style>

子组件:

<template>
    <button @click="isShow = true">展示弹窗</button>
    <div class="modal2" v-show="isShow">
        <h2>我是弹窗的标题</h2>
        <p>我是弹窗的内容</p>
        <button @click="isShow = false">关闭弹窗</button>
    </div>
</template>
<script setup lang='ts'>
import { ref } from 'vue'
const isShow = ref(false)
</script>
<style scoped>
.modal2 {
    width: 200px;
    height: 180px;
    background-color: skyblue;
    border-radius: 10px;
    padding: 5px;
    box-shadow: 0 0 5px;
    text-align: center;
    position: fixed;
    left: 50%;
    top: 20px;
    margin-left: -100px;
}
</style>

子组件是一个弹框,通常应该基于视窗(Window)进行定位,即显示在整个屏幕的居中位置。但因为这里父组件使用了滤镜(filter: saturate(200%);),导致子组件的弹窗基于父组件进行定位。

这里可以使用 Teleport(传送门)“强行”改变弹出框的 div 元素在最终 HTML DOM 树中的位置,让其位于 body 节点下,这样就可以基于屏幕进行定位和展示:

<template>
    <button @click="isShow = true">展示弹窗</button>
    <teleport to="body">
        <div class="modal2" v-show="isShow">
            <h2>我是弹窗的标题</h2>
            <p>我是弹窗的内容</p>
            <button @click="isShow = false">关闭弹窗</button>
        </div>
    </teleport>
</template>

当然,teleport只会影响最终渲染时的 Html DOM 树结构,不会影响 Vue 中的组件隶属关系。

Suspense

子组件:

<template>
    <div class="content2">
        <h2>我是Child组件</h2>
        <ul>
            <li v-for="user in users">{{ user.name }}</li>
        </ul>
    </div>
</template>
<script setup lang='ts'>
import axios from 'axios'
const res = await axios.get('https://jsonplaceholder.typicode.com/users')
const users = res.data
console.log(users)
</script>
<style scoped>
.content2 {
    background-color: skyblue;
    border-radius: 10px;
    padding: 10px;
    box-shadow: 0 0 10px;
}
</style>

父组件:

<template>
    <div class="content">
        <h2>我是Suspense组件</h2>
        <child />
    </div>
</template>
<script setup lang='ts'>
import Child from './Child.vue'
</script>
<style scoped>
.content {
    background-color: #ddd;
    border-radius: 10px;
    padding: 10px;
    box-shadow: 0 0 10px;
}
</style>

子组件没有被正常加载,无法在视图中看到子组件。这是因为子组件的setup方法中有接口调用,且使用await以同步的方式获取结果,这就会阻止组件的正常创建和加载。

此时可以在父组件中使用suspense组件异步加载子组件:

<template>
    <div class="content">
        <h2>我是Suspense组件</h2>
        <suspense>
            <template #default>
                <child />
            </template>
            <template #fallback>
                <div>加载中...</div>
            </template>
        </suspense>
    </div>
</template>

suspense有两个插槽,default对应的是异步任务结束后正常加载的内容,fallback是异步任务执行期间需要展示的内容(菊花图或者加载中之类的信息)。

  • 可以使用浏览器的开发者工具模拟慢速网络下的加载情况,观察fallback插槽的作用。

  • 如果子组件的异步任务不是以这种方式阻碍setup方法(比如将异步任务放在onMounted钩子中执行),就不需要使用suspense组件,这里是为了演示suspense组件的用途。

全局 API

app.component

定义一个组件src\components\Hello.vue:

<template>
    <div>
        <h2>Hello World!</h2>
    </div>
</template>
<script setup lang='ts'>
</script>
<style scoped></style>

使用app.component将这个组件注册为全局组件:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Hello from './components/Hello.vue'
​
const app = createApp(App)
app.component('Hello', Hello)
app.use(router)
app.mount('#app')

此时该组件可以不需要引入直接使用:

<template>
    <div>
        <Hello/>
    </div>
</template>
<script setup lang='ts'>
</script>

app.config

可以使用app.config添加全局属性:

app.config.globalProperties.helloMsg = 'Hello World'

此时可以在任意组件的差值表达式中直接使用该属性:

<template>
    <div>
        <Hello/>
        <h2>{{ helloMsg }}</h2>
    </div>
</template>
<script setup lang='ts'>
</script>

缺点是虽然可以正常使用,但是编译器会因为找不到这个变量定义而报错,可以在src\main.ts中添加以下定义来解决这个问题:

declare module 'vue' {
  interface ComponentCustomProperties {
    helloMsg: string
  }
}

app.directive

使用app.directive可以定义一个全局指令:

app.directive('beauty',(element,{value})=>{
    element.innerText += value
    element.style.color = 'green'
    element.style.backgroundColor = 'yellow'
})

可以在任意组件中使用这个全局指令:

<h2 v-beauty="123">你好</h2>

参考资料

  • 尚硅谷Vue3入门到实战

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: vue
最后更新:2025年10月3日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号