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;