使用ref操作DOM(React)

ops/2024/11/13 14:01:23/

一、获取指向节点的ref

1、一般方式:

  • 引入useRef Hook函数,它用来创建可变的ref对象。一般用于访问DOM元素或在组件之间维持不变的值
import { useRef } from 'react';
  • 使用useRef来声明一个myRef
const myRef = useRef(null);
  • 最后,将myRef作为ref属性传递给想要获取的DOM节点的JSX标签
<div ref={myRef}>
  • useRef函数会返回一个对象,该对象有一个current属性。最初,myRef.current为null;创建完那个div节点时,会把对那个节点的引用放入myRef.current。但设置 myRef 的 current 值不会触发重新渲染。
// 你从事件处理器(如点击、输入等等)访问该DOM节点,并使用在其上面定义的任意浏览器 API,例如:
myRef.current.scrollIntoView();

2、ref回调方式:

背景:

当想要给一个有未知数量项的列表绑定ref时,上面的一般方法显然是行不通的,因为Hook 只能在组件的顶层被调用,不能在循环语句、条件语句或 map() 函数中调用 useRef。即下面的示例代码是不可取的:

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

解决方法:

方法1(有局限性):用一个 ref 引用其父元素,然后用 DOM 操作方法如 querySelectorAll 来寻找它的子节点。例:

import React, { useRef } from 'react';function ParentComponent() {const parentRef = useRef(null);const handleButtonClick = () => {// 使用 querySelectorAll 获取所有子元素const children = parentRef.current.querySelectorAll('.child');// 遍历子元素并打印它们的文本内容children.forEach((child) => {console.log(child.textContent);});};return (<div><div ref={parentRef} style={{ border: '1px solid black', padding: '10px' }}><div className="child">Child 1</div><div className="child">Child 2</div><div className="child">Child 3</div></div><button onClick={handleButtonClick}>获取子元素并修改</button></div>);
}export default ParentComponent;

缺点:这种方法很脆弱,如果 DOM 结构发生变化,可能会失效或报错

方法2:将函数传递给 ref 属性,这称为 ref 回调。当需要设置 ref 时,React 将传入 DOM 节点来调用你的 ref 回调,并在需要清除它时传入 null。例:

import { useRef, useState } from "react";export default function CatFriends() {const itemsRef = useRef(null);const [catList, setCatList] = useState(setupCatList);function scrollToCat(cat) {const map = getMap();const node = map.get(cat);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={() => scrollToCat(catList[0])}>Neo</button><button onClick={() => scrollToCat(catList[5])}>Millie</button><button onClick={() => scrollToCat(catList[9])}>Bella</button></nav><div><ul>{catList.map((cat) => (<likey={cat}ref={(node) => {const map = getMap();if (node) {map.set(cat, node);} else {map.delete(cat);}}}><img src={cat} /></li>))}</ul></div></>);
}function setupCatList() {const catList = [];for (let i = 0; i < 10; i++) {catList.push("https://loremflickr.com/320/240/cat?lock=" + i);}return catList;
}

注:在这个例子中,itemsRef 保存的不是单个 DOM 节点,而是保存了包含列表项 ID 和 DOM 节点的 Map。(Ref 可以保存任何值!)

特点:

  • 清晰的引用管理:通过回调函数,可以灵活地管理 DOM 引用,并在组件的生命周期内做清理。
  • 动态更新:如果 ref 需要在不同条件下变化,回调函数可以轻松适应。
  • 直接访问 DOM:可以在需要时直接访问和操作 DOM 元素,而不需要依赖于额外的状态管理。

二、访问另一个组件的DOM节点

上面提到的组件都是浏览器元素的内置组件,React会将该ref的current属性设置为相应的DOM节点

但若将ref放在自定义的组件上,例<MyInput />,默认情况下会得到null

1、错误情况:

例:

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></>);
}

此时执行点击事件后,会控制台报错:Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

报错原因:默认情况下,React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!

2、正确情况:

背景:Refs 是一种脱围机制,应该谨慎使用。手动操作另一个组件的 DOM节点会使你的代码更加脆弱,所以想要暴露其DOM节点的组件必须选择该行为,一个组件可以指定将它的ref转发给一个子组件

使用API:forwardRef API

示例代码:

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

工作原理:

  1. MyInput 组件是使用 forwardRef 声明的。 这让从上面接收的 inputRef 作为第二个参数 ref 传入组件,第一个参数是 props 。
  2. MyInput 组件将自己接收到的 ref 传递给它内部的<input> 。
拓展:

上面那种方法是把DOM元素整个暴露出去了,所以能在父组件直接访问和操作该DOM元素,也就是说能够对子组件的DOM元素执行所有该元素支持的操作

若想限制暴露的功能,useImperativeHandle可以胜任:

const MyInput = forwardRef((props, ref) => {const realInputRef = useRef(null);useImperativeHandle(ref, () => ({// 只暴露 focus,没有别的focus() {realInputRef.current.focus();},}));return <input {...props} ref={realInputRef} />;
});

三、React何时添加refs

更新分为两个阶段:

渲染阶段:React调用组件来确定页面上应该显示什么

提交阶段:React把变更应用于DOM

在 React 中,通常不建议在组件的渲染过程中访问 refs,因为在这个阶段,DOM 节点可能尚未存在或尚未更新。这会导致 ref.current 的值为 null,或者返回一个过时的 DOM 节点引用。

1、渲染期间的时间点

  • 在 React 的渲染周期中,组件的 render 方法执行时,DOM 节点尚未被创建。此时,如果你尝试访问 ref.current,它将返回 null,因为 DOM 元素还没有被挂载到 DOM 树中。

2、更新期间的时间点

  • 当组件更新时,React 会先更新虚拟 DOM,然后再实际更新真实 DOM。在这个过程中,如果你在渲染方法中访问 ref,它可能会返回未更新的 DOM 节点引用,导致你无法获取到最新的 DOM 状态。

推荐做法:

1、useEffect Hook:useEffect 会在组件渲染完成后执行,此时 DOM 节点已经被创建并挂载到 DOM 树上。因此,访问 ref.current 是安全的。

2、事件处理函数:在事件处理函数中访问 ref 是安全的,因为这些函数是在用户交互后执行的,此时 DOM 元素已经存在并可以安全访问。

四、注意: 

1、避免更改由React管理的DOM节点:更改DOM后,React就不知道该如何正确的管理它了,可能会导致崩溃现象

2、并不是完全不能修改,可以修改React没有理由更新的部分DOM。例:如果某些<div>在 JSX 中始终为空,React 将没有理由去变动其子列表。 因此,在那里手动增删元素是安全的。


http://www.ppmy.cn/ops/132949.html

相关文章

hbase 工具类

hbase 工具类 pom.xml <dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-client</artifactId><version>2.5.10-hadoop3</version> </dependency> <dependency><groupId>com.google.guava<…

如何不封禁UDP协议同时防止UDP攻击

UDP&#xff08;User Datagram Protocol&#xff09;协议因其简单、高效的特点&#xff0c;广泛应用于各种网络服务中&#xff0c;如视频流、在线游戏和VoIP等。然而&#xff0c;UDP协议的无连接特性和缺乏内置的安全机制使其容易成为攻击者的靶标&#xff0c;常见的攻击类型包…

Redis 高并发分布式锁实战

目录 环境准备 一 . Redis 安装 二&#xff1a;Spring boot 项目准备 三&#xff1a;nginx 安装 四&#xff1a;Jmeter 下载和配置 案例实战 优化一&#xff1a;加 synchronized 锁 优化二&#xff1a;使用 redis 的 setnx 实现分布式锁 优化三&#xff1a;使用 Lua 脚本…

docker快速安装与配置mongoDB

docker快速安装与配置mongoDB 拉取 MongoDB Docker 映像 docker pull mongodb/mongodb-community-server:latest将映像作为 container 运行 docker run --restartalways --name mongodb -p 27017:27017 --privilegedtrue -e MONGO_INITDB_ROOT_USERNAMEroot -e MONGO_INITD…

科技改变生活:最新智能开关、调光器及插座产品亮相

根据QYResearch调研团队的最新力作《欧洲开关、调光器和插座市场报告2023-2029》显示&#xff0c;预计到2029年&#xff0c;欧洲开关、调光器和插座市场的规模将攀升至57.8亿美元&#xff0c;并且在接下来的几年里&#xff0c;将以4.2%的复合年增长率&#xff08;CAGR&#xff…

《手写Spring渐进式源码实践》实践笔记(第十七章 数据类型转换)

文章目录 第十七章 数据类型转换工厂设计实现背景技术背景Spring数据转换实现方式类型转换器&#xff08;Converter&#xff09;接口设计实现 业务背景 目标设计实现代码结构类图实现步骤 测试事先准备属性配置文件转换器工厂Bean测试用例测试结果&#xff1a; 总结 第十七章 数…

数据中心类DataCenter(三)

数据中心类DataCenter&#xff08;三&#xff09; 前言 在上一集&#xff0c;我们就完成了整个数据中心类的构造函数以及析构函数&#xff0c;我们讨论了我们的数据持久化的相关事宜&#xff0c;那么我们这一集就要来完成这一个内容。 需求分析 我们需要暂且就规定我们的路…

springboot快递物流管理系统-计算机设计毕业源码85178

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 快递物流管理系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2 数据修改流程 2.2.3 数据…