目录
一、响应式数据
首先,我们需要理解响应式基础,在 Vue 3 中,const 声明的变量确实不能直接重新赋值,这是因为 const 声明的是一个不可重新绑定的常量。然而,在 Vue 3 中,响应式数据的修改并不涉及重新绑定,而是修改数据本身的内容或引用的值。
const:
不可重新赋值: const 声明的变量在赋值后不能再被重新赋值。
可修改引用内容: 如果 const 绑定的是一个对象、数组或者 ref,你可以修改它们的属性或值,但不能把这个变量重新绑定到另一个对象或引用。
Vue 3 中的响应式数据:
ref: 创建一个包含单个值的响应式引用,通过 .value 访问或修改该值。
reactive: 创建一个包含多个属性的响应式对象,可以直接修改这些属性。
二、ref的认识
ref的作用与特性:
1.创建响应式数据:
- ref 主要用于声明一个可响应的数据,当这个数据发生变化时,Vue 会自动更新使用该数据的视图。
- 它适用于基本类型的数据,如字符串、数字、布尔值,甚至是复杂的对象和数组。
2.访问和修改数据:
当你使用 ref 创建一个响应式数据时,Vue 会将这个数据包装在一个对象中,这个对象有一个 .value 属性,你可以通过这个属性访问和修改数据。
import { ref } from 'vue';
const count = ref(0); // 创建一个响应式的 count 变量
console.log(count.value); // 访问 count 的值
count.value++; // 修改 count 的值
1.声明响应式状态
ref 是 Vue 3 中一个重要的响应式 API,用于创建一个响应式的引用,可以是基本类型的数据(如字符串、数字、布尔值)或对象类型的数据。它是 Vue 3 组合式 API(Composition API)的一部分,为开发者提供了更强大的工具来管理组件的状态和响应式数据。
在组合式api中,推荐使用ref()函数来声明响应式状态
import { ref } from 'vue'
const count = ref(0)
ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
2.深入了解:为ref()标准类型
ref 会根据初始化时的值推导其类型:
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
当我们使用 TypeScript 时,ref 的默认类型推断可能无法满足复杂场景中的需求。通过显式地为 ref 注入类型,你可以明确指定 ref 的值的类型,从而提高代码的类型安全性和可读性。
}
!!!一定要通过.value然后在访问
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
//year定义为可以是number或string类型的ref
year.value = 2020 // 成功!
或者,在调用 ref() 时传入一个泛型参数,来覆盖默认的推导行为:
// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
//使用ref处理dom引用
const inputRef = ref<HTMLInputElement | null>(null);
//使用ref对复杂数据结构进行类型注入
interface Todo {
id: number;
text: string;
completed: boolean;
}
const todos = ref<Todo[]>([]); // 定义一个响应式的 Todo 列表
function addTodo(text: string) {
todos.value.push({
id: Date.now(),
text,
completed: false,
});
}
3.深层次响应ref()和浅层shallow ref()
Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.value.nested.count++
obj.value.arr.push('baz')
}
shallowRef() 浅层作用形式:
和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
const state = shallowRef({ count: 1 })
// 不会触发更改,此时直接对count属性修改是没有作用的。
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
浅层响应的作用:
Vue 的响应性系统默认是深度的。虽然这让状态管理变得更直观,但在数据量巨大时,深度响应性也会导致不小的性能负担,因为每个属性访问都将触发代理的依赖追踪。Vue 确实也为此提供了一种解决方案,通过使用 shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新。
1.triggerRef()
使用trig1gerRef(),强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
const shallow = shallowRef({
greet: 'Hello, world'
})
// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
console.log(shallow.value.greet)
})
// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'
// 打印 "Hello, universe"
triggerRef(shallow)
2.customRef()
用于创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}
customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。
创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:
import { customRef } from 'vue'
//value: 初始值。
//delay: 防抖的延迟时间,默认值为 200 毫秒。
export function useDebouncedRef(value, delay = 200) {
let timeout
//接受一个工厂函数,工厂函数的两个参数是 track 和 trigger:
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
//设置防抖
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
这个代码片段定义了一个 useDebouncedRef 函数,该函数返回一个带有防抖特性的自定义 ref 对象。防抖的意思是,当快速、多次设置一个值时,只有最后一次设置会生效,并且会在指定的延迟时间后触发更新。Vue 3 的 customRef 函数使得我们可以完全控制 ref 对象的 get 和 set 操作,并通过 track 和 trigger 函数手动管理依赖追踪和触发更新。
track: 用于手动追踪对 ref 的依赖,通常在 get 方法中调用。
trigger: 用于手动触发依赖的更新,通常在 set 方法中调用。
在 set 方法中实现了防抖逻辑:每次设置值时,会先清除之前的 timeout,然后设置一个新的定时器。只有在延迟时间之后,newValue 才会被赋值给 value,并且通过 trigger() 触发相关依赖更新。
示例:
假设我们有一个输入框,每次输入都需要发送一个 API 请求,但我们希望在用户停止输入后的 500 毫秒后才发送请求:
<template>
<input v-model="searchQuery" placeholder="Type to search..." />
</template>
<script setup>
import { useDebouncedRef } from './useDebouncedRef'
import { ref, watch } from 'vue'
const searchQuery = useDebouncedRef('', 500)
watch(searchQuery, (newQuery) => {
// 触发API请求
console.log('Search for:', newQuery)
})
</script>
三、总结:为什么要使用ref?
你可能会好奇:为什么我们需要使用带有 .value 的 ref,而不是普通的变量?为了解释这一点,我们需要简单地讨论一下 Vue 的响应式系统是如何工作的。
当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。
在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。
该 .value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。
// 伪代码,不是真正的实现
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
另一个 ref 的好处是,与普通变量不同,你可以将 ref 传递给函数,同时保留对最新值和响应式连接的访问。当将复杂的逻辑重构为可重用的代码时,这将非常有用。