230712 Throttling & Debouncing

๋‚˜์œค๋นˆยท2023๋…„ 7์›” 12์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
19/55

Throttling & Debouncing

๐Ÿ“Œ Throttling & Debouncing?

์งง์€ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์—ฐ์†ํ•ด์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๊ณผ๋„ํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

๐Ÿ“Œ Throttling?

์งง์€ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์—ฐ์†ํ•ด์„œ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๋“ค์„ ์ผ์ •์‹œ๊ฐ„ ๋‹จ์œ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ์ฒ˜์Œ ๋˜๋Š” ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋งŒ ํ˜ธ์ถœ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๐Ÿ“Œ Debouncing?

์งง์€ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์—ฐ์†ํ•ด์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋‹ค๊ฐ€ ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ๋กœ๋ถ€ํ„ฐ ์ผ์ • ์‹œ๊ฐ„์ด ๊ฒฝ๊ณผํ•œ ํ›„์— ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

Throttling & Debouncing ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ

1) react-router-dom ํŒจํ‚ค์ง€ ์„ค์น˜

yarn add react-router-dom

2) pages > Home.jsx์™€ Company.jsx ์ƒ์„ฑ

3) App.jsx ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ผ์šฐํ„ฐ ๊ด€๋ จ ์„ค์ •

import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Company from "./pages/Company";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/company" element={<Company />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

4) Home.jsx์—์„œ Debouncing ์˜ˆ์‹œ ๊ตฌํ˜„

import React from "react";

const Home = () => {
  // Throttling๊ณผ Debouncing์„ ์ œ์–ดํ•˜๋Š” ํ‚ค
  // timerId๋ฅผ ๋ณ€ํ™”์‹œํ‚ค๋ฉด์„œ ๋‹ค๋ฃฐ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— let์œผ๋กœ ์„ ์–ธ
  // (1) timerId์˜ ์ดˆ๊ธฐ๊ฐ’์€ null
  let timerId = null;

  const throttle = (delay) => {};

  // Debouncing : ๋ฐ˜๋ณต์ ์ธ ์ด๋ฒคํŠธ ์ดํ›„, delay๊ฐ€ ์ง€๋‚˜๋ฉด function
  // 2000(์‹œ๊ฐ„)์„ ๋ฐ›์•„์˜ฌ ๋ณ€์ˆ˜ delay ํ•„์š”
  const debounce = (delay) => {
    // (5) timerId์€ null์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— clearTimeout์ด ๋™์ž‘ํ•จ
    if (timerId) {
      // ํ• ๋‹น๋˜์–ด ์žˆ๋Š” timerId์— ํ•ด๋‹นํ•˜๋Š” ํƒ€์ด๋จธ ์ œ๊ฑฐ
      // (6) ๋‹ค์‹œ setTimeout ์žฌํ• ๋‹น
      clearTimeout(timerId);
    }
    // (2) timerId๊ฐ€ null์ด๊ธฐ ๋•Œ๋ฌธ์— setTimeout์ด ๋™์ž‘ํ•จ
    // (7) ๋‹ค์‹œ 2์ดˆ๊ฐ€ ์นด์šดํŠธ ๋จ..!
    // delay๋งŒํผ ์ƒˆ๋กญ๊ฒŒ setTimeout ํ•ด์คŒ
    timerId = setTimeout(() => {
        // (3) 2์ดˆ ํ›„์— ์•„๋ž˜ ๋กœ์ง ์ˆ˜ํ–‰ 
        // (4) ๊ทผ๋ฐ ๋งŒ์•ฝ? 2์ดˆ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ๋ชป ํ•˜๊ณ  ํ•œ ๋ฒˆ ๋” ๋ˆ„๋ฅธ๋‹ค??? (timerId์€ null์ด ์•„๋‹˜)
      console.log(`๋งˆ์ง€๋ง‰ ์š”์ฒญ์œผ๋กœ๋ถ€ํ„ฐ ${delay}ms ์ง€๋‚ฌ์œผ๋ฏ€๋กœ API ์š”์ฒญ ์‹คํ–‰!`);
      timerId = null;
    }, delay);
  };

  return (
    <div
      style={{
        paddingLeft: 20,
        paddingRight: 20,
      }}
    >
      <h1>Button ์ด๋ฒคํŠธ ์˜ˆ์ œ</h1>
      {/* Throttling๊ณผ Debouncing์—๋Š” ๋ช‡ ์ดˆ๋ฅผ ๋”œ๋ ˆ์ด ๋‘˜ ๊ฒƒ์ธ์ง€ ๊ธฐ์ค€ ์‹œ๊ฐ„(ms)์ด ํ•ญ์ƒ ํ•„์š”ํ•จ! */}
      <button onClick={() => throttle(2000)}>Throttling ๋ฒ„ํŠผ</button>
      <button onClick={() => debounce(2000)}>Debouncing ๋ฒ„ํŠผ</button>
    </div>
  );
};

