响应式API:理解并掌握ref

一、响应式数据

首先,我们需要理解响应式基础,在 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 传递给函数,同时保留对最新值和响应式连接的访问。当将复杂的逻辑重构为可重用的代码时,这将非常有用。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值