「回顾 2022,展望 2023,我正在参与2022 年终总结征文大赛活动」
概念
当一条信息用于渲染时,将它保存在 state 中。当一条信息仅被事件处理器需要,并且更改它不需要重新渲染时,使用 ref
- ref 对象在组件的整个生命周期内持续存在
- 可以很方便地保存任何可变值,类似于一个 class 的实例属性
- 变更
.current属性不会引发组件重新渲染 - 通常,你将从事件处理器访问 refs。 如果你想使用 ref 执行某些操作,但没有特定的事件可以执行此操作,你可能需要一个 effect
何时使用 Refs
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方 DOM 库
- 存储 timeout ID
- 存储和操作 DOM 元素
- 存储不需要被用来计算 JSX 的其他对象
React 何时添加 refs
React 在组件挂载设置 ref.current。在更新 DOM 之前,React 将受影响的 ref.current 值设置为 null。更新 DOM 后,React 立即将它们设置到相应的 DOM 节点。
ref 的最佳实践
- 将 ref 视为应急方案。 当你使用外部系统或浏览器 API 时,ref 很有用。如果你很大一部分应用程序逻辑和数据流都依赖于 ref,你可能需要重新考虑你的方法。
- 不要在渲染过程中读取或写入
ref.current。 唯一的例外是像if (!ref.current) ref.current = new Thing()这样的代码,它只在第一次渲染期间设置一次 ref。
使用
事件处理器中清除循环定时器
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
function handleCancelClick() {
clearInterval(intervalRef.current);
}
}
使用 ref 操作 DOM
避免更改由 React 管理的 DOM 节点。
import { useRef } from 'react';
const myRef = useRef(null);
<div ref={myRef}>
myRef.current.scrollIntoView();
访问另一个组件的 DOM 节点(ref 转发)
默认情况下,React 不允许组件访问其他组件的 DOM 节点。
ref 可以是对象或者函数,没有传递 null
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
const MyInput = forwardRef(function MyInput (props, ref) {
return <input {...props} ref={ref} />;
});
比 ref 转发更高的灵活性
通过 props 明文传递 ref 给子组件的 ref 属性
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
const Parent = () => {
const inputElement = useRef(null);
return <CustomTextInput inputRef={inputElement} />;
};
注意
- 避免使用 refs 来做任何可以通过声明式实现来完成的事情
- 勿过度使用 Refs 尽量使用 state 提升
- 除了 初始化 外不要在渲染期间写入 或者读取
ref.current
避免重复创建 ref 的内容
// 每次渲染时都会调用 new VideoPlayer
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
回调 ref
绑定或解绑 DOM 节点的 ref 时运行某些代码
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
问答
ref 总是 null
转发组件忘记使用 ref
ref 指定的节点被隐藏了
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});
原理
useRef 内部是如何运行的?
尽管 useState 和 useRef 都是由 React 提供的,原则上 useRef 可以在 useState 的基础上实现。 你可以想象在 React 内部,useRef 是这样实现的:
// React 内部
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
ref 和 state
| ref | state |
|---|---|
useRef(initialValue)返回 { current: initialValue } | useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue]) |
| 更改时不会触发重新渲染 | 更改时触发重新渲染。 |
可变 —— 你可以在渲染过程之外修改和更新 current 的值。 | “不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 |
你不应在渲染期间读取(或写入) current 值。 | 你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照。 |