react 에서 debounce, throttle 이용하기

wiz·2022년 5월 16일
0

기본 개념

debounce

짧은 간격으로 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 하는 것


  • - 검색어를 빠르게 입력하는 동안 검색 잠시 미루기

throttle

마지막 함수가 호출된 후 일정 시간이 지나기 전까지 다시 호출되지 않도록 하는 것


  • - 스크롤하거나 윈도우 사이즈 조절할 때 이벤트 핸들러

적용

lodash 라이브러리 이용

import type { NextPage } from 'next'
import React, {useCallback, useEffect, useMemo, useState} from 'react'
import {debounce, throttle} from "lodash"

// ref: https://dmitripavlutin.com/react-throttle-debounce/
const TDOnePage: NextPage = () => {
  // debounce
  const [value, setValue] = useState("")

  const logInput = useCallback((newValue: string)=>{
    console.log(newValue)
  },[])

  // as default option, 
  // leading: false
  // trailing: true
  const debouncedLogInput = useMemo(()=>debounce(logInput, 500), [logInput])

  useEffect(()=>{
    debouncedLogInput(value)
    // logInput(value)
  },[debouncedLogInput, value])

  const handleChangeInput: React.ChangeEventHandler<HTMLInputElement> = useCallback((event)=>{
    setValue(event.currentTarget.value)
  },[])

  // throttle
  const logScroll = useCallback(()=>{
    console.log("scrolling")
  },[])

  // as default option, 
  // leading: true
  // trailing: true
  const throttledLogScroll = useMemo(()=>throttle(logScroll, 2000), [logScroll])

  const handleScroll = useCallback(()=>{
    throttledLogScroll()
  },[throttledLogScroll])

  useEffect(() => {
    console.log("window", window)
    window.addEventListener('scroll', handleScroll)
    return () => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [handleScroll])

  useEffect(()=>{
    return () => {
      debouncedLogInput.cancel()
      throttledLogScroll.cancel()
    }
  },[debouncedLogInput, throttledLogScroll])

  return (
    <div style={{height: 5000}}>
      <input onChange={handleChangeInput}/>
    </div>
  )
}

export default TDOnePage

간단히 함수 구현

import type { NextPage } from 'next'
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'

const TDTwoPage: NextPage = () => {
  // debounce
  const [value, setValue] = useState("")

  const logInput = useCallback((newValue: string)=>{
    console.log(newValue)
  },[])

  const logInputRef = useRef(logInput)

  useEffect(()=>{
    logInputRef.current = logInput
  }, [logInput])

  const [debouncedLogInput, cancelDebouncedLogInput] = useMemo(()=>
    debounce((...args: Parameters<typeof logInput>)=>logInputRef.current(...args), 500)
  , [])

  useEffect(()=>{
    return () => {
      cancelDebouncedLogInput()
    }
  },[cancelDebouncedLogInput])

  useEffect(()=>{
    debouncedLogInput(value)
    // logInput(value)
  },[debouncedLogInput, value])

  const handleChangeInput: React.ChangeEventHandler<HTMLInputElement> = useCallback((event)=>{
    setValue(event.currentTarget.value)
  },[])

  // throttle
  const logScroll = useCallback(()=>{
    console.log("scrolling")
  },[])

  const logScrollRef = useRef(logScroll)

  useEffect(()=>{
    logScrollRef.current = logScroll
  }, [logScroll])

  const [throttledLogScroll, cancelThrottledLogScroll] = useMemo(()=>
    throttle((...args: Parameters<typeof logScroll>)=>logScrollRef.current(...args), 500)
  , [])

  useEffect(()=>{
    return () => {
      cancelThrottledLogScroll()
    }
  },[cancelThrottledLogScroll])


  const handleScroll = useCallback(()=>{
    throttledLogScroll()
  },[throttledLogScroll])

  useEffect(() => {
    console.log("window", window)
    window.addEventListener('scroll', handleScroll)
    return () => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [handleScroll])

  return (
    <div style={{height: 5000}}>
      <input onChange={handleChangeInput}/>
    </div>
  )
}

export default TDTwoPage


const debounce = <Callback extends (...args: any) => any>(callback: Callback, delay: number) => {
  let timeOutId: ReturnType<typeof setTimeout> | null = null;
  
  // make order to call callback after delay, 
  // but if it's called again within delay,
  // it increase entire delay
  const debouncedCallback = (...args: Parameters<Callback>) => {
    if (timeOutId) {
      clearTimeout(timeOutId)
    }

    timeOutId = setTimeout(()=>{
      callback(...args)
      timeOutId = null
    }, delay)
  }

  const cancel = () => {
    if (!timeOutId) return 

    clearTimeout(timeOutId)
  }

  return [debouncedCallback, cancel] as const
}

const throttle = <Callback extends (...args: any) => any>(callback: Callback, delay: number) => {
  let timeOutId: ReturnType<typeof setTimeout> | null = null;

  // make order to call callback after delay,
  // at the same time, it disabled incoming call for it's delay
  const throttledCallback = (...args: Parameters<Callback>) => {
    if (timeOutId) return;

    timeOutId = setTimeout(()=>{
      callback(...args)
      timeOutId = null
    }, delay)
  }

  const cancel = () => {
    if (!timeOutId) return 

    clearTimeout(timeOutId)
  }

  return [throttledCallback, cancel] as const
}
profile
성장 중인 프론트엔드 개발자

0개의 댓글