【React】ref

330 阅读3分钟

概述

使用 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;

image.png

ref 和 state 的区别:

refstate
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;

image.png

ref 的使用场景:

如果组件需要存储一些值,但不影响渲染逻辑,选择 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;

image.png

注意: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>
    </>
  );
}

image.png

image.png

想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件,使用 forwardRef API。

const MyInput = forwardRef((props, ref) => {
    return <input {...props} ref={ref} />;
});

image.png

React 中,每次更新都分为 两个阶段

  • 在 渲染 阶段, React 调用你的组件来确定屏幕上应该显示什么。
  • 在 提交 阶段, React 把变更应用于 DOM。

在第一次渲染期间,DOM 节点尚未创建,因此 ref.current 将为 null。在渲染更新的过程中,DOM 节点还没有更新,所以也不应该在此刻读取 ref。

更新 DOM 后,React 立即将它们设置到相应的 DOM 节点。

切换 dom 节点可以使用 条件渲染state 切换它的显示和隐藏。而ref.current.remove();可以将节点直接从 DOM 中删除。