# React 中的 Ref
Table of Contents
组件被调用时会新建一个实例,ref 可以指向该实例,如果是原生 DOM 组件,我们可以得到 DOM 节点。
用 Refs 引用值
当你想一个组件记住一些信息,但你不想这些信息触发新的渲染,你就可以使用 ref。
使用
使用 useRef 可以添加一个新的 ref。
import { useRef } from 'react'
function App() { const ref = useRef(0) //...}useRef 会返回一个对象, 使用 ref.current 可以获取当前的值。
{ current: 0 // 使用 useRef 传递的值}State 区别
下面是 Ref 与 State 的区别对比
| Ref | State |
|---|---|
| useRef(initialValue) 返回 {current: initialValue} | useState(initialValue) 返回当前值和设置函数 [value, setValue] |
| 修改时候不会触发重新渲染 | 修改的时候触发重新渲染 |
| 可变的 - 可以直接修改更新值 | 不可变的 - 必须使用设置函数修改值等待重新渲染 |
| 在渲染的时候不要读取/修改 current | 任何时候都可以读取 state 值 |
一直强调修改 ref 不会触发渲染,以下是来自官方的一个例子 codesandbox
import { useRef } from 'react'
export default function Counter() { let countRef = useRef(0)
function handleClick() { // This doesn't re-render the component! countRef.current = countRef.current + 1 }
return <button onClick={handleClick}>You clicked {countRef.current} times</button>}内部实现
在 React 内部,可以想象 useRef 的实现如下:
function useRef(initialValue) { const [ref, unsed] = useState({ current: initialValue }) return ref}使用
当我们需要“走出” React 组件并与外部 API 通信时,可能会使用到 Ref。
- 保存 timeout IDs
- 保存并操纵 DOM 元素
- 保存不需要 JSX 计算的对象
引用 DOM
Ref 可以引用值,但是我们更多是的使用是引用 DOM 元素。
有时候我们需要访问 React 下面的 DOM 元素,如给 input 聚焦/失焦,滚动,或者测量位置,我们就需要使用 ref 引用到对应的 DOM 元素。
import { useRef } from 'react'
function App() { const myRef = useRef(null)
return <div ref={myRef}></div>}这样子我们就可以通过事件处理访问到 DOM 元素内置的浏览器 API,如:myRef.current.scrollIntoView();。
其他
forwardRef
当我们直接使用 Ref 给子组件的时候,经常会收到一个错误提示。以下是来自官方的例子 codesandbox
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}>Focus the input</button> </> )}在打印里我们看到如下的错误:
发生这种情况是因为,默认情况下,React 不允许组件访问另一个组件的 DOM 元素。如果需要暴露其 DOM 元素,就需要使用到 forwardRef API:
const MyInput = forwardRef((props, ref) => { return <input {...props} ref={ref} />})ref 列表
在列表中遍历循环声明 ref 并不会起作用,如:
<ul> {items.map((item) => { // Doesn't work! const ref = useRef(null) return <li ref={ref} /> })}</ul>但是可以通过 ref 使用 Map,在 Map 里面保存每个节点的相关信息。
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) { // Initialize the Map on first usage. itemsRef.current = new Map() } return itemsRef.current }
return ( <> <div> <ul> {catList.map((cat) => ( <li key={cat.id} ref={(node) => { const map = getMap() if (node) { map.set(cat.id, node) } else { map.delete(cat.id) } }} > <img src={cat.imageUrl} alt={'Cat #' + cat.id} /> </li> ))} </ul> </div> </> )}