当前位置: 首页 > article >正文

使用ref操作DOM(React)

一、获取指向节点的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) => (
            <li
              key={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.kler.cn/a/387942.html

相关文章:

  • 蓝凌OA-EKP hrStaffWebService 任意文件读取漏洞
  • 项目模块十七:HttpServer模块
  • DHCP与DNS安全管理
  • 羊城杯2020Easyphp
  • go语言中的log 包详解
  • ReactPress:构建高效、灵活、可扩展的开源发布平台
  • Qt菜单功能实现
  • 【Ant.designpro】上传图片
  • LeetCode-两整数之和
  • 清华大学提出Mini-Omni2:开源多模态模型,功能与GPT-4o媲美!
  • 继承的学习
  • 漫途LoRa家族系列产品,高效、稳定、智能的物联网通信!
  • 智象未来(HiDream.ai):从科技创新启程,绘制智能未来新篇章
  • 【MySQL】数据的增删查改
  • //字符串数组
  • STM32单片机WIFI语音识别智能衣柜除湿消毒照明
  • 在 C# 中,ICollection 和 IList 接口有什么区别?
  • SQL注入攻击及其在SpringBoot中使用MyBatisPlus的防范策略
  • [OpenGL]使用OpenGL实现硬阴影效果
  • Audio-Language Models
  • MySQL8完全卸载方法-Win10系统
  • 4.python字面量
  • nvm 切换 Node.js 版本
  • LabVIEW扫描探针显微镜系统
  • 订单分库分表
  • 常用的c++特性-->day02