customRef 与 ref

news2024/11/23 15:36:38

ref() 我们已经很熟悉了,就是用来定义响应式数据的,其底层原理还是通过 Object.defineprotpty 中的 get 实现收集依赖( trackRefValue 函数收集),通过 set 实现分发依赖通知更新( triggerRefValue 函数分发 )。我们看看 ref 的源码就知道了

class RefImpl {
  private _value: any;    // 用来存储响应值
  private _rawValue: any;    // 用来存储原始值
  public dep?: Dep = undefined;    // 用来收集分发依赖
  public readonly __v_isRef = true;    //是否只读,暂不考虑
 
  // 接收 new RefImpl() 传递过来的 rawValue 和 shallow  
  constructor(value, public readonly __v_isShallow: boolean) {
    // 判断是否需要深层响应,如果不用,直接返回 Value 值,如果需要深层响应,则调用 toRaw 函数解除 value 的响应式,将其转化为原始值,以保证后续的深层响应
    this._rawValue = __v_isShallow ? value : toRaw(value);
 
    // 判断是否需要深层响应,如果不用,则直接返回Value,不做响应式处理。如果需要深层响应,则调用 reactive 函数进行深层响应
    this._value = __v_isShallow ? value : reactive(value);
  }
 
  get value() {
    // 收集依赖
    trackRefValue(this);
 
    // 返回响应式数据
    return this._value;
  }
 
  set value(newVal) {
 
    // 将 newVal 转化为原始值,并于初始原始值比较,若不同,则准备更新数据,渲染页面,分发依赖
    if (hasChanged(toRaw(newVal), this._rawValue)) {
 
      //判断是否需要深层响应,如果不用,直接返回 newVal 值,如果需要深层响应,则调用 toRaw 函数解除 newVal 的响应式,将其转化为原始值,以保证后续的深层响应
      this._rawValue = this.__v_isShallow ? newVal : toRaw(newVal);
 
      // 判断是否需要深层响应,如果不用,则直接返回Value,不做响应式处理。如果需要深层响应,则调用 reactive 函数进行深层响应
      this._value = this.__v_isShallow ? newVal : reactive(newVal);
 
      // 分发依赖,通知更新
      triggerRefValue(this);
    }
  }
}

具体的关于 ref 的使用以及更深层的理解请参考之前的文章 -- ref 函数

那么这个 customRef 函数是用来干啥的呢?

customRef

概念:创建一个自定义的 ref 函数,在其内部显式声明对其依赖追踪和更新触发的控制方式。

前面一句好理解,创建一个自定义的 ref ,其类型是一个函数,函数体内部的逻辑内容自定义。

后面一句就有点绕了,显式声明对其依赖追踪和更新触发的控制方式该怎么理解呢?

我们看看 ref 就知道了,当我们调用 ref 之后,读取数据时,Vue 底层就会自动去在 get 中收集依赖。修改数据时,会自动在 set 中分发依赖。这是不需要我们关心的,我们只需要调用 ref 函数就可以实现了。

但是 customRef 并没有按照 ref 的逻辑去实现,customRef 的处理是:既然你都自定义了,那你就自定义完整一点,依赖收集和分发工作你也自己做了,别去麻烦 Vue 底层在给你适配转化一次。

用法:customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

按照官网的例子我们一点点实现优化:创建一个自定义 ref ,实现防抖。具体效果就是我在 input 框中输入值,延时展示值。

第一步:不延迟,直接同步展示,v-model 双向绑定数据,插值语法展示数据,setup 定义数据

<template>
	<input type="text" v-model="keyword">
	<h3>{{keyword}}</h3>
</template>

<script>
	import {ref} from 'vue'

	export default {
		name:'Demo',
		setup(){
			let keyword = ref('hello') //使用Vue准备好的内置ref
			
			return {
				keyword
			}
		}
	}
</script>

展示效果:

第二步:定义自己的 ref 函数,并且使用它

setup(){
  // let keyword = ref('hello') //使用Vue准备好的内置ref

  // 定义自己的 ref 函数,接收值,并 return 出具体的值,否则返回undefined
  function myRef(value) {
    console.log(value);
    return value
  }

  let keyword = myRef('hello')  // 使用自定义的 ref 函数

  return {
    keyword
  }
}

此时我们发现,当我们使用自定义 ref 函数 时,因为我们并没有对这个数据进行响应式处理,所以页面数据并没有同步更新,这个时候我们就需要用到  customRef 来实现内部的逻辑。

第三步:调用 customRef 实现内部逻辑,按照 customRef() 的使用方法,完善 myRef()

这是 vscode 插件的提示语法,可以看到 customRef() 的完整用法。所以,我们完善一下 myRef()

