使用 React ref 和真正理解它轼两回事。说实话,我不确定我是否正确地理解了到目前为止的一切,因为它不像 state 或副作用那样经常使用在 React 中,因为它的 API 在 React 的过去确实经常改变。在这个 React Ref 指南中,我想给你一步一步的介绍 React refs。
React useRef Hook:REFS
React refs 和 DOM 有很强的关联。在过去确实如此,但是在 React 引入 React Hooks 后就不再是了。Ref 的意思是引用,但是它可以是任何东西的引用(DOM 节点,JavaScript 值...)。因此,在深入研究 React ref 在 HTML 元素中的用法之前,我们先退一步研究一下不带 DOM 的 React ref。让我们以以下 React 组件为例:
function Counter() {
const [count, setCount] = React.useState(0);
function onClick() {
const newCount = count + 1;
setCount(newCount);
}
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
</div>
);
}
React 为我们提供了 React useRef Hook,它是 React 函数组件中使用 refs 时的 API。useRef 钩子返回给我们一个可变对象,它在 React 组件的生命周期内保持不变。具体来说,返回的对象有一个 current
属性,它可以为我们保存任何可修改的值:
function Counter() {
const hasClickedButton = React.useRef(false);
const [count, setCount] = React.useState(0);
function onClick() {
const newCount = count + 1;
setCount(newCount);
hasClickedButton.current = true;
}
console.log('Has clicked button? ' + hasClickedButton.current);
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
</div>
);
}
ref 的 current
属性使用我们为 useRef hook 提供的参数初始化(此处为 false)。只要需要,就可以将 ref 的 current 属性重新赋一个新的值。在前面的实例中,我们只是跟踪按钮是否已被点击。
将 React ref 设置为新的值的关键在于,它不会触发组件的重新渲染。虽然在上一个例子中 state updater 函数(这里是 setCount)更新组件的状态,并重新渲染组件,但仅仅切换 ref current 属性的布尔值不会触发重新渲染:
function Counter() {
const hasClickedButton = React.useRef(false);
const [count, setCount] = React.useState(0);
function onClick() {
// const newCount = count + 1;
// setCount(newCount);
hasClickedButton.current = true;
}
// Does only run for the first render.
// Component does not render again, because no state is set anymore.
// Only the ref's current property is set, which does not trigger a re-render.
console.log('Has clicked button? ' + hasClickedButton.current);
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
</div>
);
}
好的,我们可以使用 React 的 useRef hook 来创建一个可变对象,它会在组件存在的整个时间里一直存在。但当我们改变它时,它不会触发重新渲染,因为这是 state 的用途,所以这里的 ref 的用法是什么?
React REF 作为实例变量
当我们需要在不使用 React 的重新渲染机制的情况下跟踪某种状态时,ref 可以作为 React 中函数组件的 实例变量。例如,我们可以跟踪一个组件是第一次渲染还是重新渲染:
function ComponentWithRefInstanceVariable() {
const [count, setCount] = React.useState(0);
function onClick() {
setCount(count + 1);
}
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
{/*
只会在 setCount 触发重新渲染时工作。
只改变 ref.current 属性不会触发重新渲染。
*/}
<p>{isFirstRender.current ? 'First render.' : 'Re-render.'}</p>
</div>
);
}
在这个例子中,我们用 true 初始化 ref 的当前属性,因为我们正确地假设组件在第一次初始化时就开始了它的第一次渲染。然而,然后我们使用 React 的 useEffect hook——没有设置其第二个参数的依赖数组——在组件的第一次渲染后更新 ref 的当前属性。设置 ref 的当前属性为 false 不会触发重新渲染。
现在我们可以创建一个 useEffect hook,它只在每次组件更新时运行逻辑,而不是在初始渲染时运行逻辑。这当然是每个 React 开发者在某些时候都需要的功能,但却不是由 React 的 useEffect 钩子提供的:
function ComponentWithRefInstanceVariable() {
const [count, setCount] = React.useState(0);
function onClick() {
setCount(count + 1);
}
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log(
`
I am a useEffect hook's logic
which runs for a component's
re-render.
`
);
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
</div>
);
}
使用 refs 为 React 组件设置实例变量并没有被广泛使用,也不经常需要。然而,在我的课上学了这个特性手,我也见过我的 React 研讨会上的开发人员确定地相信他们的特定情况需要 useRef 生成的实例变量。
**经验法则:当你需要跟踪你 React 组件的状态(它不应该触发组件的重新渲染)时,你可以使用 React 的 useRef hook 为它创建一个实例变量。
React useRef hook:DOM REFS
让我们来看看 React 的 ref 专长: DOM。大多数情况下,当你必须与 HTML 元素交互时,你会使用 React 的 ref。React 本质上是声明式的,但有时您需要从 HTML 元素读取值,与 HTML 元素的 API 交互,甚至必须向 HTML 元素写入值。对于这些罕见的情况,必须使用 React 的 refs 以命令式而非声明式的方式与 DOM 交互。
下面这个 React 组件展示了 React ref 和 DOM API 相互作用的最流行的例子:
function App() {
return (
<ComponentWithDomApi
label="Label"
value="Value"
isFocus
/>
);
}
function ComponentWithDomApi({ label, value, isFocus }) {
const ref = React.useRef(); // (1)
React.useEffect(() => {
if (isFocus) {
ref.current.focus(); // (3)
}
}, [isFocus]);
return (
<label>
{/* (2) */}
{label}: <input type="text" value={value} ref={ref} />
</label>
);
}
像之前一样,我们使用 React 的 useRef hook 来创建一个 ref 对象(1)。在这种情况下,我们不给它赋任何初始值,因为这将在下一步(2)中完成,我们将把 ref 对象作为 ref HTML 属性提供给 HTML 元素。React 自动为我们将这个 HTML 元素的 DOM 节点分配给 ref 对象。最后(3)我们可以使用 DOM 节点(它现在被赋值给 ref 的 current 属性)来与它的 API 交互。
前面的示例向我们展示了如何在 React 中与 DOM API 交互。接下来,你将学习如何用 ref 从 DOM 节点中读取值。下面的例子从我们的元素中读取大小,以在浏览器中显示为标题:
function ComponentWithRefRead() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useRef();
React.useEffect(() => {
const { width } = ref.current.getBoundingClientRect();
document.title = `Width:${width}`;
}, []);
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
和之前一样,我们用 React 的 useRef hook 初始化 ref 对象,在 React 的 JSX 中使用它来将 ref 的当前属性赋值给 DOM 节点,最后通过 React 的 useEffect hook 读取组件的第一次渲染的元素宽度。你应该能够在浏览器的选项卡中看到你的元素的宽度作为标题。
不过,读取 DOM 节点的大小仅适用于初始渲染。如果您想每次状态更改时都读取它,因为毕竟这会改变我们 HTML 元素的大小,您可以将状态作为依赖变量提供给 React 的 useEffect Hook。每当状态(此处为 text)改变时,都会从 HTML 元素中读取元素的新大小并将其写入文档的 title 属性中:
function ComponentWithRefRead() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useRef();
React.useEffect(() => {
const { width } = ref.current.getBoundingClientRect();
document.title = `Width:${width}`;
}, [text]);
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
这两个例子都使用了 React 的 useEffect hook 来处理 ref 对象。我们可以通过使用 callback ref 来避免这个问题。
React Callback REF
对于前面的例子,更好的方法是使用所谓的 callback ref。有了 callback ref,你就不必再使用 useEffect 和 useRef,因为 callback ref 让你在每次渲染时都能访问 DOM 节点:
function ComponentWithRefRead() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = (node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
document.title = `Width:${width}`;
};
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
callback ref 只是一个函数,可以用于 JSX 中的 HTML 元素的 ref 属性。这个函数可以访问 DOM 节点,只要在 HTML 元素的 ref 属性上使用它就会被触发。本质上,它所做的与前面的副作用相同,但这一次 callback ref 本身通知我们它已附加到 HTML 元素。
在使用 useRef + useEffect 组合之前,可以在 useEffect 的依赖项数组的帮助下运行副作用若干次。你也可以通过 React 的 useCallback hook 来增强 callback ref,使它只在组件的第一次渲染时运行:
function ComponentWithRefRead() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useCallback((node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
document.title = `Width:${width}`;
}, []);
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
这里,你还可以更具体地使用 useCallback hook 的依赖项数组。例如,只有当状态(这里是 text)发生变化时,才执行 callback ref 的回调函数,当然,还有在组件的第一次渲染:
function ComponentWithRefRead() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useCallback((node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
document.title = `Width:${width}`;
}, [text]);
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
然而,我们最终会再次得到与之前没有使用 React 的 useCallback hook,只在适当的地方有普通的 callback ref 时相同的行为,——这在每次渲染时都会被调用。
React ref 用于读写操作
到目前为止,我们只将 DOM ref 用于读操作(例如读取 DOM 节点的大小)。还可以修改引用的 DOM 节点(写操作)。下一个例子向我们展示了如何在不管理任何额外的 React 状态的情况下使用 React 的 ref 样式:
function ComponentWithRefReadWrite() {
const [text, setText] = React.useState('Some text ...');
function handleOnChange(event) {
setText(event.target.value);
}
const ref = (node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
if (width >= 150) {
node.style.color = 'red';
} else {
node.style.color = 'blue';
}
};
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
可以对这个引用的 DOM 节点上的任何属性执行此操作。重要的是要注意,通常不应该这样使用 React,因为它是声明式的。相反,你可以使用 React 的 useState hook 来设置一个布尔值:你是想将文本涂成红色还是蓝色。然而,出于性能方面的原因,有时直接操作 DOM 并防止重新渲染是非常有用的。
为了便于学习,我们还可以在 React 组件中这样管理状态:
function ComponentWithImperativeRefState() {
const ref = React.useRef();
React.useEffect(() => {
ref.current.textContent = 0;
}, []);
function handleClick() {
ref.current.textContent = Number(ref.current.textContent) + 1;
}
return (
<div>
<div>
<span ref={ref} />
</div>
<button type="button" onClick={handleClick}>
Increase
</button>
</div>
);
}
不过,不建议你掉进这个兔子洞……本质上,它应该只向您展示如何使用 React 的 ref 属性和写操作来操作 React 中的任何元素。然而,为什么我们有 React 而不再使用普通的 JavaScript 呢?因此,React 的 ref 主要用于读操作。
本文介绍应该向您展示了如何通过使用 React 的 useRef hook 或 callback refs 来使用 React 的 ref 来引用 DOM 节点和实例变量。为了完整起见,我还想提一下 React 的 createRef()
顶级API,它相当于 React 类组件的 useRef()。还有其他称为 string ref 的refs,在React中已弃用。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!