export default Home;

5) Home.jsx์—์„œ Throttling ์˜ˆ์‹œ ๊ตฌํ˜„

import React from "react";

const Home = () => {
  let timerId = null;

  // Throttling ํ•จ์ˆ˜
  const throttle = (delay) => {
    if (timerId) {
      // timerId๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ํ•จ์ˆ˜ ์ข…๋ฃŒ
      return;
    }
    console.log(`API์š”์ฒญ ์‹คํ–‰! ${delay}ms ๋™์•ˆ ์ถ”๊ฐ€ ์š”์ฒญ์€ ์•ˆ ๋ฐ›์Šต๋‹ˆ๋‹ค!`);
    timerId = setTimeout(() => {
      console.log(`${delay}ms ์ง€๋‚จ ์ถ”๊ฐ€ ์š”์ฒญ ๋ฐ›์Šต๋‹ˆ๋‹ค!`);
      timerId = null;
    }, delay);
  };

  const debounce = (delay) => {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      console.log(`๋งˆ์ง€๋ง‰ ์š”์ฒญ์œผ๋กœ๋ถ€ํ„ฐ ${delay}ms ์ง€๋‚ฌ์œผ๋ฏ€๋กœ API ์š”์ฒญ ์‹คํ–‰!`);
      timerId = null;
    }, delay);
  };

  return (
    <div
      style={{
        paddingLeft: 20,
        paddingRight: 20,
      }}
    >
      <h1>Button ์ด๋ฒคํŠธ ์˜ˆ์ œ</h1>
      {/* Throttling๊ณผ Debouncing์—๋Š” ๋ช‡ ์ดˆ๋ฅผ ๋”œ๋ ˆ์ด ๋‘˜ ๊ฒƒ์ธ์ง€ ๊ธฐ์ค€ ์‹œ๊ฐ„(ms)์ด ํ•ญ์ƒ ํ•„์š”ํ•จ! */}
      <button onClick={() => throttle(2000)}>Throttling ๋ฒ„ํŠผ</button>
      <button onClick={() => debounce(2000)}>Debouncing ๋ฒ„ํŠผ</button>
    </div>
  );
};

export default Home;

6) ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ

import React, { useEffect } from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";

const Home = () => {
  let timerId = null;
  const [state, setState] = useState(true);
  const navigate = useNavigate();

  // Throttling ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ๋ฐ”๋กœ ํŽ˜์ด์ง€ ์ด๋™์„ ํ•˜๋ฉด??
  // ์ด์ „ ํŽ˜์ด์ง€์—์„œ ์–ด๋– ํ•œ ์š”์ฒญ์„ ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ํ˜„์žฌ ํŽ˜์ด์ง€์—์„œ๋„ ์˜ํ–ฅ์„ ๋ผ์น˜๊ณ  ์žˆ์Œ -> ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์— ํ•ด๋‹น
  // useEffect์˜ return๋ฌธ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ!
  useEffect(() => {
    return () => {
      // ์–ธ๋งˆ์šดํŠธ ์‹œ (Home์ด๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ)
      // timerId๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด, clearTimeout์„ ํ†ตํ•ด์„œ timerId๋ฅผ ์—†์• ๋‹ฌ๋ผ! (๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐ)
      if (timerId) {
        clearTimeout(timerId);
      }
    };
  }, []);

  const throttle = (delay) => {
    if (timerId) {
      return;
    }
    // ์ž„์˜์˜ state๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฆฌ๋žœ๋”๋ง ์‹œ์ผœ์คŒ -> timerId๋Š” ๋ฌด์กฐ๊ฑด null -> Throttling ์ ์ƒ ์ž‘๋™ X
    setState(!state);
    console.log(`API์š”์ฒญ ์‹คํ–‰! ${delay}ms ๋™์•ˆ ์ถ”๊ฐ€ ์š”์ฒญ์€ ์•ˆ ๋ฐ›์Šต๋‹ˆ๋‹ค!`);
    timerId = setTimeout(() => {
      console.log(`${delay}ms ์ง€๋‚จ ์ถ”๊ฐ€ ์š”์ฒญ ๋ฐ›์Šต๋‹ˆ๋‹ค!`);
      timerId = null;
    }, delay);
  };

  const debounce = (delay) => {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      console.log(`๋งˆ์ง€๋ง‰ ์š”์ฒญ์œผ๋กœ๋ถ€ํ„ฐ ${delay}ms ์ง€๋‚ฌ์œผ๋ฏ€๋กœ API ์š”์ฒญ ์‹คํ–‰!`);
      timerId = null;
    }, delay);
  };

  return (
    <div
      style={{
        paddingLeft: 20,
        paddingRight: 20,
      }}
    >
      <h1>Button ์ด๋ฒคํŠธ ์˜ˆ์ œ</h1>
      <button onClick={() => throttle(2000)}>Throttling ๋ฒ„ํŠผ</button>
      <button onClick={() => debounce(2000)}>Debouncing ๋ฒ„ํŠผ</button>
      <div>
        <button
          onClick={() => {
            navigate("/company");
          }}
        >
          ํŽ˜์ด์ง€ ์ด๋™
        </button>
      </div>
    </div>
  );
};

