vue3.0(四) ref全家桶以及响应的 源码分析

1 ref

> 在Vue3中,ref成为了一个全家桶,除了用于创建响应式数据之外,还可以用于引用DOM元素、组件实例和其他对象。以下是ref的具体用法:

1.1 ref() 是什么

ref() 是一个函数;
ref() 在组合式 API 中,推荐使用 ref()函数用来声明响应式的状态(就是来声明变量的)
ref() 函数声明的变量,是响应式的,变量的值改变之后,页面中会自动重新渲染。

1.2 ref() 特点

1.ref() 可以声明基础类型的变量,也可以声明复杂类型的变量;
如 基本的 number 类型、string类型,以及 json对象类型都可以进行声明;
2.ref() 声明的变量,是深度响应式的;
比如一个变量是json类型的,会有多层的嵌套,那么深层嵌套的属性值发生改变时,该变量同样是响应式的;
3.ref() 声明的变量 在 script 脚本中使用时,需要添加一个 [.value] 属性才能取到对应的值;
如 : 声明变量: const a = ref(100);
使用变量: console.log(a.value); // 添加 .value 才可以真正访问到变量的值
4.ref() 声明的变量 在template 模板中,可以直接使用,无需使用 [.value] 属性,这是因为 vue3 在渲染页面时进行了自动的解包;
如 :声明变量: const a = ref(100);
模板中使用变量 :<div>{{ a }}</div>

1.3 创建响应式数据

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}

1.4 引用DOM元素

<template>
  <div ref="container"></div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({
	setup() {
		const container = ref(null) // 注意:此处的变量名必须和标签上的属性名一致
		onMounted(() => {
		  console.log(container.value) // 输出 <div></div>
		})
		return {
			container,
		}
	}
})
</script>
<style scoped>
</style>

使用ref函数来创建一个container引用,然后在模板中使用ref="container"来将这个引用绑定到一个<div>元素上。在setup函数中,我们使用onMounted钩子来访问这个引用的值。

1.5 深层响应性

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:

<template>
  <button @click="mutateDeeply">修改数据</button>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
  setup () {
    const obj = ref({
      nested: { count: 0 },
      arr: ['foo', 'bar']
    })
    function mutateDeeply () {
      // 以下都会按照期望工作
      obj.value.nested.count++
      obj.value.arr.push('baz')
      console.log(obj)
    }
    return {
      mutateDeeply
    }
  }
})
</script>

上述代码console.log输出
非原始值将通过 reactive() 转换为响应式代理,该函数以后文章会有所介绍。
也可以通过 shallow ref 来放弃深层响应性。对于浅层 ref,只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。

1.6 DOM 更新时机

当修改了响应式状态时,DOM 会被自动更新。
但是需要注意的是,DOM 更新不是同步的。
Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:

import { nextTick } from 'vue'
async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}

1.7 ref源码

function ref(value) {
  return createRef(value, false);
}
// 传递进来的本来就是ref,那么就可以直接返回这个值,如果不是ref,那就返回一个RefImpl的实例,
function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}
class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow;
    this.dep = void 0;
    this.__v_isRef = true;
    this._rawValue = __v_isShallow ? value : toRaw(value); // 还记得ref的第二个参数是false,可以理解成不是浅响应,这时候 _rawValue 需要取到原始值(对象)
    this._value = __v_isShallow ? value : toReactive(value); // 同理,只不过这个 _value 取得是Proxy代理的对象
  }
  get value() {
    trackRefValue(this);
    return this._value; // 返回当前的_value
  }
  set value(newVal) {
    // 修改
    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
    newVal = useDirectValue ? newVal : toRaw(newVal);
    // newVal 取到 原始值 
    if (hasChanged(newVal, this._rawValue)) {
      // 当前值和旧数据做对比,
      this._rawValue = newVal;
      this._value = useDirectValue ? newVal : toReactive(newVal);
      triggerRefValue(this, 4, newVal);
    }
  }
}

2 isRef

检查某个值是否为 ref。

2.1 isRef运用

类型

function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
let foo: unknown
if (isRef(foo)) {
  // foo 的类型被收窄为了 Ref<unknown>
  foo.value
}
 const num = '1'
 console.log(isRef(num)) // false

foo.value代码行报错
请注意,返回值是一个类型判定 (type predicate),这意味着 isRef 可以被用作类型守卫:

2.2 isRef源码

// // 判断一个对象是否为ref对象
function isRef(r) {
  return !!(r && r.__v_isRef === true);
}

3 unref

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。
类型

function unref<T>(ref: T | Ref<T>): T

