iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

debounce 都提到了,怎麼能不提 throltte 呢?

情境

在玩 Pokemon Go 打對戰的時候,有時候想著按快一點把對方打趴,殊不知再怎麼快,你的神奇寶貝似乎也快不上你。

其他遊戲也有類似情境,例如點擊時角色會進行攻擊,按快一點角色還是會乖乖跑完第一下的動畫,等動畫完成之後,等你按「真正」的第二下才會繼續動作 (儘管前面按了三千多下),或像是連續點擊的技能,每點一下會有1秒的CD光圈跑完才能繼續按第二下,CD時間點擊了也不會有任何反應。

但有時候戰況激烈時,為了拚那 0.1 秒的死活真的是會不停瘋狂的按,為了這一刻,我們來製作一個點擊練習器吧!

刻意練習 throlttle (X)
製作無意義的功能 (O)

功能與描述

首先,我們需要一個視覺化的量表,Chakra-UI 有 <CircularProgress /> ,將它小小改造一下變成可點擊的按鈕。

(好的,看起來很欠按)

期望是:

  • 量表會從 0 單位成長到 100 單位 (意即填滿圓圈)
  • 每次點擊增加 1 單位
  • 重點: 模仿上述情境加入 throlttle 來使點擊過程更加焦慮(?
  • 加入計時與重置

開始!

首先,先把點擊功能製作出來,大概的樣子:

function Example() {
  const [value, setValue] = useState(0)

  const handleClick = () => {
    setValue((prev) => {
      const nextValue = prev + 1

      return nextValue >= 100 ? 100 : nextValue
    })
  }

  return (
    <Stack align="center">
      <CircularProgress value={value} onClick={handleClick} />
    </Stack>
  )
}

實作 useThroltte

function useThrottle(cb, time = 500) {
  const isEnableRef = useRef(true)

  return (...args) => {
    if (!isEnableRef.current) {
      return
    }
    isEnableRef.current = false

    cb(...args)
    setTimeout(() => {
      isEnableRef.current = true
    }, time)
  }
}

與 debounce 不同在於,throltte 是立即觸發,而等到 isEnableReftrue 時,才允許下一次的執行,而 debounce 則是只要一直觸發持續延遲執行。

一樣是使用了 useRefsetTimeout 搭配組合,當觸發執行回傳的 function 時,isEnableRef 會變為 true,一直到 setTimeout 跑完給定的秒數時,才會變回 false,這時 function 就可以再執行一次 (準確來說,function 是可以一直被執行,但因 isEnableRef 為 false 的關係則 early return,當變回 true 時,就能順利執行後續的程式)。

Params

Param Type Description
cb function callback,當 setTimeout 的 time 時間到時會更改 isEnableRef 的狀態
time number 單位ms, 定義 setTimeout 要 time 多久,default 為 500ms

Return

Return Type Description
() => {} function 執行的event,其中傳入的參數會進一步傳入一開始的cb

加進來

我們把原本的 setValue 傳入 useThrottle,並將回傳的 throttledEvent 替換原本 setValue 所放置的位置,也另外加上 <Timer/> 跟重置按鈕。

function Example() {
  const [value, setValue] = useState(0)

  const throttledEvent = useThrottle(setValue, 450)

  const handleClick = () => {
    throttledEvent((prev) => {
      const nextValue = prev + 1

      return nextValue >= 100 ? 100 : nextValue
    })
  }

  return (
    <Stack align="center">
      <Timer value={value} />
      <CircularProgress value={value} onClick={handleClick} />
      <Button onClick={() => setValue(0)} disabled={value !== 100}>
        RESET
      </Button>
    </Stack>
  )
}

然後就完成啦!

DEMO 在這裡

結語

恩,手指好酸


上一篇
[DAY 12] 自己的Hook自己做!能取消動作的 useEventControl!
下一篇
[DAY 14] 自己的Hook自己做!網頁常看到的 Dialog 其實不會自己關?!
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言