export default Home;

lodash ์ ์šฉ

1) App.js ์„ธํŒ…ํ•˜๊ธฐ

import "./App.css";
import { useState } from "react";

function App() {
  const [searchText, setSearchText] = useState("");
  const [inputText, setInputText] = useState("");

  const handleSearchText = () => {};

  // ๊ณตํ†ตํ•จ์ˆ˜
  const handleChange = (event) => {
    setInputText(event.target.value);
    handleSearchText();
  };

  return (
    <div
      style={{
        paddingLeft: "20px",
        paddingRight: "20px",
      }}
    >
      <h1>๋””๋ฐ”์šด์‹ฑ ์˜ˆ์ œ</h1>
      <input
        placeholder="์ž…๋ ฅ๊ฐ’์„ ๋„ฃ๊ณ  ๋””๋ฐ”์šด์‹ฑ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์„ธ์š”."
        style={{ width: "300px" }}
        type="text"
        onChange={handleChange}
      />
      <p> Search Text : {searchText}</p>
      <p> Input Text : {inputText}</p>
    </div>
  );
}

export default App;

2) lodash ํŒจํ‚ค์ง€ ์„ค์น˜

yarn add lodash

3) lodash์™€ useCallback ์ ์šฉํ•˜๊ธฐ

import "./App.css";
import { useState, useCallback } from "react";
import _ from "lodash";

function App() {
  const [searchText, setSearchText] = useState("");
  const [inputText, setInputText] = useState("");

  // lodash์™€ useCallback ์ ์šฉํ•˜๊ธฐ
  const handleSearchText = useCallback(
    _.debounce((text) => {
      setSearchText(text);
    }, 2000)
  );

  // ๊ณตํ†ตํ•จ์ˆ˜
  const handleChange = (event) => {
    setInputText(event.target.value);
    handleSearchText(event.target.value);
  };

  return (
    <div
      style={{
        paddingLeft: "20px",
        paddingRight: "20px",
      }}
    >
      <h1>๋””๋ฐ”์šด์‹ฑ ์˜ˆ์ œ</h1>
      <input
        placeholder="์ž…๋ ฅ๊ฐ’์„ ๋„ฃ๊ณ  ๋””๋ฐ”์šด์‹ฑ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์„ธ์š”."
        style={{ width: "300px" }}
        type="text"
        onChange={handleChange}
      />
      <p> Search Text : {searchText}</p>
      <p> Input Text : {inputText}</p>
    </div>
  );
}

export default App;

4) custom debounce ๋งŒ๋“ค๊ธฐ

import "./App.css";
import { useState, useCallback } from "react";
import _ from "lodash";

function App() {
  const [searchText, setSearchText] = useState("");
  const [inputText, setInputText] = useState("");

  // custom debounce ๋งŒ๋“ค๊ธฐ
  const debounce = (callback, delay) => {
    let timerId = null;
    return (...args) => {
      if (timerId) clearTimeout(timerId);
      timerId = setTimeout(() => {
        callback(...args);
      }, delay);
    };
  };
  
  // useCallback์„ ํ†ตํ•ด ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ํ•ด์ฃผ๊ธฐ
  const handleSearchText = useCallback(
    debounce((text) => {
      setSearchText(text);
    }, 2000),
    []
  );

  // ๊ณตํ†ตํ•จ์ˆ˜
  const handleChange = (event) => {
    setInputText(event.target.value);
    handleSearchText(event.target.value);
  };

  return (
    <div
      style={{
        paddingLeft: "20px",
        paddingRight: "20px",
      }}
    >
      <h1>๋””๋ฐ”์šด์‹ฑ ์˜ˆ์ œ</h1>
      <input
        placeholder="์ž…๋ ฅ๊ฐ’์„ ๋„ฃ๊ณ  ๋””๋ฐ”์šด์‹ฑ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์„ธ์š”."
        style={{ width: "300px" }}
        type="text"
        onChange={handleChange}
      />
      <p> Search Text : {searchText}</p>
      <p> Input Text : {inputText}</p>
    </div>
  );
}

export default App;
profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ฅผ ๊ฟˆ๊พธ๋Š”

0๊ฐœ์˜ ๋Œ“๊ธ€