关于vue的ref

ref,isRef

在 Vue 3 中,ref 是一个函数 ,用于创建响应式的数据。它接受一个参数作为初始值,并返回一个包装过的响应式对象。

ref跟vue2的以下这段代码是一个意思,也就是创建响应式数据:

Vue 复制代码
<template>
  <div>

  </div>
</template>

<script setup lang='ts'>
export default{
  data(){
    return {
      age:18
    }
  }
}
</script>
<style scoped>

</style>

使用ref去写则是这样的:

Vue 复制代码
<template>
  <div>
    ref {{ rarrot }}
  </div>
  <button @click="change">点击我</button>
</template>

<script setup lang='ts'>
import { ref, isRef } from 'vue'

const rarrot = ref({ name: "rarrot" })

const change = () => {
  // 通过value去改变rarrot的值
  rarrot.value.name='nihao'
  console.log("🚀", rarrot)

  // 用于检测是否为响应式对象
  console.log(isRef(rarrot))
}

</script>
<style scoped></style> 

shallowRef,triggerRef

在 Vue 3 中,shallowRef 和 ref 区别如下:

响应式对象的处理方式 性能影响
ref ref 创建的响应式对象会对其值进行深层次 的响应式转换。 当访问或修改 ref 返回的对象时Vue 会自动追踪依赖并进行视图更新 由于 ref 对值进行深层次的响应式转换,当值是复杂对象或数组时,会有一定的性能开销 因为 Vue 需要递归地追踪其所有属性的变化
shallowRef shallowRef 创建的响应式对象只对其值进行浅层次 的响应式转换。 当访问或修改 shallowRef 返回的对象时,Vue 只会追踪该对象本身的变化,而不会深度追踪其内部属性的变化。 由于 shallowRef 只对值进行浅层次的响应式转换,对于复杂对象或数组,它的性能开销较低 。因为 Vue 只会追踪对象本身的变化,而不会深度追踪其内部属性的变化。

示例:

通过triggerRef(rarrot2)rarrot2.value={ name:'nihao2' }这两种方式会更新收集的依赖,也就是视图的更新,看代码:

Vue 复制代码
<template>
  <div>
    shallowRef {{ rarrot2 }}
  </div>
  <button @click="change">点击我</button>
</template>

<script setup lang='ts'>
import { shallowRef, triggerRef } from 'vue'
// ref shallowRef
// ref深层次   shallowRef浅层次的响应
// shallowRef跟ref混写的话,改变ref,shallowRef的页面也会被修改,原因是调用了triggerRef()

const rarrot2 = shallowRef({ name: 'rarrot2' })

const change = () => {
  // shallowRef以下这么修改会导致对象被修改,但是页面上没有被修改
  rarrot2.value.name = 'nihao2'
  triggerRef(rarrot2)//triggerRef会强制更新收集的依赖
  console.log("🚀  rarrot2", rarrot2)

// shallowRef用以下这种方式对象和页面上都会被修改
  // rarrot2.value={
  //   name:'nihao2'
  // }
  // console.log("🚀  rarrot2", rarrot2)
}
</script>
<style scoped></style> 

下图将triggerRef(rarrot2)rarrot2.value={ name:'nihao2' }都注释了:

customRef

在 Vue 3 中,customRef 是一个函数,用于创建自定义的响应式数据。它接受一个工厂函数作为参数,该工厂函数负责定义自定义的响应式行为,并返回一个包装过的响应式对象。

通过使用 customRef,你可以完全控制响应式数据的依赖追踪和触发更新的逻辑。这对于实现一些特定的响应式行为或处理复杂的数据情况非常有用。看代码:

Vue 复制代码
<template>
  <div>
    customRef {{ rarrot3 }}
  </div>
  <button @click="change">点击我</button>
</template>

<script setup lang='ts'>
import { isRef, customRef } from 'vue'

