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

文章目录

  • 1 ref
    • 1.1 ref() 是什么
    • 1.2 ref() 特点
    • 1.3 创建响应式数据
    • 1.4 引用DOM元素
    • 1.5 深层响应性
    • 1.6 DOM 更新时机
    • 1.7 ref源码
  • 2 isRef
    • 2.1 isRef运用
    • 2.2 isRef源码
  • 3 unref
    • 3.1 unref运用
    • 3.2 unref源码
  • 4 shallowRef
    • 4.1 shallowRef运用
    • 4.2 shallowRef源码
  • 5 triggerRef
    • 5.1 triggerRef运用
    • 5.2 triggerRef源码
  • 6 customRef
    • 6.1 customRef运用
    • 6.2 triggerRef源码
  • 7 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580757.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud系列(10)--Eureka集群原理及搭建

前言&#xff1a;当注册中心只有一个&#xff0c;而且当这个注册中心宕机了&#xff0c;就会导致整个服务环境不可用&#xff0c;所以我们需要搭建Eureka注册中心集群来实现负载均衡故障容错 Eureka架构原理图 1、Eureka集群原理 2、创建Eureka Server端服务注册中心模块 (1)在…

ios微信小程序禁用下拉上拉

第一步&#xff1a; page.json配置页面的"navigationStyle":"custom"属性&#xff0c;禁止页面滑动 "navigationStyle":"custom" 第二步&#xff1a; 页面里面使用scroll-view包裹内容&#xff0c;内容可以内部滑动 <view class&…

LLaMA 3:大模型之战的新序幕

作者 | 符尧 OneFlow编译 翻译&#xff5c;杨婷、宛子琳、张雪聃 本文要点概览&#xff1a; 文本数据的扩展可能已经达到了极限&#xff0c;因为易于获取的网络文本资源&#xff08;如Common Crawl、GitHub、ArXiv等&#xff09;已基本被充分利用。 尽管如此&#xff0c;通过更…

Redis底层数据结构之ZSkipList

目录 一、概述二、ZSkipList结构三、和平衡树和哈希表的对比 redis底层数据结构已完结&#x1f44f;&#x1f44f;&#x1f44f;&#xff1a; ☑️redis底层数据结构之SDS☑️redis底层数据结构之ziplist☑️redis底层数据结构之quicklist☑️redis底层数据结构之Dict☑️redis…

[Diffusion Model 笔记]Score based

目录 概述方法怎么估计score&#xff08;估计噪声就是估计score&#xff09;怎么采样&#xff08;给原始数据加噪声&#xff0c;早期大后来变小&#xff09;inpainting &#xff08;来自补充材料&#xff09;还没有细究的地方&#xff1a; 概述 本文是观看以下视频的笔记&…

使用JMeter模拟设备通过MQTT发送数据

需求&#xff1a; 需要一个工具能够支持MQTT协议发送各种不同的数据。 目的&#xff1a; 模拟小型温室设备反馈&#xff0c;搭建一个测试环境&#xff0c;根据测试的数据显示硬件的状态和数值。 工具&#xff1a;JMeter 环境&#xff1a;需要配置Java运行环境。 操作步骤&a…

机器人操作系统ROS2学习—编译工作空间colcon build报错问题

在ROS2中&#xff0c;工作空间创建完成后&#xff0c;会经常需要编译工作空间。在工作空间dev_ws 下打开一个终端&#xff0c;通过指令Colcon build来编译工作空间。 1、这个过程有可能会出现如下错误: "colconbuild:Duplicate package names not supported" 根据…

阅读笔记——《BLEEM: Packet Sequence Oriented Fuzzing for Protocol Implementations》

【参考文献】Zhengxiong Luo, Junze Yu, Feilong Zuo, Jianzhong Liu, Yu Jiang, Ting Chen, Abhik Roychoudhury, and Jiaguang Sun. Bleem: Packet sequence oriented fuzzing for protocol implementations. In 32nd USENIX Security Symposium (USENIX Security 23), pages…

西湖大学赵世钰老师【强化学习的数学原理】学习笔记2节

强化学习的数学原理是由西湖大学赵世钰老师带来的关于RL理论方面的详细课程&#xff0c;本课程深入浅出地介绍了RL的基础原理&#xff0c;前置技能只需要基础的编程能力、概率论以及一部分的高等数学&#xff0c;你听完之后会在大脑里面清晰的勾勒出RL公式推导链条中的每一个部…

github two-factor authentication是个啥?