function myRef(value) {
  return customRef((track,trigger) => {
    return {
      get() {
        // ...
      },
      set() {
        // ...
      }
    }
  })
}

到了这一步是不是就很眼熟了,这不就是 ref() 函数里面的响应式么,取值调 get,修改调 set。按照想法实现一下

function myRef(value) {
  return customRef((track,trigger) => {
    return {
      get() {
        return value
      },
      set(newValue) {
        value = newValue
      }
    }
  })
}

虽然数据发生了变更,但是页面并没有同步更新

这是因为数据只是发生了变更,但是并没有实现依赖追踪和触发更新,这个时候,我们在看看 ref() 的源码。

get value() {
    // 收集依赖
    trackRefValue(this);
 
    // 返回响应式数据
    return this._value;
  }
 
  set value(newVal) {
 
    // 判断逻辑 
    ......
    
    // 更新数据
    this._value = this.__v_isShallow ? newVal : reactive(newVal);
 
      // 分发依赖,通知更新
      triggerRefValue(this);
      
  }

在 ref() 中,在get 中收集依赖,在 set 中分发依赖,按这个模式,我们在 customRef() 中的 get 和 set 中也应该收集或分发。而 customRef 接收的工厂函数接收  track 和 trigger 两个函数作为参数,这两个函数其实就是对应的 ref() 中的 trackRefValue() 和 triggerRefValue() ,于是完善后的代码就成了这样。

function myRef(value) {
  return customRef((track,trigger) => {
    return {
      get() {
        track()    // 先收集依赖,告诉Vue 这个 value 值是需要被追踪的
        return value    // 然后返回被追踪的值,此时Vue底层已经对 value 实现了追踪
      },
      set(newValue) {
        value = newValue    // 先设置值,因为 value 被追踪,所以数据改变时,Vue底层是能监听到
        trigger()    // 然后分发依赖,告诉 Vue 需要更新界面
      }
    }
  })
}

实现的效果

到了这里,其实我们就完成了与第一步同样的效果:不延迟,直接同步展示。

剩下的就是实现防抖了。当数据改变时,我们通过 setTimeout 我们可以实现延迟 500ms 展示值。

set(newValue) {
  setTimeout(() => {
    value = newValue;
    trigger();
  }, 500);
},

 

但是我们发现,当过快输入时,值出现了诡异的变动,会突然卡一下,这是因为,每次改变数据时,都会开启一个定时器,但是定时器却并没有清除,这就导致累计了多个定时器才会出现这种情况。 

按照标准防抖的流程,那就是在一定的时间内只执行一次,如果此时重复触发,则重新开始计时。代码改进之后展示

function myRef(value) {
  return customRef((track, trigger) => {
    let timer    // 定义变量,接收定时器
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timer);    // 每次开启定时器之前先清除之前的定时器,防止出现错误

        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, 500);
      },
    };
  });
}

连续快速点击效果:只有在最后一次点击完成,且定时器延迟触发之后,才会展示改变后的值

慢速点击效果:每次点击都等待定时器执行完毕之后再触发下一次动作

到了这里,其实我们就完成了对依赖项跟踪和更新触发进行显式控制。可以看到,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。但是其实我们完全实控制了 track()、trigger() 的使用,包括但不限于在哪使用,是否需要使用等。

问题点

当你将 customRef 作为 prop 传递时,它可能会影响父组件和子组件之间的关系,尤其是在响应式系统的依赖追踪和更新通知方面。

案例代码

// 自定义 ref,没有调用 track()
function useCustomRef(value) {
  return customRef((track, trigger) => ({
    get() {
      track()
      return value;
    },
    set(newValue) {
      value = newValue;
      trigger(); // 触发更新
    }
  }));
}

// 父组件
export default {
  setup() {
    const customValue = useCustomRef('Hello');
    return { customValue };
  },
  template: '<ChildComponent :propValue="customValue" />'
};

// 子组件
export default {
  props: {
    propValue: {
      type: Object,
      required: true
    }
  },
  watch: {
    propValue(newValue) {
      console.log('Prop value updated:', newValue);
    }
  },
  template: '<div>{{ propValue }}</div>'
};

1. 依赖追踪不完整

在Vue 响应式系统中 ,Vue会自动进行依赖追踪。当父组件传递一个 ref 或响应式对象作为 prop 给子组件时,Vue 会追踪这个 prop 的依赖。

但是,customRef 可以自定义依赖追踪逻辑。如果你在 customRefget 方法中没有正确调用 track(),Vue 就无法知道子组件在依赖这个 prop。这意味着,当父组件更新这个 prop 时,子组件可能无法感知到这个变化,因为依赖关系没有被正确建立。

