概述
使用 ref 引用值 – React 中文文档
希望组件“记住”某些信息,但又不想让这些信息更新时 触发新的渲染 时,可以使用 ref 。
也就是说 ref 对象 包裹的值 React 追踪不到的,他像是用来存储组件信息的秘密“口袋”。
与 state 相同的是,React 会在每次重新渲染组件之间保留 ref。与 state 不同的是,设置 state 会重新渲染组件,更改 ref 不会!ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current
属性(也就是我们初始化的值)。
下面是一个计时器的示例:
需要追踪按下“开始”按钮的时间和当前时间,并用于视图渲染,因此保存在 state 中。
而取消现有的 interval,让它停止更新 now
state 变量。需要为其提供 interval ID, 由于 interval ID 不用于渲染,可以将其保存在 ref 中。
import {useRef, useState} from "react";
function App() {
const [startTime, setStartTime] = useState(null)
const [now, setNow] = useState(null)
const intervalRef = useRef(null);
function handleStart() {
// 设置开始时间和 当前时间
setStartTime(Date.now())
setNow(Date.now())
// 清除之前可能存在的定时器
clearInterval(intervalRef.current)
intervalRef.current = setInterval(() => {
setNow(Date.now())
}, 10)
}
function handleStop () {
clearInterval(intervalRef.current)
}
let secondsPassed = 0
if (startTime && now) {
secondsPassed = (now - startTime) / 1000
}
return (
<div className="App">
<h1>时间计时器,时间过去了:{secondsPassed.toFixed(3)}s</h1>
<button onClick={handleStart}>开始</button>
<button onClick={handleStop}>停止</button>
</div>
);
}
export default App;
ref 和 state 的区别:
ref | state |
---|---|
useRef(initialValue) 返回 { current: initialValue } | useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue] ) |
更改时不会触发重新渲染 | 更改时触发重新渲染。 |
可变 —— 你可以在渲染过程之外修改和更新 current 的值。 | “不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 |
你不应在渲染期间读取(或写入) current 值。 | 你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照。 |
如果将 useRef的值用于 试图渲染,将会发生什么呢?
可以看到值虽然改变了,但是视图并没有同步更新。
import React, {useRef} from 'react';
function ChildCom1(props) {
let countRef = useRef(0);
function handleClick() {
// 这样并未重新渲染组件!
countRef.current = countRef.current + 1;
console.log(countRef.current)
}
return (
<>
<h1>这是子组件</h1>
<button onClick={handleClick}>
你点击了 {countRef.current} 次
</button>
</>
);
}
export default ChildCom1;
ref 的使用场景:
- 存储 timeout ID
- 存储和操作 DOM 元素,我们将在 下一页 中介绍
- 存储不需要被用来计算 JSX 的其他对象。
如果组件需要存储一些值,但不影响渲染逻辑,选择 ref。
state 就像 每次渲染的快照,并且 不会同步更新。但是当你改变 ref 的 current 值时,它会立即改变。
ref 与 DOM
访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。需要一个指向 DOM 节点的 ref 来实现。
import React, {useRef} from 'react';
function ChildCom2(props) {
const inputRef = useRef(null)
function handleClick() {
inputRef.current.focus()
}
return (
<div>
<h1>这是子组件2</h1>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</div>
);
}
export default ChildCom2;
注意:React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!
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>
</>
);
}
想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件,使用 forwardRef
API。
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
React 中,每次更新都分为 两个阶段:
- 在 渲染 阶段, React 调用你的组件来确定屏幕上应该显示什么。
- 在 提交 阶段, React 把变更应用于 DOM。
在第一次渲染期间,DOM 节点尚未创建,因此 ref.current
将为 null
。在渲染更新的过程中,DOM 节点还没有更新,所以也不应该在此刻读取 ref。
更新 DOM 后,React 立即将它们设置到相应的 DOM 节点。
切换 dom 节点可以使用 条件渲染 和 state 切换它的显示和隐藏。而ref.current.remove();
可以将节点直接从 DOM 中删除。