最近在逛github时&#xff0c;总是时不时会弹出一下界面&#xff0c;很烦 看到红框里的文字&#xff0c;这明显是强制要求做这个认证&#xff0c;如果不认证4天后账号将不可访问&#xff0c;所以今天花点时间看看怎么做这个认证&#xff0c;点“Enable 2FA now”进入这个界面&a…

Vue后台系统demo小计

创建项目 1.报错 Error: command failed: npm install --loglevel error --legacy-peer-deps 措施1&#xff1a;node.js文件夹属性 》高级 》选择第一个允许 Users(XXX\Users) &#xff08;对我无用&#xff09; 措施2&#xff1a;PowerShell(以管理员身份运行) 》 cd 想存…

java基础之java容器-Collection,Map

java容器 java容器分类一. Collection1. List①. ArrayList② . LinkedList③ . Vector 2. Queue队列①. LinkedList②. PriorityQueue 3. Set集合①. HashSet②. TreeSet 二. Map1. HashMap2.TreeMap3. Hashtable java容器分类 java容器分为两大类&#xff0c;分别是Collecti…

VMWare下建的CentOS7 扩容

记录一下扩容过程中踩过的坑 背景&#xff1a;一年半以前私有化部署了一个gitLab服务&#xff0c;当时只分配了30G的磁盘容量&#xff0c;这两天小伙伴总是反馈gitLab登不上。排查发现是因为磁盘满了 然后就开始了磁盘扩容之旅 各种 vgs\pv\pvdisplay\lv\lvm 等等都没用 一下…

ChatGPT记忆功能终于上线了, OpenAI 官方:用得越久越聪明!

原文 ChatGPT记忆功能终于上线了, OpenAI 官方&#xff1a;用得越久越聪明&#xff01; Aitrainee | 公众号&#xff1a;AI进修生 &#x1f31f; 记得今年2月份OpenAI发布过ChatGPT上线记忆功能的消息&#xff0c;我记得当时还弹出过这个窗口给我&#xff0c;但是仅仅体验了几…

【书生浦语第二期实战营学习笔记作业(六)】

课程文档&#xff1a;https://github.com/InternLM/Tutorial/tree/camp2/agent 课程作业&#xff1a;https://github.com/InternLM/Tutorial/blob/camp2/agent/homework.md Lagent & AgentLego 智能体应用搭建 1、Agent 理论1.1 为什么要有智能体1.2 什么是智能体1.3 智能体…

【兼职宝典】七大靠谱手机兼职副业平台,让你乐在其中,轻松实现财务自由!

数字化时代已经到来&#xff0c;互联网的普及与技术的飞速发展让越来越多的人开始关注兼职工作&#xff0c;以此增加收入、锻炼能力或追求兴趣爱好。本文将为您详细解读几种热门的兼职方式&#xff0c;助您找到最适合自己的兼职岗位。 一&#xff0c;自媒体运营&#xff1a;创…

Java 循环语句

文章目录 Java 循环语句一&#xff0c;for 循环1. for 循环结构2. for 循环案例: 输出5行HelloWord3. for 循环案例: 写出输出的结果 (格式多样性)4. for 循环案例: 遍历100以内的偶数。并获取偶数的个数&#xff0c;获取所有的偶数的和5. for 循环案例: 输出所有的水仙花数6. …

Jmeter中http请求时加HTTP Cookie管理器,cookie不生效问题

只是想加个cookie&#xff0c;就新建了cookie管理器&#xff0c;用的都是默认的&#xff0c;然后跑到怀疑自己的jmeter是不是出问题了。 还好没卸载重装。只是把策略改成netscape就好了。

【代码随想录刷题记录】LeetCode844比较含退格的字符

题目地址 1. 思路 1.1 基本思路 拿到这个题&#xff0c;我们要单独写一个函数去将退格后的字符串结果返回出来&#xff08;生成退格后的真实的字符串&#xff09;&#xff0c;我还是想魔改 O ( n ) O(n) O(n)时间复杂度的删除数组元素的算法&#xff1a;【代码随想录刷题记录…

GoLand 2021.1.3 下载与安装

当前环境&#xff1a;Windows 8.1 x64 1 浏览器打开网站 https://www.jetbrains.com/go/download/other.html 找到 2021.1.3 版本。 2 解压 goland-2021.1.3.win.zip 到 goland-2021.1.3.win。 3 打开 bin 目录下的 goland64.exe&#xff0c;选择 Evaluate for free -- Evalu…
最新文章