Vue3-其他API

耶温

2292字约8分钟

2024-07-17

Vue3-其他API

shallowRef

shallowRefref() 的浅层作用形式。只有第一层具有响应式。

ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

换句话说只有data.value = 'xx'可以响应。再深层的data.value.str = 'xxx'不行。

示例:

<template>
  data1:{{ data1 }} <br />
  data2:{{ data2 }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>
  <button @click="changeData3">Click3</button>
</template>
<script setup lang="ts" name="father">
import { shallowRef } from 'vue';
let data1 = shallowRef('hello')
let data2 = shallowRef({
  name: 'iyuwb', age: '18'
})
function changeData1() {
  data1.value += '+'
}
function changeData2() {
  data2.value.name += '+'
}
function changeData3() {
  data2.value = { name: 'yevin', age: '20'}
}
</script>

如上述代码所示,我们只有按钮1、3可以实现修改数据展示。因为他们俩是对.value的操作。

shallowReactive

shallowReactivereactive() 的浅层作用形式。

reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

示例:

<template>
  data:{{ data }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>
  <button @click="changeData3">Click3</button>
</template>
<script setup lang="ts" name="father">
import { shallowReactive } from 'vue';


let data = shallowReactive({
  name: 'iyuwb',
  age: 18,
  car: {name: 'BYD', price: 500 }
})

function changeData1() {
  data.name += '+'
}
function changeData2() {
  data.car.name += '+'
}
function changeData3() {
  data = {
    name: 'yevin',
    age: 20,
    car: { name: 'BMW',price: 5000 }
  }
}
</script>

如示例代码,我们只有按钮1可以实现修改数据展示。我们只能修改data中最外层的数据。直接覆盖真个data数据也不行。

总结:

通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。

readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的深只读代理。不能进行修改操作。

示例:

<template>
  data1:{{ data1 }} <br />
  data2:{{ data2 }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>

</template>
<script setup lang="ts" name="father">
import { readonly, shallowRef } from 'vue';
let data1 = shallowRef('hello')
let data2 = readonly(data1)
function changeData1() {
  data1.value += '+'
}
function changeData2() {
  data2.value += '+'
}
</script>

如上图所示,我们只有按钮1可以实现修改数据展示。因为readonly是只读的。并且编译器和浏览器控制台会提示我们。

shallowReadonly

readonly()的浅层作用形式。和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。

示例:

<template>
  data1:{{ data1 }} <br />
  data2:{{ data2 }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>
  <button @click="changeData3">Click3</button>
</template>
<script setup lang="ts" name="father">
import { reactive, shallowReadonly, } from 'vue';
let data1 = reactive({
  name: 'iyuwb',
  age: 18,
  car: { name: 'BYD', price: 500 }
})
let data2 = shallowReadonly(data1)
function changeData1() {
  data2.name += '+'
}
function changeData2() {
  data2.car.name += '+'
}
function changeData3() {
  data2 = {
    name: 'yevin',
    age: 20,
    car: { name: 'BMW', price: 5000 }
  }
}
</script>

运行上述代码,可以看出,只有按钮2能实现数据的修改展示。直接覆盖整体数据,和修改最外层数据都不能实现数据的修改展示。并且和readonly一样,编译器和浏览器控制台会提示我们。

toRaw

根据一个 Vue 创建的代理返回其原始对象。返回的对象不再是响应式的。 toRaw() 可以返回由 reactive()readonly()shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

示例:

<template>
  data1:{{ data1 }} <br />
  data2:{{ data2 }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>
</template>
<script setup lang="ts" name="father">
import { reactive,toRaw } from 'vue';
let data1 = reactive({
  name: 'iyuwb',
  age: 18,
  car: { name: 'BYD', price: 500 }
})
let data2 = toRaw(data1)
function changeData1() {
  data1.name += '+'
}
function changeData2() {
  data2.name += '+'
}
</script>

如上面示例所示,我们在按钮2的修改不会展示在页面上,但是当点击按钮1时data1的改变会同步刷新data2的数据以及展示。

何时使用? —— 在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象

markRaw

将一个对象标记为不可被转为代理。返回该对象本身。换句话说就是,标记一个对象,使其永远不会变成响应式的。

markRaw() 可以将一个普通的 JavaScript 对象标记为“原始”对象,即永远不会成为一个代理。这在处理第三方代码或来自不可信来源的输入时非常有用。

需要注意的是,当传给markRaw()的对象是一个由reactive()创建的响应式对象时,返回的对象还是一个响应式对象。需要给markRaw()传入一个非响应式的对象。

示例:

<template>
  data1:{{ data1 }} <br />
  data2:{{ data2 }} <br />
  data2:{{ data3 }} <br />
  <button @click="changeData1">Click1</button>
  <button @click="changeData2">Click2</button>
  <button @click="changeData3">Click3</button>
</template>
<script setup lang="ts" name="father">
import { markRaw, reactive, } from 'vue';
let data1 = {
  name: 'iyuwb',
  age: 18,
  car: { name: 'BYD', price: 500 }
}
let data2 = markRaw(data1)
let data3 = reactive(data2)
function changeData1() {
  data1.name += '+'
}
function changeData2() {
  data2.name += '+'
}
function changeData3() {
  data3.name += '+'
}
</script>

如上所示,我们点击三个按钮时,页面的数据都不会发生改变。

customRef

自定义ref,创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。

示例:

<template>
  {{ inputValue }}
  <input type="text" v-model="inputValue">
</template>
<script setup lang="ts" name="father">
import { customRef } from 'vue';
let data = '默认数据'
let inputValue = customRef((track,trigger)=>{
  return{
    // 数据被读取时调用
    get(){
      // 在这里可以写一些逻辑处理
      track() // 跟踪 告诉Vue 持续跟踪 当前数据,一旦数据变化就需要更新 
      return data
    },
    // 数据被修改时调用
    set(value){
      // 在这里可以写一些逻辑处理
      console.log('set',value)
      data = value
      trigger() //触发器 通知Vue 当前数据变化了
    },
  }
})
</script>

如上代码所示,我们自定义ref的时候需要额外定义一个变量,然后在get中返回这个变量,在set中修改这个变量。并且customRef()的回调函数中有两个参数,一个是track,一个是trigger。两个缺一不可。

  • track():跟踪函数,用于告诉Vue 持续跟踪当前数据,一旦数据变化就需要更新
  • trigger():触发器函数,用于通知Vue 当前数据变化了。

将自定定义ref封装为hook: hook/useInputRef.ts:

import { customRef } from 'vue'
export default function (initValue: string) {
    let data = initValue
    let inputValue = customRef((track, trigger) => {
        return {
            // 数据被读取时调用
            get() {
                // 在这里可以写一些逻辑处理
                track() // 跟踪 告诉Vue 持续跟踪 当前数据,一旦数据变化就需要更新 
                return data
            },
            // 数据被修改时调用
            set(value) {
                // 在这里可以写一些逻辑处理
                console.log('set', value)
                data = value
                trigger() //触发器 通知Vue 当前数据变化了
            },
        }
    })
    return {
        inputValue
    }
}

组件中使用:

<template>
  {{ inputValue }}
  <input type="text" v-model="inputValue">
</template>
<script setup lang="ts" name="father">
import useInputRef from './hook/useInputRef';
let { inputValue } = useInputRef('默认')
</script>

teleport

什么是teleport?

teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

示例:

<template>
  <button @click="isShow = true">提示</button>
  <teleport to="body">
    <div v-if="isShow" class="notice">
      这是一段提示文本!
      <button @click="isShow = false">关闭</button>
    </div>
  </teleport>
</template>
<script setup lang="ts" name="father">
import { ref } from 'vue';
let isShow = ref(false)
</script>
<style scoped>
.notice{
  width: 200px;
  height: 160px;
  border-radius: 12px;
  padding: 20px;
  border: 2px solid #ccc;
  background-color: #eee;
  margin-top: 10vh;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
}
</style>

alt text 可以看到我们的提示是在body的最下面。而不是在我们的组件中。在<teleport to="body">中指定的位置,也可以是选择器名字,例如#app 等。

suspense

<Suspense> is an experimental feature and its API will likely change.

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
  • 使用步骤:
    • 异步引入组件
    • 使用Suspense包裹组件,并配置好defaultfallback

示例:

父组件

<template>
  <div class="father">
    I am father
    <!--  <Son></Son> -->
    <suspense>
      <template #default>
        <Son></Son>
      </template>
      <template #fallback>
        <div>Loading...</div>
      </template>
    </suspense>
  </div>
</template>

子组件

<template>
  <h1>I am Son</h1>
  {{ msg }}
</template>
<script setup lang="ts" name="son">
import { ref } from 'vue';
let msg = ref('')
// 模拟网络请求
await new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, World!');
  }, 2000);
}).then((data) => {
  msg.value = data as string
})
</script>

如上图,当我们直接在子组件中的setup函数中使用异步网络请求时并添加await时,会导致子组件整体未被加载,控制台报警告。

 [Vue warn]: Component <son>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered. 
  at <Son> 
  at <Father>

对于这种情况,所以我们需要使用Suspense包裹组件,并配置好defaultfallbackSuspense实际上是基于插槽 Slot 实现的, default是我们加载组件的默认出口。fallback则是我们组件异步请求过程中的加载提示出口。