์ฆ์ ๋น๋๋ก ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ์๋ฌด๋ฐ ์ ์ฝ ์์ด ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ฉด ์ฑ๋ฅ ์ ํ์ ์ฌ์ฉ์ ๊ฒฝํ ์ ํ๋ฅผ ์ด๋ํฉ๋๋ค. ๋๋ฐ์ด์ฑ(Debouncing)๊ณผ ์ฐ๋กํ๋ง(Throttling)์ ์ฌ์ฉํ์ฌ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์คํ ๋น๋๋ฅผ ์ต์ ํํจ์ผ๋ก์จ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ณ ๋ถ๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํด์ผ ํฉ๋๋ค.
์ฆ์ ๋น๋๋ก ๋ฐ์ํ๋ ์ด๋ฒคํธ(์: scroll, resize, input, mousemove)์ ์๋ฌด๋ฐ ์ ์ฝ ์์ด ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ฉด, ์ด๋ฒคํธ ๋ฐ์ ์๋ง๋ค ํด๋น ํธ๋ค๋ฌ๊ฐ ์คํ๋์ด ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๋ค์ ์ผ๊ธฐํฉ๋๋ค:
์ด๋ฒคํธ ํธ๋ค๋ฌ์ ์คํ ๋น๋๋ฅผ ์ต์ ํํ๊ธฐ ์ํด **๋๋ฐ์ด์ฑ(Debouncing)**๊ณผ ์ฐ๋กํ๋ง(Throttling) ๊ธฐ๋ฒ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๋๋ฐ์ด์ฑ (Debouncing): ํน์ ์ด๋ฒคํธ๊ฐ ์ผ์ ์๊ฐ ๋์ ๋ค์ ๋ฐ์ํ์ง ์์ ๋๊น์ง ํจ์ ์คํ์ ์ง์ฐ์ํต๋๋ค. ๋ง์ง๋ง ์ด๋ฒคํธ ๋ฐ์ ์์ ์ผ๋ก๋ถํฐ ์ง์ ๋ delay ์๊ฐ ๋์ ์ถ๊ฐ ์ด๋ฒคํธ๊ฐ ์์ผ๋ฉด ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ์ฃผ๋ก ์
๋ ฅ ํ๋(๊ฒ์ ์๋ ์์ฑ, ์ ํจ์ฑ ๊ฒ์ฌ), ์๋์ฐ ๋ฆฌ์ฌ์ด์ฆ, ์คํฌ๋กค ์ด๋ฒคํธ ์ข
๋ฃ ์์ ์ ์ ์ฉํฉ๋๋ค.
์ฐ๋กํ๋ง (Throttling): ํน์ ์๊ฐ(interval) ๋์ ํจ์๊ฐ ์ต๋ ํ ๋ฒ๋ง ์คํ๋๋๋ก ์ ํํฉ๋๋ค. ์ด๋ฒคํธ๊ฐ ์๋ฌด๋ฆฌ ๋น ๋ฅด๊ฒ ๋ฐ์ํด๋ ์ง์ ๋ ์๊ฐ ๊ฐ๊ฒฉ ๋ด์์๋ ํจ์๊ฐ ํ ๋ฒ๋ง ์คํ๋ฉ๋๋ค. ์ฃผ๋ก ์คํฌ๋กค ์ด๋ฒคํธ(๋ฌดํ ์คํฌ๋กค), ๋ง์ฐ์ค ์ด๋, ๊ฒ์์ ๊ณต๊ฒฉ ๋ฒํผ ์ฐํ ๋ฑ ์ง์์ ์ธ ์ด๋ฒคํธ์ ์ ์ฉํฉ๋๋ค.
๊ตฌํ ๋ฐฉ๋ฒ: ์ผ๋ฐ JavaScript ํจ์๋ก ์ง์ ๊ตฌํํ๊ฑฐ๋, Lodash์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ์ ํธ๋ฆฌํฐ ํจ์(_.debounce, _.throttle)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์
๋๋ค. React ํ๊ฒฝ์์๋ useCallback ํ
๊ณผ useRef๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ ๋๋ฐ์ด์ค/์ฐ๋กํ ํจ์๊ฐ ์ปดํฌ๋ํธ ๋ผ์ดํ์ฌ์ดํด ๋์ ์ผ๊ด๋๊ฒ ์ ์ง๋๋๋ก ๊ด๋ฆฌํด์ผ ํฉ๋๋ค.
import React, { useState, useEffect } from 'react';
function SearchInput() {
const [searchText, setSearchText] = useState('');
const [results, setResults] = useState([]);
// ๋ฌธ์ ์ : input ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค(๊ธ์ ํ๋ํ๋ ์
๋ ฅ ์) API ํธ์ถ
// ์ฌ์ฉ์๊ฐ 'hello'๋ฅผ ์
๋ ฅํ๋ฉด 'h', 'he', 'hel', 'hell', 'hello' ์ด 5๋ฒ ํธ์ถ ๋ฐ์
const fetchSearchResults = async (query) => {
if (query.length < 2) {
setResults([]);
return;
}
console.log(`API ํธ์ถ: ${query}`);
// ์ค์ API ํธ์ถ ๋ก์ง (์์)
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
setResults(data);
};
const handleChange = (e) => {
const value = e.target.value;
setSearchText(value);
fetchSearchResults(value); // ๋ฌธ์ ์ : ๋งค ์
๋ ฅ๋ง๋ค ํธ์ถ
};
return (
<div>
<input
type="text"
value={searchText}
onChange={handleChange}
placeholder="๊ฒ์์ด๋ฅผ ์
๋ ฅํ์ธ์"
/>
<ul>
{results.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
</div>
);
}
export default SearchInput;
```
```jsx
import React, { useEffect, useState } from 'react';
function ScrollTracker() {
const [scrollPos, setScrollPos] = useState(0);
// ๋ฌธ์ ์ : ์คํฌ๋กค ์๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ณ , ์ด์ ๋ฐ๋ผ ์ํ ์
๋ฐ์ดํธ ๋ฐ ๋ฆฌ๋ ๋๋ง์ด ๊ณผ๋ํ๊ฒ ๋ฐ์
const handleScroll = () => {
setScrollPos(window.scrollY);
// ์คํฌ๋กค ์์น์ ๋ฐ๋ผ ์ถ๊ฐ ๋ก์ง (์: ํน์ ์คํฌ๋กค ์์น์์ UI ๋ณ๊ฒฝ ๋ฑ)
console.log('Scroll position:', window.scrollY);
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div style={{ height: '200vh', paddingTop: '50px' }}>
<h1>์คํฌ๋กค ํ
์คํธ</h1>
<p>ํ์ฌ ์คํฌ๋กค ์์น: {scrollPos}</p>
<div style={{ height: '100vh', background: 'lightgray' }}>
์คํฌ๋กคํ์ฌ ์ด ์์ญ์ ์ง๋๊ฐ์ธ์.
</div>
</div>
);
}
export default ScrollTracker;import React, { useState, useEffect, useCallback, useRef } from 'react';
import debounce from 'lodash.debounce'; // ๋๋ ์ง์ ๊ตฌํํ debounce ํจ์
function SearchInputOptimized() {
const [searchText, setSearchText] = useState('');
const [results, setResults] = useState([]);
const fetchSearchResults = async (query) => {
if (query.length < 2) {
setResults([]);
return;
}
console.log(`API ํธ์ถ: ${query}`);
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
setResults(data);
};
// useRef๋ฅผ ์ฌ์ฉํ์ฌ debounce ํจ์๋ฅผ ์ปดํฌ๋ํธ ์์ ์ฃผ๊ธฐ ๋์ ์ ์ง
// useEffect ๋ฐ์์ ์ง์ debounce๋ฅผ ํธ์ถํ๋ฉด ๋ ๋๋ง ์๋ง๋ค ์๋ก์ด ํจ์๊ฐ ์์ฑ๋์ด debounce๊ฐ ์ ๋๋ก ๋์ํ์ง ์์ ์ ์์
const debouncedFetchResults = useRef(
debounce((query) => fetchSearchResults(query), 500)
).current;
// ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ debounce ํ์ด๋จธ ํด๋ฆฌ์ด
useEffect(() => {
return () => {
debouncedFetchResults.cancel(); // Lodash debounce์ cancel ๋ฉ์๋
};
}, [debouncedFetchResults]);
const handleChange = (e) => {
const value = e.target.value;
setSearchText(value);
debouncedFetchResults(value); // ๋๋ฐ์ด์ค๋ ํจ์ ํธ์ถ
};
return (
<div>
<input
type="text"
value={searchText}
onChange={handleChange}
placeholder="๊ฒ์์ด๋ฅผ ์
๋ ฅํ์ธ์ (500ms ์ดํ ๊ฒ์)"
/>
<ul>
{results.map((item, index) => (
<li key={item.id || index}>{item.name}</li>
))}
</ul>
</div>
);
}
export default SearchInputOptimized;
```
```jsx
import React, { useEffect, useState, useRef } from 'react';
import throttle from 'lodash.throttle'; // ๋๋ ์ง์ ๊ตฌํํ throttle ํจ์
function ScrollTrackerOptimized() {
const [scrollPos, setScrollPos] = useState(0);
const handleScroll = () => {
setScrollPos(window.scrollY);
console.log('Scroll position:', window.scrollY);
};
// useRef๋ฅผ ์ฌ์ฉํ์ฌ throttle ํจ์๋ฅผ ์ปดํฌ๋ํธ ์์ ์ฃผ๊ธฐ ๋์ ์ ์ง
const throttledHandleScroll = useRef(
throttle(() => handleScroll(), 200)
).current; // 200ms๋ง๋ค ์ต๋ 1๋ฒ ์คํ
useEffect(() => {
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
throttledHandleScroll.cancel(); // Lodash throttle์ cancel ๋ฉ์๋
};
}, [throttledHandleScroll]);
return (
<div style={{ height: '200vh', paddingTop: '50px' }}>
<h1>์คํฌ๋กค ํ
์คํธ (์ต์ ํ๋จ)</h1>
<p>ํ์ฌ ์คํฌ๋กค ์์น: {scrollPos}</p>
<div style={{ height: '100vh', background: 'lightgray' }}>
์คํฌ๋กคํ์ฌ ์ด ์์ญ์ ์ง๋๊ฐ์ธ์.
</div>
</div>
);
}
export default ScrollTrackerOptimized;