什么时候使用 ref 操作 DOM?
有时你可能需要访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。在 React 中没有内置的方法来做这些事情,所以你需要一个指向 DOM 节点的 ref 来实现。
怎么使用 ref 操作 DOM?
首先,引入 useRef Hook:
import { useRef } from 'react';片
然后,在你的组件中使用它声明一个 ref:
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。
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。
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 => (<likey={cat.id}ref={(node) => {const map = getMap();if (node) {map.set(cat.id, node);} else {map.delete(cat.id);}}}><imgsrc={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。
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:
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></>);
}