function myRef<T>(value: T) {
  let timer:any
  // track 用于追踪依赖,trigger 用于触发更新
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer=setTimeout(()=>{
          console.log('触发了')
          value=newVal
          timer=null;
          trigger()
        },500)
        
      }
    }
  })
}

// 对其进行赋值时,会调用customRef的get()方法去收集这个依赖
const rarrot3=myRef<string>('rarrot3')
console.log(isRef(rarrot3))//true
console.log(rarrot3)

const change = () => {
  //.value会调用customRef的set()方法去更新收集的这个依赖
  rarrot3.value='nihao3'
}
</script>
<style scoped></style> 

以上代码中使用了一个简单的防抖,防止用户过快点击,导致卡顿,防抖会使得用户在过快点击时不会触发定时器,在停止点击后才触发,以下为代码解释:

这段代码的作用是在修改 rarrot3 的值后,延迟500毫秒才触发更新。这是通过在 set() 方法中使用 setTimeout() 来实现的。在 set() 方法中,清除之前的定时器(如果有的话),然后设置一个新的定时器来延迟更新。当定时器触发时,就将新的值赋给 value,并通过调用 trigger() 方法来触发更新。

ref 小妙招

我们在使用ref后,想要打印返回的响应式对象,但是给了一堆属性,如下图:

所以可以进行以下设置:

设置之后打印的效果就变得很简洁,如下图所示:

用ref操作DOM

看代码:

Vue 复制代码
<template>
  <div ref="domm">
    我是domm
  </div>
  <button @click="change">点击我</button>
</template>

<script setup lang='ts'>
import { ref } from 'vue'

const domm=ref<HTMLDivElement>()

const change = () => {
  // ?. 是可选链操作符(Optional Chaining Operator)
  // 在访问对象的属性或方法之前,需要先确保对象本身不为 null 或 undefined
  // 如果对象为 null 或 undefined,直接访问其属性或方法将会导致 TypeError 错误。
  console.log(domm.value?.innerText)

}


</script>
<style scoped></style> 

从源码解释ref和shallowRef的区别

ref的创建过程

先说一下ref的创建过程:

typescript 复制代码
/**
 * 获取一个内部值并返回一个reactive(响应)和mutable(可变)的 ref 对象
 * 有一个指向内部值的属性`.value`
 *
 * @param value - The object to wrap in the ref.
 * @see {@link https://vuejs.org/api/reactivity-core.html#ref}
 */
export function ref<T extends Ref>(value: T): T
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value, false)
}

以上运用函数的重载,以适应多种传入的类型。

上面代码中调用createRef(value, false),value为用户传入的值,代码如下:

typescript 复制代码
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

先判断了rawValue是否为响应式对象,是就直接返回rawValue,否则调用RefImpl(rawValue, shallow)创建ref对象。

记住,这里的shallow传入为false

上面代码中不是ref对象就调用了RefImpl(rawValue, shallow),这里是将rawValueshallow传入到构造方法中,代码如下:

typescript 复制代码
class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    // 判断是否为浅响应式对象,shallowRef传入的__v_isShallow为true,
    // isShallow(newVal)判断传入的值是否为浅层响应式,
    // isReadonly(newVal)判断传入的值是否为只读响应式对象
    // 只要三个条件中有一个为true,useDirectValue就为true
    const useDirectValue =this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
       // 当useDirectValue为false,也就是为深层次响应对象时,就将其转换为原始对象
    newVal = useDirectValue ? newVal : toRaw(newVal)
       // 这里比较原始对象
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

以上代码先定义私有的_value_rawValue,这个类中的构造方法的参数__v_isShallow前面传入为false ,所以又会调用toReactive(value)

toReactive(value)的代码如下:

typescript 复制代码
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

以上这段代码就是说判断value是不是一个引用类型,是的话就会调用reactive(value),这个方法可以用来将普通对象转换为响应式对象,也就是说当对象的属性发生变化时,Vue 可以检测到这些变化并自动更新相关的视图。

shallowRef的创建过程

