react 学习 —— 16、使用 ref 操作 DOM

news/2024/7/15 18:41:34 标签: react.js, 学习, javascript

什么时候使用 ref 操作 DOM?

有时你可能需要访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。在 React 中没有内置的方法来做这些事情,所以你需要一个指向 DOM 节点的 ref 来实现。

怎么使用 ref 操作 DOM?

首先,引入 useRef Hook:

javascript">import { useRef } from 'react';

然后,在你的组件中使用它声明一个 ref:

javascript">const myRef = useRef(null);

最后,将你的 ref 作为 ref 属性传递给你想要获得 DOM 节点的 JSX 标签:

<div ref={myRef}>

useRef Hook 返回一个对象,该对象有一个名为 current 的属性。最初,myRef.current 是 null。当 React 为这个 <div /> 创建一个 DOM 节点时,React 会把对该节点的引用放入 myRef.current。然后,你可以从 事件处理器 访问此 DOM 节点,并使用在其上定义的内置浏览器 API。

javascript">myRef.current.scrollIntoView();

如何使用 ref 回调管理 ref 列表?

有时候,你可能需要为列表中的每一项都绑定 ref ,而你又不知道会有多少项。像下面这样做是行不通的:

<ul>
  {items.map((item) => {
    // 行不通!
    const ref = useRef(null);
    return <li ref={ref} />;
  })}
</ul>

这是因为 Hook 只能在组件的顶层被调用。不能在循环语句、条件语句或 map() 函数中调用 useRef 。
一种可能的解决方案是用一个 ref 引用其父元素,然后用 DOM 操作方法如 querySelectorAll 来寻找它的子节点。然而,这种方法很脆弱,如果 DOM 结构发生变化,可能会失效或报错。
另一种解决方案是将函数传递给 ref 属性。这称为 ref 回调。当需要设置 ref 时,React 将传入 DOM 节点来调用你的 ref 回调,并在需要清除它时传入 null 。这使你可以维护自己的数组或 Map,并通过其索引或某种类型的 ID 访问任何 ref。

javascript">import { useRef } from 'react';

export default function CatFriends() {
  const itemsRef = useRef(null);

  function scrollToId(itemId) {
    const map = getMap();
    const node = map.get(itemId);
    node.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function getMap() {
    if (!itemsRef.current) {
      // 首次运行时初始化 Map。
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToId(0)}>
          Tom
        </button>
        <button onClick={() => scrollToId(5)}>
          Maru
        </button>
        <button onClick={() => scrollToId(9)}>
          Jellylorum
        </button>
      </nav>
      <div>
        <ul>
          {catList.map(cat => (
            <li
              key={cat.id}
              ref={(node) => {
                const map = getMap();
                if (node) {
                  map.set(cat.id, node);
                } else {
                  map.delete(cat.id);
                }
              }}
            >
              <img
                src={cat.imageUrl}
                alt={'Cat #' + cat.id}
              />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

const catList = [];
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}

怎样访问另一个组件的 DOM 节点?

当你将 ref 放在像 这样输出浏览器元素的内置组件上时,React 会将该 ref 的 current 属性设置为相应的 DOM 节点(例如浏览器中实际的 )。
但是,如果你尝试将 ref 放在 你自己的 组件上,例如 ,默认情况下你会得到 null。

javascript">import { useRef } from 'react';

function MyInput(props) {
  return <input {...props} />;
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

为了帮助您注意到这个问题,React 还会向控制台打印一条错误消息:
在这里插入图片描述
发生这种情况是因为默认情况下,React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!这是故意的。Refs 是一个应急方案,应该谨慎使用。手动操作 另一个 组件的 DOM 节点会使你的代码更加脆弱。

相反,想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件。下面是 MyInput 如何使用 forwardRef API:

javascript">import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

http://www.niftyadmin.cn/n/5113235.html

相关文章

【配置本地仓库yum源的两种方式】

一、使用下载的rpm包搭建本地仓库yum源 ​  使用自己下载的rpm包搭建本地仓库&#xff0c;需要用createrepo去创建仓库元数据。 1、下载需要的软件包 在一台可以联网的服务器上下载。或者通过其他方式下载自己需要的软件包。 # 下载所需工具依赖包到 /mypath [roottest ~]…

进阶JAVA篇-深入了解 Set 系列集合

目录 1.0 Set 类的说明 1.1 Set 类的特点 1.2 Set 类的常用API 2.0 HashSet 集合的说明 2.1 从 HashSet 集合的底层原理来解释是如何实现该特性 2.2 HashSet 集合的优缺点 2.3 深入理解 HashSet 集合去重的机制 2.4 如何快速编写已经重写好的 hashCode 和 equals 方法 3.0 Tree…

代码随想录训练营二刷第六十一天 | 503.下一个更大元素II 42. 接雨水

代码随想录训练营二刷第六十一天 | 503.下一个更大元素II ● 42. 接雨水 一、503.下一个更大元素II 题目链接&#xff1a;https://leetcode.cn/problems/next-greater-element-ii/ 思路&#xff1a;循环数组多遍历一次&#xff0c;逻辑上拼一块往单调栈里放。 class Solutio…

Jetpack:015-Jetpack的是脚手架

文章目录 1. 概念介绍2. 使用方法2.1 核心思想2.2 具体内容 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack中小红点相关的内容&#xff0c;本章回中将介绍 脚手架。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff01; 1. 概念介绍 我们在本章回中介…

算法通关村第十关黄金挑战——归并排序详解

大家好&#xff0c;我是怒码少年小码。 归并排序 就是将一个大的序列分为多个小的序列&#xff0c;对小的序列排序&#xff0c;最后利用归并的思想合并序列。归并排序的思想就是分而治之。 分&#xff1a;将大序列分为很多个小的序列 治&#xff1a;将多个小的序列合并为大序…

linux下C++开发环境搭建

安装GCC,GDB 先更新软件包安装源 sudo apt update安装编译器和调试器 sudo apt install build-essential gdb"build-essential" 是编译器和 "gdb" 调试器。1. **build-essential**:- "build-essential" 是一个用于Ubuntu和Debian Linux系统的…

2.3.C++项目:网络版五子棋对战之实用工具类模块的设计

文章目录 一、实用工具类模块&#xff08;一&#xff09;功能 二、设计和封装&#xff08;一&#xff09;日志宏封装&#xff08;二&#xff09;mysql_util封装&#xff08;三&#xff09;Jsoncpp-API封装&#xff08;四&#xff09;file_util封装&#xff08;五&#xff09;st…

linux系统查看bash的history

要输出最近的20条命令&#xff0c;可以使用history命令。在Bash终端中&#xff0c;输入以下命令即可获取最近的20条命令历史记录&#xff1a; history 20这将显示你最近执行的20条命令及其相应的行号。 要将最近的20条命令写入到一个名为 “command.txt” 的文本文件中&#…