3.1 unref运用

  const num = 1
    const mun1 = ref<number>(2)
    function useFoo (x: number | Ref<number>) {
      const wrapped = x
      const unwrapped = unref(x) // 会将ref转化为不是ref响应
      console.log(wrapped, unwrapped)
      // unwrapped 现在保证为 number 类型
    }

上述代码console.log输出

3.2 unref源码

function unref(ref2) {
  return isRef(ref2) ? ref2.value : ref2; // 判断是不是ref,若是返回值,若不是返回当前值
}

4 shallowRef

ref() 的浅层作用形式。
类型

function shallowRef<T>(value: T): ShallowRef<T>
interface ShallowRef<T> {
  value: T
}

4.1 shallowRef运用

和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。

  const state = shallowRef({ count: 1, num: 3 })
   // 不会触发更改
   state.value.count = 2
   // 会触发更改
   state.value = { count: 3, num: 4 }
   console.log(state)

console.log的输出

4.2 shallowRef源码

function shallowRef(value) {
  // 与ref不同的是,第二个参数传的是true, 如果你更改了 target 的属性,vue 不会触发任何副作用或计算属性的重新计算。
  // 这个参数或将其设置为 false,那么 createRef 将创建一个深度引用,即 target 的所有属性都将被转换为响应式的。
  return createRef(value, true);
}
function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}
class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow;
    this.dep = void 0;
    this.__v_isRef = true;
    this._rawValue = __v_isShallow ? value : toRaw(value);
    this._value = __v_isShallow ? value : toReactive(value);
  }
  get value() {
    trackRefValue(this);
    return this._value;
  }
  set value(newVal) {
    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
    newVal = useDirectValue ? newVal : toRaw(newVal);
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal;
      this._value = useDirectValue ? newVal : toReactive(newVal);
      triggerRefValue(this, 4, newVal);
    }
  }
}

5 triggerRef

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
类型

function triggerRef(ref: ShallowRef): void

5.1 triggerRef运用

    const shallow = shallowRef({
      greet: 'Hello, world'
    })

    // 触发该副作用第一次应该会打印 "Hello, world"
    watchEffect(() => {
      console.log(shallow.value.greet)
    })

    // 这次变更不应触发副作用,因为这个 ref 是浅层的
    shallow.value.greet = 'Hello, universe'

    // 打印 "Hello, universe"
    triggerRef(shallow)

5.2 triggerRef源码

function triggerRef(ref2) {
  triggerRefValue(ref2, 4, ref2.value );
}
function triggerRefValue(ref2, dirtyLevel = 4, newVal) {
  ref2 = toRaw(ref2);
  const dep = ref2.dep;
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      {
        target: ref2,
        type: "set",
        key: "value",
        newValue: newVal
      } 
    );
  }
}
function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {
  var _a;
  pauseScheduling();
  for (const effect2 of dep.keys()) {
    let tracking;
    if (effect2._dirtyLevel < dirtyLevel && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {
      effect2._shouldSchedule || (effect2._shouldSchedule = effect2._dirtyLevel === 0);
      effect2._dirtyLevel = dirtyLevel;
    }
    if (effect2._shouldSchedule && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {
      {
        (_a = effect2.onTrigger) == null ? void 0 : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));
      }
      effect2.trigger();
      if ((!effect2._runnings || effect2.allowRecurse) && effect2._dirtyLevel !== 2) {
        effect2._shouldSchedule = false;
        if (effect2.scheduler) {
          queueEffectSchedulers.push(effect2.scheduler);
        }
      }
    }
  }
  resetScheduling();
}

6 customRef

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
如创建一个防抖ref,即只在最近一次set调用后的一段固定间隔后再调用:
类型

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

6.1 customRef运用

创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

注意:customRef 函数的参数是一个函数,这个函数接收两个参数,分别是 track 和 trigger 函数,它们用于追踪依赖和触发依赖更新。

6.2 triggerRef源码

function customRef(factory) {
  return new CustomRefImpl(factory);
}
class CustomRefImpl {
  constructor(factory) {
    this.dep = void 0;
    this.__v_isRef = true;
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    );
    this._get = get;
    this._set = set;
  }
  get value() {
    return this._get();
  }
  set value(newVal) {
    this._set(newVal);
  }
}

7 Ref 泛型写法

vue 3 中 Ref 也被作为一个类型使用,可以接收传入的类型

<script setup lang="ts">
import type { Ref } from "vue";
import { ref } from "vue";

type P = {
  num?: number;
};

// 泛型写法一:
// 适合用于属性较少时
// const count = ref<P>({ num: 18 });

// 泛型写法二:
// 适合用于属性较多时,或者类型较复杂的时候
const count: Ref<P> = ref({ num: 28 });

// 普通写法:
// 依靠类型推导实现
// const count = ref({ num: 18 });
</script>
  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值