2. 更新通知的不一致

当你在 customRefset 方法中没有正确调用 trigger(),即使 prop 在父组件中被更新,子组件也不会收到更新通知。这会导致子组件的数据与父组件不同步,从而产生 UI 不一致的问题。

3. 异步逻辑导致的延迟

如果 customRef 中包含异步逻辑(例如防抖或节流),这种延迟处理可能会导致子组件在接收 prop 时得到的是过时的数据。这在需要子组件立即响应父组件更新的场景下,可能引发状态不同步的问题。

在上面的例子中,debouncedRef 可能导致子组件在 prop 变更后并未立即更新,而是延迟更新,可能引发父子组件数据状态不同步的问题。

总结

1、customRef的作用: 创建一个自定义的 ref 函数,在其内部显式声明对其依赖追踪和更新触发的控制方式。

2、customRef 接收的工厂函数接收  track 和 trigger 两个函数作为参数,这两个函数其实就是对应的 ref() 中的 trackRefValue() 和 triggerRefValue() ,并返回一个带有 get 和 set 方法的对象。

3、一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

4、当 customRef 作为 prop 传递时,可能会影响父组件和子组件之间的关系,

  • 依赖追踪不完整
  • 更新通知的不一致
  • 异步逻辑导致的延迟

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适合学生党用的充电宝有哪些?四款百元性价比充电宝推荐

在如今这个电子设备不离手的时代&#xff0c;充电宝成为了学生党们的必备好物。无论是在教室、图书馆学习&#xff0c;还是外出游玩&#xff0c;一款可靠的充电宝能够为手机、平板等设备随时补充电量&#xff0c;让你不再为电量焦虑而烦恼。今天&#xff0c;我们就为学生党们精…

AES对称加密算法

1. 简介 AES是一种对称加密算法, 它有3种类型: AES-128: 密钥为128位(16字节)的AES, 加密10轮AES-192: 密钥为192位(24字节)的AES, 加密12轮AES-256: 密钥为256位(32字节)的AES, 加密14轮 密钥长度越长, 加密的强度越大, 当然与此同时开销也越大。每种类型下都有几种操作模式…

【JavaEE】深入浅出 Spring AOP:概念、实现与原理解析

目录 Spring AOPAOP概述Spring AOP快速⼊⻔引⼊AOP依赖编写AOP程序 Spring AOP 详解Spring AOP核⼼概念切点(Pointcut)连接点(Join Point)通知(Advice)切⾯(Aspect) 通知类型PointCut切⾯优先级 Order切点表达式execution表达式annotation⾃定义注解 MyAspect切⾯类添加⾃定义注…

力扣第71题:简化路径 放弃栈模拟,选择数据流√(C++)

目录 题目 思路 解题过程 复杂度 Code 题目 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中&#xff0c;一个点&#xff…

K8S持久化存储数据volumeMountsvolumes

环境&#xff1a; Ubuntu-1:192.168.114.110作为主 Ubuntu-2:192.168.114.120作为从1&#xff0c;node节点1 Ubuntu-3:192.168.114.130作为从2&#xff0c;node节点2 持久化volumeMounts pod里面&#xff1a;emptyDir和hostPath。存储在node&#xff0c;NFS...&#xff0c;Clo…

文本处理函数

1.文本的提取 left mid right 2.文本的查找与替换 replace&#xff0c;substitute 3.字符个数 len字符 lenb字节, office365好像没有此功能 4.数据的清理 clean , trim 5.找不同 exact

codetop标签动态规划大全C++讲解(上)!!动态规划刷穿地心!!学吐了家人们o(╥﹏╥)o

主要供自己回顾学习&#xff0c;会持续更新&#xff0c;题源codetop动态规划近半年 1.零钱兑换2.零钱兑换II3.面试题08.11.硬币4.单词拆分5.最长递增子序列6.最长递增子序列的个数7.得到山形数组的最少删除次数8.最长公共子序列9.最长重复子数组10.最长等差数列11.最大子数组和…

