Skip to content

脱围机制 Escape Hatches

脱围机制是 React 提供给开发者一种在必要时刻绕过框架直接使用较低级别的 API 或原生 DOM API 进行底层操作的机制。例如:操作浏览器 API 实现输入框聚集,或者存储和操作 DOM 元素等。

Ref

组件中用于存储与渲染无关的值可以使用 ref,它是一个普通的 JavaScript 对象,在更改其值时不会触发组件重新渲染。如果试图使用 ref 存储渲染所需的状态,那么组件的行为将难以预测。

使用 ref 引用值:

  1. react 导入 useRef Hook API:
jsx
import { useRef } from 'react';
  1. 在组件中声明 ref ,并提供初始值:
jsx
const ref = useRef(0); // 提供 0 为 ref 的初始值
  1. 读取/修改 ref 当前的值:
jsx
console.log(ref.current); // 读取 ref 当前的值

ref.current = ref.current + 1; // 读取并修改 ref 当前的值

示例:制作秒表

jsx
import { useRef, useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    // 清除已有的 interval
    clearInterval(intervalRef.current);
    // 记录新的 interval ID
    intervalRef.current = setInterval(() => {
      setNow(Date.now);
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>时间过去了:{secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>开始</button>
      <button onClick={handleStop}>停止</button>
    </>
  );
}

使用 ref 操作 DOM:

在 React 中 DOM 渲染由框架自动处理,因此在组件中通常不需要操作 DOM。对于 React 没有内置的一些操作 DOM 的情况,例如,让文本输入框获得焦点、滚动元素或测量元素的尺寸等就需要用到 ref 来指向 DOM ,从而使用原生 API 操作。

  1. react 导入 useRef Hook API:

    jsx
    import { useRef } from 'react';
  2. 在组件中声明 ref ,并提供初始值(操作 DOM 时,初始值通常设置为 null):

    jsx
    const myRef = useRef(null);
  3. 将 ref 作为属性设置到目标 DOM 节点的 JSX 标签:

    jsx
    <div ref={myRef}></div>

示例:文本输入框获取焦点

通过 ref 与 DOM 绑定实现点击按钮时调用 focus() 函数使得文本框获得焦点。

jsx
import { useRef } from 'react';

export default function InputFocus() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>获取焦点</button>
    </>
  );
}

示例:滚动至一个元素:

通过 ref 与 DOM 绑定实现操作 scrollIntoView API 滚动元素的效果。

jsx
import { useRef } from 'react';

export default function CatFriends() {
  const firstCarRef = useRef(null);
  const secondCarRef = useRef(null);
  const thirdCarRef = useRef(null);

  function handleScrollToFirstCat() {
    firstCarRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center',
    });
  }

  function handleScrollToSecondCat() {
    secondCarRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center',
    });
  }

  function handleScrollToThirdCat() {
    thirdCarRef.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
              ref={firstCarRef}
              alt="Tom"
              src="https://picsum.photos/seed/picsum/200/200"
            />
          </li>
          <li>
            <img
              ref={secondCarRef}
              alt="Maru"
              src="https://picsum.photos/seed/picsum/300/200"
            />
          </li>
          <li>
            <img
              ref={thirdCarRef}
              alt="Jellylorum"
              src="https://picsum.photos/seed/picsum/250/200"
            />
          </li>
        </ul>
      </div>
    </>
  );
}

示例:访问另一个组件的 DOM 节点:

React 默认不允许组件访问其他组件的 DOM 节点(包括子组件)。想要暴露组件 DOM 节点需要使用到 forwardRef API,将 ref 转发给另一个组件。

jsx
import { useRef, forwardRef } from 'react';

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

export default function SubInputFocus() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef}></MyInput>
      <button onClick={handleClick}>获取焦点</button>
    </>
  );
}

ref 与 state 的不同:

在 React 框架中大多数情况仍建议使用 state,ref 作为一种“脱围机制”,仅在存储与渲染无关的值时使用。

refstate
useRef(initialValue) 返回useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数([value, setValue])
更改时不会触发重新渲染更改时触发重新渲染
可变——可以在渲染过程之外修改和更新 current 的值“不可变”——必须使用 state 设置函数修改 state 变量,从而排队重新渲染
不应在渲染期间读取(或写入)current 值可以随时读取 state,但每次渲染都有自己不变的 state 快照

Effect

开发环境 Effect 执行两次

Hook