사용자 입력마다 트리거 되는 이벤트레 맞춰 핸들러를 실행하는 것과 달리 일정 시간(time window)안에 발생하는 이벤트를 모아서 한번만 실행하는 debounce를 적용하는 방법
경험적으로 100ms~150ms 정도가 사용자 경험의 저하를 최소화 하면서, 성능을 개선할 수 있는 효과적인 시간이다.
import { useEffect, useRef } from "react"
/**
* @callback callbackFunc
* @param {any[]} args - arguments passed into callback
*/
/**
* Debounce function to reduce number executions
* @param {callbackFunc} cb - callback function to be executed
* @param {number} wait - number of milliseconds to delay function execution
* @param {any[]} deps - dependencies array
*/
const useDebounce = (cb, wait = 500, deps = []) => {
const timerRef = useRef(null)
useEffect(() => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => {
cb.apply(this, args)
}, wait)
return () => clearTimeout(timerRef.current)
/** used JSON.stringify(deps) instead of just deps
* because passing an array as a dependency causes useEffect
re-render infinitely
* @see {@link https://github.com/facebook/react/issues/14324}
*/
/* eslint-disable react-hooks/exhaustive-deps */
}, [cb, wait, JSON.stringify(deps)])
}
function Controlled() {
const [value, setValue] = useState()
const [user, setUser] = useState()
// Debounce our search
useDebounce(async () => {
try {
const [userDetails] = await fetch(`${API}?name=${value}`)
.then(res => res.json())
setUserDetails(prevDetails => ({ ...prevDetails, ...userDetails }))
} catch (error) {
console.log(error)
}
}, 500, [value])
const handleChange = event => {
setValue(event.target.value)
}
return (
<form id="search">
<label id="search-label" htmlFor="search-input">
Search for user details
</label>
<input
name="search"
type="search"
id="search-input"
value={value}
onChange={handleChange}
/>
</form>
)
}
import debounce from 'lodash-es/debounce';
function App({ initialValue }) {
const [value, setValue] = useState(initialValue);
const handleChange = debounce((e) => {
setValue(e.target?.value)
}, 150);
return <input type="text" value={value} onChange={handleChange} />;
}
memoization은 이전 값과 현재 값의 차이를 비교하기 때문에 계산 비용이 추가되는 trade-off가 있기에, 성능을 다소 향상시켜주는 것은 사실이지만 그 효과의 한계는 분명하다
function Button({ disabled = false, onClick, children }) {
return (
<button type="button" disabled={disabled} onClick={onClick}>
{children}
</button>
);
}
const MemoButton = React.memo(Button);
// -----------
function App({ initialValue }) {
const [value, setValue] = useState(initialValue);
const isDisabled = useMemo(() => {
return !value;
}, [value]);
const handleClick = useCallback(() => {
console.log('버튼을 클릭합니다');
}, []);
const handleChange = (e) => {
setValue(e.target?.value)
};
return (
<>
<input type="text" value={value} onChange={handleChange} />
<MemoButton disabled={isDisabled} onClick={handleClick}>
버튼
</MemoButton>
</>
);
}
참고