返回顶部
首页 > 资讯 > 前端开发 > JavaScript >ReactRefCallback使用场景最佳实践详解
  • 233
分享到

ReactRefCallback使用场景最佳实践详解

ReactRefCallback场景实践ReactRefCallback 2023-01-12 09:01:24 233人浏览 泡泡鱼
摘要

目录引言ref callback使用场景1. DOM 元素挂载并滚动到它所在的位置2. 当 DOM 元素变化时的重新渲染3. 在 render 中访问 DOM 元素4. 共享 DOM

引言

本文源于翻译 React ref Callback Use Cases

在 React 中,"Ref" 具有两个相关的含义,而且经常让人困惑。在本文正式开始之前,我们先弄清楚它的定义:

  • 作为 useRef hook 返回的 “ref 对象”:在这种场景中,它就是一个普通的 javascript 对象,具有一个名为 current 的属性,并且可以读取或设置为任意值。
  • 作为 jsX DOM 元素上的 “ref 属性”:用于访问其对应的 DOM 元素

这两者经常一起使用,“ref 对象” 可以传递给 “ref 属性”,React 会将对 DOM 元素设置为它的 current 属性值。

ref callback

ref 属性除了接受 ref 对象之外,还可以接受函数也就是 ref callback。在该函数中,DOM 元素作为其唯一参数。

与 effect 函数一样,React 在组件周期中的某些时刻中调用它。当创建 DOM 元素之后会立即执行 ref callback(参数是 DOM 元素),在删除元素时也会再次调用 ref callback,只不过这时的参数是 null。

如果 ref callback 被定义为内联函数,React 将在每次渲染时调用它两次,第一次的参数是 null,第二次的参数是 DOM 元素。

虽然内联 ref callback 被调用两次可能会令人惊讶,如果从 React 的角度来看,我认为这种行为是合理的。它保证了一致性,因为每次渲染都会创建新的函数实例,它可能是一个完全不同的函数。这些函数可能会依赖 props 或 state,而这些 props 或 state 也可能在此期间发生了变化。

因此 React 需要清除旧的 ref callback(参数是 null),然后设置新的回调(参数是 DOM 元素)。这样我们可以根据条件来设置 ref 属性的值,甚至在 React 元素之间交换它们。

这可能会导致一些不必要的调用。在大多数情况下,这不是引起什么问题。如果你不想执行这些不必要的调用,可以通过在 useCallback 中包装 ref callback或将函数移出组件来避免这种行为。

使用场景

在 React Docs 中关于 ref callback 的内容较少。也许是他们故意不去讨论它,因为它的使用场景非常少,访问 DOM 元素的场景并不多见。

ref callback 是 React 的一个小众功能,你不会每天都需要它。尽管如此,还是有一些场景会用到它,否则,它就不会存在于 React 中了!所以让我们来看一下在哪些场景会用到它。

需要明确的是,只有在访问底层的 DOM 元素时,才需要 ref callback。那么,ref callback 何时有用?答案是,当您想要在 React 中对 DOM 元素执行操作时,请使用 ref callback。据我所知,这可以归结为以下四种情况。

  • 当元素挂载或更新时,调用 DOM 元素上的方法来执行一些操作。
  • “通知” DOM 元素的更改,当 DOM 元素的某些属性发生更改时,重新读取该元素。
  • 将 DOM 元素设置到 state 中,以便在渲染期间访问它。
  • 共享 DOM 元素;使用 DOM 元素执行多项操作。

现在,让我们具体来看看每一个场景。

1. DOM 元素挂载并滚动到它所在的位置

您可以在 ref callback 中调用 DOM 元素上的方法,以执行滚动或聚焦等 DOM 操作。例如,自动滚动到列表中的最后一项:

