ref 是什么
- 在 React 中,ref 主要用于获取组件实例或访问 DOM 节点,也可以用于组件间通信。它是一个普通的 JavaScript 对象,拥有一个名为 current 的属性。
- 它的创建方式有两种,createRef 和 useRef。
createRef
调用 createRef 在类组件中声明一个 ref,createRef 不接收任何参数。调用 createRef 声明的 ref 会在组件每次渲染的时候重新创建,每次渲染都会返回一个新的引用。
import { Component, createRef } from 'react';
export default class Form extends Component {
inputRef = createRef();
handleClick = () => {
this.inputRef.current.focus();
}
render() {
return (
<>
<input ref={this.inputRef} />
<button onClick={this.handleClick}>
输入框聚焦
</button>
</>
);
}
}
useRef
函数组件中使用 useRef 来创建 ref。接收一个任意初始值,如字符串、对象,甚至是函数等。使用 useRef 创建的 ref 只会在组件首次渲染时创建,每次都会返回相同的引用,所以通过 ref 可以拿到最新的值。
useRef(initialValue) returns { current: initialValue }
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const textRef = useRef(text);
function handleChange(e) {
setText(e.target.value);
textRef.current = e.target.value;
}
function handleSend() {
setTimeout(() => {
alert('Sending: ' + textRef.current);
}, 3000);
}
return (
<>
<input
value={text}
onChange={handleChange}
/>
<button
onClick={handleSend}>
发送
</button>
<div>{text}</div>
</>
);
}
上面例子中,输入文本后点击发送,alert 会在3秒后显示,在这期间,如果修改文本内容,alert 会显示修改后的文本内容。
const ref = useRef() 相当于 const [ref, _] = useState(() => createRef(null))
什么时候用 ref
When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a ref.
当您希望组件“记住”某些信息,但不希望该信息触发新的渲染时,可以使用 ref。
ref 的使用场景
存储定时器 ID
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState(false);
// let timeoutID = null;
const timeoutRef = useRef(null);
function handleSend() {
setIsSending(true);
// timeoutID = setTimeout(() => {
// alert('Sent!');
// setIsSending(false);
// }, 3000);
timeoutRef.current = setTimeout(() => {
alert('hahaha!');
setIsSending(false);
}, 3000);
}
function handleUndo() {
setIsSending(false);
// clearTimeout(timeoutID);
clearTimeout(timeoutRef.current);
}
return (
<>
<input
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<button
disabled={isSending}
onClick={handleSend}>
{isSending ? '发送中...' : '发送'}
</button>
{isSending &&
<button onClick={handleUndo}>
取消
</button>
}
</>
);
}
上面例子输入文本后点击发送按钮,3秒后才会 alert 文本内容,点击取消按钮可以取消 alert 的弹出。
存储和操作 DOM 节点
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
输入框聚焦
</button>
</>
);
}
上面例子是通过点击按钮让输入框聚焦
import { useRef } from 'react';
export default function CatFriends() {
const firstCatRef = useRef(null);
const secondCatRef = useRef(null);
const thirdCatRef = useRef(null);
function handleScrollToFirstCat() {
firstCatRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
function handleScrollToSecondCat() {
secondCatRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
function handleScrollToThirdCat() {
thirdCatRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
return (
<>
<nav>
<button onClick={handleScrollToFirstCat}>
Tom
</button>
<button onClick={handleScrollToSecondCat}>
Maru
</button>
<button onClick={handleScrollToThirdCat}>
Jellylorum
</button>
</nav>
<div>
<ul>
<li>
<img
src="https://placekitten.com/g/200/200"
alt="Tom"
ref={firstCatRef}
/>
</li>
<li>
<img
src="https://placekitten.com/g/300/200"
alt="Maru"
ref={secondCatRef}
/>
</li>
<li>
<img
src="https://placekitten.com/g/250/200"
alt="Jellylorum"
ref={thirdCatRef}
/>
</li>
</ul>
</div>
</>
);
}
上面这个例子的按钮通过在相应的 DOM 节点上调用浏览器的 rollIntoView() 方法来将图像居中。
用于组件间的通信
访问子组件的方法
假设你有一个 ChildComponent,它有一个可以被父组件调用的方法 childMethod。
// ChildComponent.js
import React from 'react';
class ChildComponent extends React.Component {
childMethod() {
console.log('Child method called');
}
render() {
return <div>子组件</div>;
}
}
父组件可以使用 ref 来调用这个方法:
// ParentComponent.js
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const childRef = useRef();
const callChildMethod = () => {
childRef.current.childMethod();
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={callChildMethod}>调用子组件方法</button>
</div>
);
}
控制子组件的状态
如果子组件有内部状态或行为需要从父组件触发,可以通过 ref 实现。
// ChildComponent.js
import { useState, forwardRef, useImperativeHandle } from 'react';
const ChildComponent = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
// useImperativeHandle 钩子用于自定义 ref 暴露给父组件的实例值。它应与 forwardRef 一起使用。
useImperativeHandle(ref, () => ({
incrementCount() {
setCount((prevCount) => prevCount + 1);
},
}));
return <div>Count: {count}</div>;
});
父组件可以控制子组件的状态
// ParentComponent.js
import { useRef } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const childRef = useRef();
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={() => childRef.current.incrementCount()}>加</button>
</div>
);
}
注意:不推荐使用 ref 代替 props 和 state 来管理组件间的数据流
ref 和 state 的区别
注意事项
- useRef 是一个 hook,所以不能在循环、条件或 map() 调用中调用 useRef。如何在列表中使用 ref
参考文档:
react 官网