| title | impact | impactDescription | tags |
|---|---|---|---|
| Event Handlers를 Refs에 저장 | LOW | stable subscriptions | advanced, hooks, refs, event-handlers, optimization |
Event Handlers를 Refs에 저장
이 패턴은 React hooks에서 흔한 성능 문제를 해결합니다. event handlers가 dependency arrays에 포함되면 매 render마다 불필요한 re-subscriptions가 발생합니다.
Incorrect (handler 변경마다 재구독):
function useWindowEvent(event: string, handler: (e: Event) => void) {
useEffect(() => {
window.addEventListener(event, handler);
return () => window.removeEventListener(event, handler);
}, [event, handler]); // handler가 변경될 때마다 effect 재실행
}이로 인해 handler가 변경될 때마다 effect가 재실행되어 반복적인 add/remove 사이클이 발생합니다.
Correct (Refs 사용):
handler를 ref에 저장하고 별도로 업데이트하여 subscription effect를 stable하게 유지:
function useWindowEvent(event: string, handler: (e: Event) => void) {
const handlerRef = useRef(handler);
useEffect(() => {
handlerRef.current = handler;
}, [handler]);
useEffect(() => {
const listener = (e: Event) => handlerRef.current(e);
window.addEventListener(event, listener);
return () => window.removeEventListener(event, listener);
}, [event]); // handler가 dependency가 아님
}Correct (useEffectEvent 사용):
React의 최신 hook은 handler의 latest version을 항상 호출하는 stable function reference를 생성하는 더 깔끔한 대안을 제공합니다:
import { useEffectEvent } from "react";
function useWindowEvent(event: string, handler: (e: Event) => void) {
const onEvent = useEffectEvent(handler);
useEffect(() => {
window.addEventListener(event, onEvent);
return () => window.removeEventListener(event, onEvent);
}, [event]); // onEvent는 stable reference
}| title | impact | impactDescription | tags |
|---|---|---|---|
| Mount마다가 아닌 App을 한 번만 초기화 | LOW-MEDIUM | development에서 중복 초기화 방지 | initialization, useEffect, app-startup, side-effects |
Mount마다가 아닌 App을 한 번만 초기화
애플리케이션 전체 초기화 코드는 components 내 useEffect([]) hooks에 배치해서는 안 됩니다. components가 development 중 remount될 수 있어 effects가 예상치 못하게 재실행될 수 있기 때문입니다.
문제점:
초기화 logic이 빈 dependency array를 가진 component의 effect에서 실행되면 여러 번 실행될 수 있습니다:
- Development에서 두 번 (React의 Strict Mode)
- Component가 remount되면 다시 실행
Incorrect (Strict Mode에서 두 번 실행):
function App() {
useEffect(() => {
// development에서 두 번 실행됨!
loadFromStorage();
checkAuthToken();
initializeAnalytics();
}, []);
return <Main />;
}Correct (module-level guard):
초기화가 정확히 한 번 실행되도록 module-level guard 변수를 구현하세요:
let didInit = false;
function App() {
useEffect(() => {
if (didInit) return;
didInit = true;
loadFromStorage();
checkAuthToken();
initializeAnalytics();
}, []);
return <Main />;
}대안: Entry Module에서 Top-level 초기화:
// index.tsx 또는 main.tsx
loadFromStorage();
checkAuthToken();
initializeAnalytics();
const root = createRoot(document.getElementById("root")!);
root.render(<App />);이 접근법은 React의 application initialization patterns에 대한 공식 guidance와 일치합니다.
Reference: React - Initializing the application
| title | impact | impactDescription | tags |
|---|---|---|---|
| Stable Callback Refs에 useEffectEvent 사용 | LOW | effect 재실행 방지 | advanced, hooks, useEffectEvent, refs, optimization |
Stable Callback Refs에 useEffectEvent 사용
dependency arrays에 추가하지 않고 callbacks에서 latest values에 접근하세요.
문제점:
callbacks가 dependency arrays에 포함되면 effects가 불필요하게 재실행됩니다:
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState("");
useEffect(() => {
const timeout = setTimeout(() => onSearch(query), 300);
return () => clearTimeout(timeout);
}, [query, onSearch]); // onSearch가 dependency로 불필요한 재실행 유발
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}onSearch를 dependency로 추가하면 callback reference가 변경될 때마다 effect가 반복적으로 실행됩니다.
Correct (useEffectEvent로 stable reference 생성):
React의 useEffectEvent hook은 callback에 stable reference를 제공합니다:
import { useEffectEvent } from "react";
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState("");
const onSearchEvent = useEffectEvent(onSearch);
useEffect(() => {
const timeout = setTimeout(() => onSearchEvent(query), 300);
return () => clearTimeout(timeout);
}, [query]); // onSearchEvent가 dependency로 필요하지 않음
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}이 접근법은 latest callback에 대한 접근을 유지하고 stale closures를 방지하면서 불필요한 재실행을 제거합니다.
useEffectEvent 사용 시점:
- Subscriptions (window events, WebSocket, etc.)
- Debounced/throttled callbacks
- Effect 내에서 변경되는 props/state를 읽어야 하지만 재실행을 trigger하고 싶지 않을 때
Reference: React useEffectEvent (experimental)