// On first render and on unmount there 
// is no DOM element so `el` will be `null` 
const scrollTo = (el) => {
  if (el) {
    el.scrollIntoView({ behavior: "smooth" });
  }
};
function List({ data }) {
  return (
    <ul>
      {data.map((d, i) => {
        const isLast = i === data.length - 1;
        return (
          <li
            key={d.name}
            // ref callback to scroll to the last list element
            ref={isLast ? scrollTo : undefined}
          >
            {d.name}
          </li>
        );
      })}
    </ul>
  );
}

记住,管理 DOM 是 React 的工作,避免执行 DOM 上的可变方法, 比如(insert, remove, set, replace 等),对于 focus 和 scroll 等非破坏性的操作则允许我们开发实现。

还要注意,浏览器在没有用户交互的情况下是不允许调用 DOM 元素的某些方法,如 requestFullscreen。当在 ref callback 中调用时,所有此类受保护的方法都不会执行任何操作。

2. 当 DOM 元素变化时的重新渲染

当我们通过 React 访问某些 DOM 元素属性时,可以使用 ref callback。当我们读取一个 DOM 元素属性,比如滚动位置,或者在 ref callback 中调用一个获取元素信息的方法,比如 getBoundinGClientRect(),并将该信息设置到 state 中。

测量 DOM 元素

这是一段直接来自(旧)React 文档的片段:如何测量 DOM 节点。这是 ref callback的一个很好的例子,所以将其复制到这里。

const [size, setSize] = useState();
const measureRef = useCallback((node) => {
  setSize(node.getBoundingClientRect());
}, []);
return <div ref={measureRef}>{children}</div>;

在这个案例中,没有选择使用 useRef,因为当 ref 是一个对象时,它并不会把当前 ref 值的变化情况通知到我们。使用 callback ref 可以确保即便被测量的节点在子组件延迟显示 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。注意到我们传递了 [] 作为 useCallback 的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。

3. 在 render 中访问 DOM 元素

如果在 ref 回调中将 DOM 元素设置到 state,它将触发新的渲染,因为这正是设置 state 的作用。但是它不会陷入无限渲染循环,因为 setState 是一个稳定的函数,因此 ref callback 仅在挂载和卸载时调用。

在这种情况下,为什么我们不使用 useRef?答案是,因为不允许在渲染过程中访问 ref 对象。对于渲染中的 DOM 元素,必须通过 state 来访问。接下来举几个例子进行介绍。

React Portal

React portal 主要用于解决组件树和 DOM 树的结构之间不一致的问题。portal 将 DOM 树上不同位置上的组件连接到一起,最为常使用的场景就是将 Modal 弹窗覆盖整个视窗。

// Assume an empty div with id 'modal' is in your html
const modalEl = document.getElementById("modal");
function Modal({ children, ...props }) {
  return ReactDOM.createPortal(
    <ModalBase {...props}>
      {children}
    </ModalBase>,
    modalEl
  );
}

可以使用 document.getElementById() 来获取 DOM 元素,前提是你能保证它是存在的。或许你不想通过 HTML 来控制 Modal,而是希望能 portal 到一个 React 创建的 DOM 元素上。

这就需要在进行 render 时访问到相应的 DOM 元素,使用 ref callback 可以实现这个功能。

