useRef 简单易懂解析(七)你学会了吗,来试试这些挑战 - 修复坏掉的聊天输入框

571 阅读2分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

翻译自:beta.reactjs.org/learn/refer…

当你希望组件“记住”一些信息,但是你不希望这些信息触发重新 render 的时候,你可以使用 ref,它像一个秘密的“口袋”,用于在组件中存储信息。

后面的文章主要是通过一些例子检验对 ref 的理解,实际在 react 官方博客上可以执行调试,文章主要是分析,可以对照着看:beta.reactjs.org/learn/refer…

系列文章

修复坏掉的聊天输入框(Fix a broken chat input)

挑战 1(共 4 个):修复坏掉的聊天输入框

输入一些信息并且单击 “Send”。您会注意到在看到 “Sent!” 提示框之前有 3 秒的延迟。在这个延迟期间,你可以看到一个 “Undo” 按钮,点击它。这个 “Undo” 按钮应该停止 “Sent!” 提示的弹出,它通过使用 handleSend 期间保存的 timeout ID 调用 clearTimeout 来完成这个操作。但是,即使在单击“Undo”后,“Sent!” 消息仍然出现。找出它不起作用的原因,并修复它。

import { useState } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  let timeoutID = null;

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('Sent!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}

来想一想如何修复吧~~

5

4

3

2

1

提示:像 let timeoutID 这样的常规变量不能在 re-render 之间“存活”,因为每次 render 都从头开始运行组件(并初始化它的变量)。是否应该将 timeoutID 保存在其他地方?

当组件 re-render 时(比如 set state 时),所有的局部变量都要从头初始化。这就是为什么不能将 timeout ID 保存在一个本地变量(如 timeoutID )中,然后期望将来另一个事件处理程序“看到”它。相反,将它存储在 ref 中,React 将在呈现之间保留该 ref。

下面我们来看修改后的结果:

import { useState, useRef } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  const timeoutRef = useRef(null);  // 这里保存在 ref 中

  function handleSend() {
    setIsSending(true);
    timeoutRef.current = setTimeout(() => {  // 设置 ref 的 current 属性
      alert('Sent!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutRef.current);  // 这里
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}