当前位置: 首页 > news >正文

Vue3 学习笔记 8:其它 API

Vue3 学习笔记 8:其它 API

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
})

就会发现只有直接修改.valuecount++修改整个人按钮是能正常工作的,另外两个按钮不能生效。这是因为与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类似,shallowReadonlyreadonly的替代,不过只会限制响应式数据的顶层属性是只读的,深层属性不做限制。

实例:

<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)创建的对象是普通对象,而非响应式对象,这是因为原始对象rawPersonmarkRaw标记了。

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 = newValuetrigger()}} 
})

customRef接收一个匿名函数作为参数,该函数返回一个包含 Getter 和 Setter 方法的对象。Getter 在响应式数据读取时触发,Setter 在响应式数据修改时触发。此外,匿名函数接收两个参数tracktriggertrack函数一般在 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: numberreturn {get() {track()return value},set(newValue) {clearTimeout(timer)timer = setTimeout(() => {value = newValuetrigger()}, 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 += valueelement.style.color = 'green'element.style.backgroundColor = 'yellow'
})

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

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

参考资料

  • 尚硅谷Vue3入门到实战
http://www.dtcms.com/a/469999.html

相关文章:

  • 库早报|15999元!先临三维发布口袋式3D扫描仪;激光制造与增材制造大会延期;拓竹双项入选《时代》年度发明榜
  • 流量网站建设教程电子商务网站建设php
  • React中Element、Fiber、createElement和Component关系
  • 大语言模型(LLM)是“预制菜”? 从应用到底层原理,在到中央厨房的深度解析
  • 做的好的商城网站南昌网站搭建公司 赣ICP
  • 软件测试资源笔记(4万字,持续更新中)
  • 做外贸网站做成哪种形式好WordPress购物个人中心
  • LeetCode 395 - 至少有 K 个重复字符的最长子串
  • 科技有限公司可以做网站建设吗成都网站网络建设
  • Qt绘制折线图
  • Idea中新建package包,变成了Directory
  • 如何自建淘宝客网站wordpress 知笔墨
  • Python爬虫实战:腾讯控股2024年资产负债分析
  • AI-调查研究-100-具身智能 现代AI方法全解析:强化学习、模仿学习与Transformer在机器人控制中的应用
  • Docker核心技术:深入理解网络模式 ——Host/None/Container 模式与混合云原生架构实践
  • 南通市住房城乡建设局网站磁力蜘蛛种子搜索
  • 解决HTML塌陷的方法
  • sqlite 使用: 03-问题记录:在使用 sqlite3_bind_text 中设置 SQLITE_STATIC 参数时,处理不当造成的字符乱码
  • 网站建设与维护难不难为什么找别人做网站
  • 广州木马网站建设公司医院门户网站建设规划
  • 大模型学习之 深入理解编码器与解码器
  • pyqt 触摸屏监听
  • C++ Primer Plus 第六版 第十三章 编程题
  • 大模型前世今生(十二):Hessian矩阵
  • 蛙跳积分法:分子动力学模拟中的高效数值积分技术
  • 详解 SNMPv1 与 SNMPv2 Trap 格式
  • 书法网站建设成都微信公众号制作
  • 宜春网站制作公司wordpress图片上传慢
  • Python串口通信与MQTT物联网网关:连接STM32与物联网平台
  • MyLanViewer(局域网IP扫描软件)