function Parent() {
  const [modalElement, setModalElement] = useState(null);
  return (
    <div>
      <div id="modal-location" ref={setModalElement} />
      {}
      <Modal modalElement={modalElement}>Warning</Modal>
    </div>
  )
}
function Modal({ children, modalElement, ...props }) {
  return modalElement
    ? ReactDOM.createPortal(
        <ModalBase {...props}>{children}</ModalBase>,
        modalElement
      )
    : null;

在最开始,modalElement 的值是 null,所以需要在创建 portal 之前做一下判断。

非受控复合组件

非受控复合组件(Uncontrolled Compound Components)是一种高级的 React 模式,其核心是 ref callback 来处理 React portal。

复合组件(Compound Component)是将多个组件组合到一起工作,进而形成一个能够展示的 UI。复合组件将复杂的功能拆分为更小的块,并且它们在一起共同完成整个复杂功能。这样就可以避免产生一个有很多 props 的“上帝组件”。

这种模式与 HTML 元素组合比较类似,比如:

  • <select> 中包含多个 <option>
  • <table> 组件会由 <thead>和 <tbody> 组成
  • <details> 元素中会包含 <summary>

在 React 中,数据总是向下流动。当数据流不符合组件树的结构时,我们可以通过提升 state 来调整数据流。大多数时候,这是一个很好的解决方案。

对于一些共享组件,如对话框或侧边栏,页面上只能有一个,提升 state 会使这些 state “过于全局”。这些 state 将通过许多中间组件连接起来,而这些中间组件实际上并不需要知道它。它会污染整个链条上的组件,并会使代码变得混乱。

我们可以把 React state 看作是悬挂在组件树上的绳子。绳子的长度代表了定义 state 的组件到使用 state 的 UI 之间的距离。所以,当你的绳子长度越长、数量越多时,它们就越容易被缠在一起。所以要尽量缩短绳子的长度,同时控制绳子的数量。

出现这个问题的根源是,我们强行将组件树和数据流适配成 DOM 树的形状。反过来,我们可以通过组件树适应数据流来反转控制。使用 Portal,我们可以重置组件间的距离,并保持 DOM 结构不变。这样就可以将拉近相关组件的距离,即便是在 DOM 树中的离得很远。这使我们的 React 代码更容易理解。

非受控复合组件的实现过程:

  • ref callback定位到元素位置。获取 DOM 元素并将其置于 state 中。这里我们不能使用 ref 对象,因为我们需要在 render 中使用它,而且要在设置它的时候触发更新。
  • 用 Context 共享元素位置。将这个 DOM 元素放存放在 context 中,以便 context 中的所有组件都可以访问 DOM 中的这个位置。
  • 使用 Portal 连接到该位置。从 context 中获取对 DOM元素,并将组件进行 portal。

如果你在想 “这一切听起来很复杂”,是的,你没有错。这是一种高级模式,需要一些额外的成本,作为回报 -- 它能够让你编写了更简单的组件。这是值得的(IMO)。也许你会发现,你并不经常需要它,但是一旦有需要时,就会体验到其中的乐趣。“Amazing”!

比如在下面的例子中实现一个复杂的面包屑:

每个 <Breadcrumb/> 都会 portal 到 <BreadcrumbPortal/> 中的 breadcrumbElement 元素

<BreadcrumbPortal/> 会按它们的渲染顺序在 <Breadcrumbs/> 展示出来

如果 <BreadcrumbPortal/> 没有渲染,这时 breadcrumbElement 为 null,<Breadcrumb/> 也不会渲染

4. 共享 DOM Ref

经常会出现不止一个消费者需要访问 DOM 元素。假设你想测量一个 <div>的宽度,并将其交给另一个 React 之外的库来处理 DOM 内容。对于 React 来说,这样的元素就是一个黑盒。React 既不知道它的内部有什么,也不关心它是什么。这个元素完全交给另外一个库来管理。

一个典型的例子就是使用 D3 或 @observable/plot 创建的响应式图标。在下面的例子中,我们会使用 @observable/plot 创建一个 plot,并且使用 react-use-measure 来计算元素的宽度。使用 ref callback将 DOM 元素传递给它们俩:

import useMeasure from "react-use-measure";
import * as Plot from "@observablehq/plot";
export function BoxPlot({ data }) {
  const [measureRef, { width, height }] = useMeasure({ debounce: 5 });
  const plotRef = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    const boxPlot = Plot.plot({
      width: Math.max(150, width),
      marks: [
        Plot.boxX(data),
      ],
    });
    plotRef.current.append(boxPlot);
    return () => boxPlot.remove();
  }, [data, width]);
  const initBoxPlot = useCallback((el: HTMLDivElement | null) => {
    plotRef.current = el;
    measureRef(el);
  }, []);
  return <div ref={initBoxPlot} />;
}