智能优化算法-海鸥优化算法(SOA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍&#xff1a; 海鸥优化算法 (Seagull Optimization Algorithm, SOA) 是一种基于群体智能的元启发式优化算法&#xff0c;它模拟了海鸥的觅食、飞行和社会交互行为&#xff0c;用于解决复杂的优化问题。 SOA的工…

wxpython Scintilla styledtextctrl滚动条拖到头文本内容还有很多的问题

wxpython Scintilla styledtextctrl滚动条拖到头文本内容还有很多的问题 使用wxpython Scintilla styledtextctrl&#xff0c;滚动条不自动更新 滚动条拖到头文本内容还有很多&#xff0c;如下&#xff1a; 以下是拖到最后的状态&#xff1a; 明显看出下图的滚动条的格子比…

书生.浦江大模型实战训练营——(十一)LMDeploy 量化部署进阶实践

最近在学习书生.浦江大模型实战训练营&#xff0c;所有课程都免费&#xff0c;以关卡的形式学习&#xff0c;也比较有意思&#xff0c;提供免费的算力实战&#xff0c;真的很不错&#xff08;无广&#xff09;&#xff01;欢迎大家一起学习&#xff0c;打开LLM探索大门&#xf…

超声波清洗机哪个品牌好?专业测评师推荐四款高质量眼镜清洗机

近年来&#xff0c;越来越多的用户在使用超声波清洗机清洗小物件&#xff0c;因为超声波清洗机不仅能清洗眼镜&#xff0c;还能清洗各种各样的小饰品、餐具、茶具、剃须刀、金属制品等&#xff0c;有一个智能超声波清洗机在家里&#xff0c;对于生活质感的提升还是挺大的&#…

第一个NIO开发演示

文章目录 Channel简介Buffer简介第一个NIO程序分析 上一篇文章 介绍了传统网络编程在处理高并发和大规模应用时通常面临性能瓶颈和资源管理问题&#xff0c;而 NIO 通过非阻塞 I/O 和选择器机制提供了更高效的解决方案。虽然 NIO 的 API 更复杂&#xff0c;但在高负载和高性能需…

先从路径优化开始学习FastPlanner之B样条曲线平滑路径(一):从拉格朗日插值到B样条曲线

参考B站视频学习 注&#xff1a;我会列出学习他人的博客&#xff0c;但我不涉及具体推导&#xff0c;原理讲解&#xff0c;旨在于理解必须概念后写代码出效果。 给若干点如何获得一条平滑的曲线&#xff1f; 两个方法插值、拟合 插值要经过给定点&#xff0c;拟合不用经过。 经…

Hostease的Windows虚拟主机如何设置错误页面

404错误设置主要用于定义当访问网站上不存在的页面时服务器应该如何响应。通常&#xff0c;404错误表示请求的页面或资源不存在。在Plesk面板中&#xff0c;你可以通过404错误设置来配置服务器对这种情况的处理方式。下面我就介绍如何在Hostease的Windows虚拟主机中设置404错误…

探索数据结构:图(一)之邻接矩阵与邻接表

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 图的定义 **图&#xff08;Graph&#xff09;**是数学和计算机科学中…

Mybatis-plus 创建自定义 FreeMarker 模板详细教程

FreeMarker 自定义模板官方步骤 网址&#xff1a;https://baomidou.com/reference/new-code-generator-configuration/#%E6%A8%A1%E6%9D%BF%E9%85%8D%E7%BD%AE-templateconfig &#xff08;页面往最下面拉为自定义模板相关内容&#xff09; 创建自定义FreeMarker 模板及使用…

案例分享—优秀ui设计作品赏析

多浏览国外优秀UI设计作品&#xff0c;深入分析其设计元素、色彩搭配、布局结构和交互方式&#xff0c;以理解其背后的设计理念和趋势。 在理解的基础上&#xff0c;尝试将国外设计风格中的精髓融入自己的设计中&#xff0c;同时结合国内用户的审美和使用习惯&#xff0c;进行创…

趣味算法------试用 6 和 9 组成的最大数字

目录 ​编辑 题目描述 解题思路 具体代码 总结 题目描述 给你一个仅由数字 6 和 9 组成的正整数 num。 你最多只能翻转一位数字&#xff0c;将 6 变成 9&#xff0c;或者把 9 变成 6 。 请返回你可以得到的最大数字。 输入格式 一个整数 输出格式 一个整数 输入输出…

【机器学习】决策树------迅速了其基本思想,Sklearn的决策树API及构建决策树的步骤!!!

目录 &#x1f354; 案例剖析 &#x1f354; 通过sklearn实现决策树分类并进一步认识决策树 &#x1f354; 基于规则构建决策树 &#x1f354; 构建决策树的三个步骤 &#x1f354; 小结 学习目标 &#x1f340; 了解决策树算法的基本思想 &#x1f340; 了解Sklearn的决策…

Linux WPA/WPA2/WPA3/IEEE 802.1X Supplicant

参考&#xff1a;wpa_supplicant 终端连接 wifi — Linux latest 文档 (gnu-linux.readthedocs.io) 为了管理无线网卡驱动&#xff0c;并且能正常连接到无线网络&#xff0c;你需要一个无线连接管理工具。 如何选择一个最佳的管理方法&#xff0c;将依赖于下面几个因素&#xf…