美文网首页
第三六章 逃生舱口-使用 ref 引用值

第三六章 逃生舱口-使用 ref 引用值

作者: 深圳都这么冷 | 来源:发表于2023-02-23 11:22 被阅读0次

    使用 ref 引用值

    当你想让一个组件“记住”一些信息,但又不想让这些信息触发新的渲染时,你可以使用 ref。

    你将学习

    • 如何向组件添加引用
    • 如何更新 ref 的值
    • refs 与 state 有何不同
    • 如何安全地使用 refs

    向您的组件添加引用

    你可以通过从 React 导入 useRef Hook 来为你的组件添加一个 ref:

    import { useRef } from 'react';
    

    在您的组件内,调用 useRef Hook 并将您要引用的初始值作为唯一参数传递。 例如,这是对值 0 的引用:

    const ref = useRef(0);
    

    useRef 返回一个这样的对象:

    { 
      current: 0 // The value you passed to useRef
    }
    

    您可以通过 ref.current 属性访问该 ref 的当前值。 这个值是有意可变的,这意味着您可以读取和写入它。 它就像 React 不跟踪的组件的秘密口袋。 (这就是使它成为 React 单向数据流的“逃生舱门”的原因——更多内容见下文!)

    在这里,一个按钮将在每次点击时增加 ref.current:

    import { useRef } from 'react';
    
    export default function Counter() {
      let ref = useRef(0);
    
      function handleClick() {
        ref.current = ref.current + 1;
        alert('You clicked ' + ref.current + ' times!');
      }
    
      return (
        <button onClick={handleClick}>
          Click me!
        </button>
      );
    }
    

    ref 指向一个数字,但是,像 state 一样,您可以指向任何东西:一个字符串、一个对象,甚至是一个函数。 与 state 不同,ref 是一个普通的 JavaScript 对象,具有您可以读取和修改的 current 属性。

    请注意,组件不会在每次增量时重新渲染。 与状态一样,refs 在重新渲染之间由 React 保留。 但是,设置状态会重新渲染组件。 更改 ref 不会!

    示例:构建秒表

    您可以将 refs 和 state 组合在一个组件中。 例如,让我们制作一个秒表,用户可以通过按下按钮来启动或停止。 为了显示自用户按下“开始”以来经过了多长时间,您需要跟踪按下“开始”按钮的时间以及当前时间。 此信息用于渲染,因此您将保持它的状态:

    const [startTime, setStartTime] = useState(null);
    const [now, setNow] = useState(null);
    

    当用户按下“开始”时,您将使用 setInterval 每 10 毫秒更新一次时间:

    import { useState } from 'react';
    
    export default function Stopwatch() {
      const [startTime, setStartTime] = useState(null);
      const [now, setNow] = useState(null);
    
      function handleStart() {
        // Start counting.
        setStartTime(Date.now());
        setNow(Date.now());
    
        setInterval(() => {
          // Update the current time every 10ms.
          setNow(Date.now());
        }, 10);
      }
    
      let secondsPassed = 0;
      if (startTime != null && now != null) {
        secondsPassed = (now - startTime) / 1000;
      }
    
      return (
        <>
          <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
          <button onClick={handleStart}>
            Start
          </button>
        </>
      );
    }
    

    当按下“停止”按钮时,您需要取消现有interval,使其停止更新 now 状态变量。 您可以通过调用 clearInterval 来执行此操作,但您需要为其提供先前在用户按下 Start 时由 setInterval 调用返回的间隔 ID。 您需要将间隔 ID 保存在某处。 由于间隔 ID 不用于渲染,您可以将其保存在 ref 中:

    import { useState, useRef } 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());
    
        clearInterval(intervalRef.current);
        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>Time passed: {secondsPassed.toFixed(3)}</h1>
          <button onClick={handleStart}>
            Start
          </button>
          <button onClick={handleStop}>
            Stop
          </button>
        </>
      );
    }
    

    当一条信息用于渲染时,保持它的状态。 当一条信息仅由事件处理程序需要并且更改它不需要重新渲染时,使用 ref 可能更有效。

    refs 和 state 的区别

    也许你在想 refs 似乎没有 state 那么“严格”——你可以改变它们,而不是总是必须使用状态设置函数,例如。 但在大多数情况下,你会想要使用状态。 Refs 是一个你不会经常需要的“逃生口”。 以下是 state 和 refs 的比较:

    refs state
    useRef(initialValue) 返回 { current: initialValue } useState(initialValue) 返回状态变量的当前值和状态设置函数 ( [value, setValue])
    更改时不会触发重新渲染。 当您更改它时,触发器会重新呈现。
    可变——您可以在渲染过程之外修改和更新 current 的值。 “不可变”——你必须使用状态设置函数来修改状态变量来排队重新渲染。
    您不应该在渲染期间读取(或写入)current 值。 您可以随时读取状态。 但是,每个渲染都有自己的状态快照,不会改变。

    这是一个用状态实现的计数器按钮:

    import { useState } from 'react';
    
    export default function Counter() {
      const [count, setCount] = useState(0);
    
      function handleClick() {
        setCount(count + 1);
      }
    
      return (
        <button onClick={handleClick}>
          You clicked {count} times
        </button>
      );
    }
    

    因为显示的是count,所以为它使用状态值是有意义的。 当使用 setCount() 设置计数器的值时,React 重新渲染组件并且屏幕更新以反映新的计数。

    如果你试图用 ref 来实现它,React 永远不会重新渲染组件,所以你永远不会看到计数发生变化! 查看单击此按钮如何不更新其文本:

    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>
      );
    }
    

    这就是为什么在渲染期间读取 ref.current 会导致代码不可靠。 如果需要,请改用状态。

    深度阅读:useRef内部如何工作?

    虽然 useState 和 useRef 都是 React 提供的,但原则上 useRef 可以在 useState 之上实现。 你可以想象在 React 内部,useRef 是这样实现的:

    // Inside of React
    function useRef(initialValue) {
      const [ref, unused] = useState({ current: initialValue });
      return ref;
    }
    

    在第一次渲染期间,useRef 返回 { current: initialValue }。 该对象由 React 存储,因此在下一次渲染期间将返回相同的对象。 请注意此示例中未使用状态设置器的方式。 这是不必要的,因为 useRef 总是需要返回同一个对象!

    React 提供了 useRef 的内置版本,因为它在实践中很常见。 但是您可以将其视为没有设置器的常规状态变量。 如果你熟悉面向对象的编程,refs 可能会让你想起实例字段——但你写的不是 this.something 而是somethingRef.current。

    何时使用ref

    通常,当您的组件需要“跳出”React 并与外部 API 通信时,您将使用 ref——通常是不会影响组件外观的浏览器 API。 以下是其中一些罕见的情况:

    • 存储超时 ID
    • 存储和操作 DOM 元素,我们将在下一页介绍
    • 存储计算 JSX 不需要的其他对象。

    如果您的组件需要存储一些值,但不影响渲染逻辑,请选择 refs。

    ref的最佳实践

    遵循这些原则将使您的组件更具可预测性:

    • 将 refs 视为逃生舱口。 当您使用外部系统或浏览器 API 时,引用很有用。 如果您的大部分应用程序逻辑和数据流都依赖于引用,您可能需要重新考虑您的方法。
    • 不要在渲染期间读取或写入 ref.current。 如果在渲染过程中需要一些信息,请改用状态。 由于 React 不知道 ref.current 何时更改,即使在渲染时读取它也会使组件的行为难以预测。 (唯一的例外是像 if (!ref.current) ref.current = new Thing() 这样的代码,它只在第一次渲染期间设置一次 ref。)

    React 状态的限制不适用于 refs。 例如,状态就像每个渲染的快照,不会同步更新。 但是当你改变 ref 的当前值时,它会立即改变:

    ref.current = 5;
    console.log(ref.current); // 5
    

    这是因为 ref 本身是一个普通的 JavaScript 对象,所以它的行为就像这样。

    当你使用 ref 时,你也不需要担心避免变异。 只要你改变的对象不用于渲染,React 就不会关心你对 ref 或它的内容做了什么。

    引用和 DOM

    您可以将 ref 指向任何值。 但是,ref 最常见的用例是访问 DOM 元素。 例如,如果您想以编程方式聚焦输入,这会很方便。 当你将 ref 传递给 JSX 中的 ref 属性时,如 <div ref={myRef}>,React 会将相应的 DOM 元素放入 myRef.current 中。 您可以在使用 Refs 操作 DOM 中阅读更多相关信息。

    回顾

    • Refs 是一个逃生舱口,用于保存不用于渲染的值。 你不会经常需要它们。
    • ref 是一个纯 JavaScript 对象,具有一个名为 current 的属性,您可以读取或设置该属性。
    • 你可以通过调用 useRef Hook 让 React 给你一个 ref。
    • 与 state 一样,refs 允许您在组件重新渲染之间保留信息。
    • 与状态不同,设置 ref 的当前值不会触发重新渲染。
    • 不要在渲染期间读取或写入 ref.current。 这使您的组件难以预测。

    相关文章

      网友评论

          本文标题:第三六章 逃生舱口-使用 ref 引用值

          本文链接:https://www.haomeiwen.com/subject/zbznkdtx.html