typescript 复制代码
declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
/**
 * Shallow version of {@link ref()}.
 *
 * @example
 * ```js
 * const state = shallowRef({ count: 1 })
 *
 * // 这不会触发响应式系统的变化
 * state.value.count = 2
 *
 * // 这会触发响应式系统的变化
 * state.value = { count: 2 }
 * ```
 *
 * @param value - The "inner value" for the shallow ref.
 * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
 */
export function shallowRef<T extends object>(
  value: T
): T extends Ref ? T : ShallowRef<T>
export function shallowRef<T>(value: T): ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

这一段跟上面ref的同理了,只不过这里传入到createRef的shallow为true。

因为传入为true,所以在构造方法中,就会直接返回value并赋值给RefImpl类的_value私有属性,

  1. 在读取时会调用get()方法,里面的trackRefValue(this)会收集依赖
  2. 设置值时会调用set(newVal)方法,这里的set(newVal)会先判断值是否改变,有改变的话将新值传递给 _rawValue
  3. 如果之前已经使用了浅层模式或者新值是浅层响应式或只读响应式,则直接使用新值,否则使用 toReactive(newVal) 函数将新值转换为响应式对象。
  4. 最后再调用 triggerRefValue(this,newValue) 函数,触发对该 customRef 对象的依赖更新。

之所以ref跟shallowRef同时使用,都会进行视图更新,因为ref会调用triggerRefValue(this, newVal),就会把收集的依赖都进行更新。

疑问

  1. 为什么深层次响应式对象要使用toRaw()转换为原始对象进行比较,而浅层次响应式对象不用转换?

看下方。

  1. 还有一个疑问就是,以下会更新视图的变化是怎么一回事?
typescript 复制代码
const rarrot2 = shallowRef({ name: 'rarrot2' })
rarrot2.value={ name:'nihao2' }

而以下这样又不会更新视图

typescript 复制代码
const rarrot2 = shallowRef({ name: 'rarrot2' }) 
rarrot2.value.name='nihao2'

解答

其实两个问题的答案都在set()方法里,看源代码:

typescript 复制代码
set value(newVal) {

    ...

  // 当useDirectValue为true,也就是为深层次响应对象时,就将其转换为原始对象

    newVal = useDirectValue ? newVal : toRaw(newVal)

    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
}
typescript 复制代码
export const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)

① 这里深层次对象需要用toRaw()转换为原始对象进行比较,原因为直接比较的话比较的是引用,那就没有变化,需要比较的是属性值。

② 这里直接比较浅层次响应式对象的话,比较的是引用,新值的引用和旧值的引用,在只改变属性的情况下,还是一样的。

但是如果通过rarrot2.value={ name:'nihao2' },则改变了引用,所以就会调用if里面的条件了。

相关推荐
Ares码农人生19 分钟前
React 前端框架简介
前端·react.js·前端框架
小汤猿人类20 分钟前
nacos-gateway动态路由
java·前端·gateway
GISer_Jing20 分钟前
前端经典面试合集(二)——Vue/React/Node/工程化工具/计算机网络
前端·vue.js·react.js·node.js
GesLuck1 小时前
C#控件开发4—仪表盘
前端·经验分享·c#
小林爱1 小时前
【Compose multiplatform教程14】【组件】LazyColumn组件
android·前端·kotlin·android studio·框架·多平台
过往记忆7 小时前
告别 Shuffle!深入探索 Spark 的 SPJ 技术
大数据·前端·分布式·ajax·spark
高兴蛋炒饭8 小时前
RouYi-Vue框架,环境搭建以及使用
前端·javascript·vue.js
m0_748240449 小时前
《通义千问AI落地—中》:前端实现
前端·人工智能·状态模式
ᥬ 小月亮9 小时前
Vue中接入萤石等直播视频(更新中ing)
前端·javascript·vue.js
呜呼~2251410 小时前
前后端数据交互
java·vue.js·spring boot·前端框架·intellij-idea·交互·css3