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

什么时候使用 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 标签:

html 复制代码
<div ref={myRef}>

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

javascript 复制代码
myRef.current.scrollIntoView();

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

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

html 复制代码
<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>
    </>
  );
}
相关推荐
IT古董12 分钟前
【机器学习】机器学习的基本分类-半监督学习(Semi-supervised Learning)
学习·机器学习·分类·半监督学习
jbjhzstsl31 分钟前
lv_ffmpeg学习及播放rtsp
学习·ffmpeg
青い月の魔女39 分钟前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
网络安全(king)40 分钟前
网络安全攻防学习平台 - 基础关
网络·学习·web安全
小马哥编程1 小时前
Function.prototype和Object.prototype 的区别
javascript
小白学前端6661 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
王小王和他的小伙伴1 小时前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
虾球xz1 小时前
游戏引擎学习第59天
学习·游戏引擎
学前端的小朱1 小时前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿2 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架