总结

  • ref callback是一个传递给元素的 ref 属性的函数。React 会在组件挂载时调用它,这时的参数是 DOM 元素;当组件卸载的时候也会调用它,这时的参数是 null。
  • 当你设置不同的 ref callback时,React 也会调用 ref callback
  • 切换绑定和取消绑定 ref 到一个 DOM 元素,ref callback可以让我们实现特定的操作。

ref callback可以用来做以下事情

  • 操作 DOM,比如在组件挂载的时候滚动或聚焦
  • 在 React 获取 DOM 属性,比如宽度或滚动位置
  • 在 React 控制的 DOM 元素上使用 Portal

非受控复合组件是一个很强大的模式,使用了 Portal

将 DOM 元素提供给多个消费者

以上就是React Ref Callback最佳实践详解的详细内容,更多关于React Ref Callback实践的资料请关注编程网其它相关文章!

--结束END--

本文标题: ReactRefCallback使用场景最佳实践详解

本文链接: https://lsjlt.com/news/177455.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • ReactRefCallback使用场景最佳实践详解
    目录引言ref callback使用场景1. DOM 元素挂载并滚动到它所在的位置2. 当 DOM 元素变化时的重新渲染3. 在 render 中访问 DOM 元素4. 共享 DOM...
    99+
    2023-01-12
    React Ref Callback场景实践 React Ref Callback
  • golang 反射的应用场景和最佳实践
    反射在 go 中提供了强大的类型和值操纵能力。其应用场景包括:类型检查/转换、动态类型/值创建、第三方库交互、自定义类型定义验证。最佳实践包括:仅在必要时使用、避免泛型反射、缓存结果、释...
    99+
    2024-04-30
    golang 反射
  • golang函数最佳实践详解
    遵循 go 函数最佳实践可以编写高效、可维护的函数,具体包括:1. 保持函数简洁;2. 使用命名参数;3. 返回多个值;4. 处理错误;5. 使用文档注释。 Go 函数最佳实践详解 在...
    99+
    2024-04-27
    golang 函数最佳实践
  • AndroidRxjava3使用场景详解
    目录一、Rxjava使用场景1、多任务嵌套回调2、多任务合并处理3、轮询4、其他小场景1)倒计时2)打字机效果二、结合Rxbinding的使用场景1、点击事件防抖2、输入搜索优化3、...
    99+
    2024-04-02
  • ts封装axios最佳实践示例详解
    目录简介什么样封装才是最合理的开整开整之前先看看 axios 基本类型Talk is cheap,show me the code.简介 看了一圈,大家对 ts 封装 axios ...
    99+
    2023-03-13
    ts封装axios ts axios
  • Python 和 Jython 的最佳应用场景对比
    Python 和 Jython 都是高级编程语言,但它们在实现和目标用户方面存在显着差异。了解它们的最佳应用场景有助于选择合适的工具来满足特定需求。 Python 最佳应用场景: 通用编程:Python 是一种通用语言,可用于构建广泛的...
    99+
    2024-03-15
    Jython
  • 详解React 代码共享最佳实践方式
    任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题。在React中实现逻辑复用通常有以下几种方式:Mixin、高阶组件(HOC)、修饰器(decorator)、Render...
    99+
    2024-04-02
  • react后台系统最佳实践示例详解
    目录一、中后台系统的技术栈选型1. 要做什么2. 要求3. 技术栈怎么选二、hooks时代状态管理库的选型contextreduxrecoilzustandMobX三、hooks的使...
    99+
    2023-01-03
    react后台系统实践 react 后台系统
  • 在PHP中使用AJAX的最佳实践
    随着Web应用程序的越来越复杂和交互性的增加,AJAX(Asynchronous JavaScript and XML)的使用逐渐流行起来。AJAX允许我们在不需要刷新整个页面的情况下与服务器进行异步通信并更新部分页面。而在PHP中使用AJ...
    99+
    2023-05-23
    PHP ajax 最佳实践
  • vue中使用Axios最佳实践方式
    目录1.前言2.使用2.1安装2.2基本用例2.2.1 get请求2.2.2post请求3.配置3.1语法3.2别名4.Axios实例4.1语法4.2请求配置4.3响应的配置配置的优...
    99+
    2024-04-02
  • 使用 PHP 函数库的最佳实践
    最佳使用 php 函数库的方法:选择最合适的函数。使用命名空间防止函数名冲突。利用自动化函数节省编码时间。缓存昂贵的函数调用以减少开销。使用依赖注入解耦函数和它们所依赖的对象。 PHP...
    99+
    2024-04-19
    实践 php
  • 使用Golang实现文件锁的最佳实践
    使用Golang实现文件锁的最佳实践 在开发中,我们经常会遇到需要对文件进行加锁的情况,以保证文件在多个goroutine或进程间的并发访问时能够正确操作。在Golang中,实现文件锁...
    99+
    2024-02-28
    golang 实现 文件锁 并发访问 golang开发
  • PHP 微服务与容器化最佳实践详解
    php 微服务的最佳实践包括分解应用程序、定义清晰的 api、使用消息总线和实施断路器模式。容器化最佳实践包括使用编排工具、创建定制镜像、自动化构建和部署、持久化数据卷以及实现负载均衡和...
    99+
    2024-05-08
    php 微服务 docker
  • golang函数缓存机制详解及最佳实践
    go语言中的函数缓存机制通过sync.pool实现了对函数结果的存储和重用,从而提升了程序性能。该机制对纯函数且频繁调用的函数效果显著。最佳实践包括:选择合适的缓存容量、使用小对象、缩短...
    99+
    2024-05-04
    golang 缓存机制 go语言
  • ApachePulsar微信大流量实时推荐场景下实践详解
    目录导语作者简介实践 1:大流量场景下的 K8s 部署实践实践 2:非持久化 Topic 的应用实践 3:负载均衡与 Broker 缓存优化实践 4:COS Offloader 开发...
    99+
    2022-11-16
    Apache Pulsar微信大流量推荐 Apache Pulsar流量推荐
  • TiDB 最佳实践系列(六)HAProxy 的使用
    作者:李仲舒 HAProxy 是一个使用 C 语言编写的自由及开放源代码软件,其提供高可用性、负载均衡,以及基于 TCP 和 HTTP 的应用程序代理。GitHub、Bitbucket、Stack Overflow、Reddit、Tumbl...
    99+
    2021-01-04
    TiDB 最佳实践系列(六)HAProxy 的使用
  • PHP中使用二维码的最佳实践
    PHP中使用二维码的最佳实践 二维码已经成为了现代社会中不可或缺的一部分。它们通常用于快速访问网站、扫描商品、或者在移动设备之间传输信息。在PHP中使用二维码可以帮助您快速生成高质量的二维码,以便在您的应用程序中使用。本文将介绍在PHP中使...
    99+
    2023-09-26
    二维码 path git
  • NPM API的使用方法和最佳实践
    NPM (Node Package Manager) 是一个用于 Node.js 的包管理器,它可以让开发者轻松地安装、更新和管理 Node.js 包。作为一个广受欢迎的 JavaScript 包管理器,NPM 提供了一系列的 API,使得...
    99+
    2023-09-26
    api npm 关键字
  • Java8 Optional用法和最佳实践
    Java 8中的Optional是一个可以包装任意类型对象的容器类,用于处理可能为空的值,以避免出现空指针异常。以下是使用Java ...
    99+
    2023-09-21
    Java
  • Memcache缓存技术的最佳使用场景及其性能测试
    Memcache是一种高性能的内存缓存系统,常用于加速访问速度和降低数据库等后端系统的压力。在使用Memcache时,开发者需要考虑如何选择合适的使用场景及其最佳实践,以获得最佳的性能和效果。最佳使用场景:内容分发Memcache可以将数据...
    99+
    2023-05-18
    Memcache 性能测试 缓存技术
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作