
Effect의 기본적인 역할부터 시작하여, 불필요한 Effect를 피하는 방법, Effect를 올바르게 작성하는 방법 (의존성 및 클린업), 그리고 의존성 관련 일반적인 문제 해결 및 실험적인 기능(Effect 이벤트)까지 다룬다.
Effect의 정의와 목적: Effect는 컴포넌트 렌더링 이후 특정 코드를 실행하여 React 외부 시스템(브라우저 DOM, 네트워크, 서드파티 위젯 등)과 컴포넌트를 동기화하는 데 사용되는 React의 탈출구(escape hatch)이다. 이는 렌더링 자체에 의해 발생하는 부수 효과를 처리하기 위함이다.
이벤트 핸들러와 Effect의 차이점
제품 구매 요청과 같이 특정 시점에 한 번 발생해야 하는 로직에 적합하다.채팅 서버 연결과 같이 컴포넌트가 화면에 보이는 상태와 외부 시스템을 지속적으로 일치시켜야 하는 로직에 사용된다.불필요한 Effect를 제거하는 이유: 코드를 따라가기 더 쉽게 만들고, 실행 속도를 빠르게 하며, 에러 발생 가능성을 줄이기 위함이다. Effect는 주로 React 외부 시스템(브라우저 DOM, 네트워크 등)과 컴포넌트를 동기화하는 데 사용된다. 만약 외부 시스템이 관여하지 않고 단순히 다른 state나 props에 따라 컴포넌트의 state를 업데이트하려는 경우 Effect가 필요 없을 수 있다.
렌더링을 위해 데이터를 변환하는 경우
useMemo Hook을 사용하여 해당 계산 결과를 캐시할 수 있다. useMemo는 의존성이 변경되지 않는 한 이전 결과를 재사용.사용자 이벤트를 처리하는 경우
props 또는 state에 따라 state를 업데이트하는 경우
prop 변경 시 state 초기화 또는 조정
애플리케이션 초기화 로직
state 변경을 부모 컴포넌트에 알리는 경우
부모에게 데이터를 전달하는 경우
외부 저장소 구독
useSyncExternalStore Hook 사용하기: 이 Hook은 외부 저장소 구독을 위해 특별히 제작되었으며 Effect보다 에러가 덜 발생.데이터 가져오기 대체 방안
useE아니ffect Hook을 사용하여 컴포넌트 안에 선언한다. 기본적으로 Effect는 컴포넌트의 초기 렌더링(마운트)을 포함하여 모든 렌더링(커밋) 이후에 실행된다. 이는 종종 원치 않는 동작일 수 있으며, 불필요한 실행이나 무한 루프를 유발할 수 있다.
useEffect 호출의 두 번째 인자로 의존성 배열을 지정.useSyncExternalStore를 사용해야 한다.setCount(count + increment)처럼 현재 State(count, increment)를 읽어 다음 State를 계산하고 State를 업데이트하는 경우, 해당 State 변수가 Effect의 의존성이 되어 불필요한 재연결/재실행을 유발할 수 있다. 이를 피하려면 setMessages(msgs => [...msgs, receivedMessage])처럼 업데이터 함수를 set 함수에 전달해야 한다. 업데이터 함수는 React가 다음 렌더링 시 최신 State 값을 인수로 제공하므로 Effect 자체는 State 변수를 직접 읽을 필요가 없다.useEffectEvent Hook을 사용하여 Effect 이벤트로 추출할 수 있습니다.때로는 Effect 내부의 코드가 특정 값(props, State 등)을 읽어야 하지만, 해당 값의 변경에 Effect가 다시 동기화되기를 원치 않는 경우가 있다. 이러한 "비반응형" 로직 부분을 주변의 "반응형" Effect 로직으로부터 분리해야 할 필요가 있다.
useEffectEvent 선언하기useEffectEvent로 선언된 함수를 Effect 이벤트라고 한다. Effect 이벤트는 Effect 로직의 일부이지만, 이벤트 핸들러와 매우 유사하게 동작한다. 즉, 그 내부의 코드는 반응형이 아니며, 항상 props와 state의 최신 값을 읽는다. Effect 이벤트는 Effect 내부에서만 호출해야 하며, 다른 컴포넌트나 Hook에 전달해서는 안 된다.useEffectEvent와 같은 방법을 통해 Effect의 의존성 문제를 해결해야 한다. 린터 오류나 경고는 실제 버그를 나타낼 가능성이 높으므로, 린터를 억제하는 것은 버그를 숨기는 위험한 시도이다. 린터를 억제하면 React가 새로운 반응형 의존성에 대해 경고하지 않아 오래된 값(stale values)으로 인한 혼란스러운 버그가 발생할 수 있다. useEffectEvent를 사용하면 린터에 "거짓말"할 필요 없이 코드가 기대한 대로 동작한다.컴포넌트와 다른 Effect의 생명주기
각 Effect는 동기화 시작 및 중지 프로세스
Effect가 다시 동기화해야 하는 시기와 그 이유
각 Effect는 별개의 독립적인 동기화 프로세스
렌더링 결과물에 부착된 Effect와 그 클로저
Effect 사용 시점: Effect는 컴포넌트를 네트워크, 브라우저 DOM, 서드파티 라이브러리 등 외부 시스템과 동기화하기 위해 사용한다. 렌더링을 위한 데이터 변환, 사용자 이벤트 처리 (예: 버튼 클릭 시 API 호출), 단순히 다른 State를 기반으로 State 업데이트 등에는 Effect가 필요하지 않을 수 있다.
의존성의 결정: Effect의 의존성은 개발자가 선택하는 것이 아니라, Effect 내부에서 사용되는 모든 반응형 값(props, State, 컴포넌트 본문에서 계산된 변수)에 의해 결정된다. 의존성 목록은 코드를 반영해야 하며, 의존성을 변경하려면 코드를 수정해야 한다.
린터의 중요성: React 린터는 Effect가 읽는 모든 반응형 값이 의존성 목록에 포함되었는지 확인하여 혼란스러운 버그를 방지하는 데 핵심적인 역할을 한다. 린터 오류는 실제 버그를 나타낼 가능성이 높으므로, 린터 경고를 절대 억제해서는 안 된다. 대신 코드를 수정하여 해당 값이 의존성이 될 필요가 없도록 만들어야 한다.
Effect의 생명주기: 각 Effect는 컴포넌트의 생명주기(마운트, 업데이트, 마운트 해제)와 별개의 생명주기를 가지며, 외부 시스템과의 독립적인 동기화 프로세스 시작 및 중지를 담당한다. Effect가 반환하는 클린업 함수는 이 동기화를 중지하는 역할을 한다. React는 의존성 값이 변경될 때 Effect의 이전 인스턴스를 정리하고 새 인스턴스를 실행하여 다시 동기화한다. 개발 모드에서는 Effect가 두 번 실행되어 클린업 로직을 테스트하는 데 도움을 준다.
일반적인 문제 해결 패턴:
setCount(count + 1)), State 변수가 의존성이 되어 불필요한 재실행을 유발할 수 있다. 이를 피하려면 setMessages(msgs => [...msgs, receivedMessage])와 같이 업데이터 함수를 set 함수에 전달해야 한다.useEffectEvent Hook을 사용하여 비반응형 로직을 Effect 이벤트로 추출한다. Effect 이벤트는 이벤트 핸들러처럼 최신 props와 state를 읽지만 Effect에서 트리거된다. Effect 이벤트는 Effect 내부에서만 호출해야 하며 다른 컴포넌트나 Hook에 전달해서는 안 된다.