useRef 简单易懂解析(九)你学会了吗,来试试这些挑战 - 修复去抖动

407 阅读3分钟

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

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

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

系列文章

修复去抖动

挑战 3(共 4 个):修复去抖动 在这个例子中,所有按钮点击处理程序都被“去抖动”了。如果我按下其中一个按钮,一秒钟后会弹出提示消息。如果你在等待消息时按下按钮,计时器将重置。因此,如果你多次快速单击同一个按钮,则直到你停止单击后,过一秒钟才会显示该消息。去抖动可以让你延迟一些动作,直到用户“停止做事”。

这个例子是有效的,但并不完全符合预期。因为这些按钮不是独立的。如果单击其中一个按钮,然后立即单击另一个按钮。预期是在延迟之后,将看到两个按钮的消息。但是现在的状况是,只显示最后一个按钮的消息。第一个按钮的消息丢失了。

那么为什么这些按钮会相互干扰?找到并修复这个问题。

import { useState } from 'react';

let timeoutID;

function DebouncedButton({ onClick, children }) {
  return (
    <button onClick={() => {
      clearTimeout(timeoutID);
      timeoutID = setTimeout(() => {
        onClick();
      }, 1000);
    }}>
      {children}
    </button>
  );
}

export default function Dashboard() {
  return (
    <>
      <DebouncedButton
        onClick={() => alert('Spaceship launched!')}
      >
        Launch the spaceship
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Soup boiled!')}
      >
        Boil the soup
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Lullaby sung!')}
      >
        Sing a lullaby
      </DebouncedButton>
    </>
  )
}

3

2

1

因为 timeoutID 变量在所有组件之间共享。所以单击第二个按钮会重置第一个按钮的 timeout。为了解决这个问题,你可以将 timeout 存在 ref 中。每个按钮都有自己的 ref,所以它们不会相互冲突。这样,快速单击两个按钮就将会显示两个消息。

import { useState, useRef } from 'react';

function DebouncedButton({ onClick, children }) {
  const timeoutRef = useRef(null);   // 这里
  return (
    <button onClick={() => {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = setTimeout(() => {
        onClick();
      }, 1000);
    }}>
      {children}
    </button>
  );
}

export default function Dashboard() {
  return (
    <>
      <DebouncedButton
        onClick={() => alert('Spaceship launched!')}
      >
        Launch the spaceship
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Soup boiled!')}
      >
        Boil the soup
      </DebouncedButton>
      <DebouncedButton
        onClick={() => alert('Lullaby sung!')}
      >
        Sing a lullaby
      </DebouncedButton>
    </>
  )
}

注意这里 ref 是在 DebouncedButton 组件中初始化的,这个例子来说没有问题,是因为组件不会重新进行 render(修改 ref 不会重新触发 render),如果会组件会 render 多次的话,需要把 ref 放到外面定义。