- useState와 useEffect는 React 함수형 컴포넌트의 대부분의 상태와 부작용을 한데 모아 기존의 많은 생명주기 메서드를 대체합니다.
- 이펙트에는 함수를 반환하는 정리 로직이 포함될 수 있으며, 이를 통해 메모리 누수 없이 구독, 간격 및 기타 외부 리소스를 관리할 수 있습니다.
- 의존성 배열은 각 효과가 실행되는 시점을 제어하며, 정확성과 성능의 균형을 유지하면서 해당 효과에 사용되는 모든 반응형 값을 포함해야 합니다.
- 책임에 따라 로직을 여러 개의 작은 useEffect로 분리하면 가독성이 향상되고 재사용이 용이해지며 무한 렌더링이나 잘못된 종속성 같은 일반적인 오류가 줄어듭니다.
React를 매일 사용한다면 useState와 useEffect를 숙달하는 것은 선택 사항이 아닙니다.이건 거의 매일같이 하는 일이죠. 이 두 가지 훅은 간단한 카운터부터 HTTP 요청, 이벤트 구독, 애니메이션 등을 포함하는 복잡한 인터페이스까지, 현대 애플리케이션의 대부분의 상태 및 부작용 로직을 처리합니다.
문제는 이러한 기능을 "눈대중으로" 사용하기가 매우 쉽고, 결국 메모리 누수, 무한 렌더링 또는 작동하지 않아야 할 때 효과가 발동되는 등의 문제가 발생할 수 있다는 점입니다.이 글에서는 useState와 특히 useEffect를 올바르게 사용하는 방법을 차근차근 살펴보겠습니다. useState와 useEffect가 정확히 무엇을 하는지, 컴포넌트 생명주기와 어떤 관련이 있는지, 정리 작업은 어떻게 처리하는지, 의존성 배열을 사용하여 성능을 최적화하는 방법, 그리고 고급 패턴을 어렵지 않게 적용하는 방법까지 알아보겠습니다.
Hooks란 무엇이며 useState와 useEffect는 왜 그렇게 중요한가요?
React 16.8에서 Hooks가 등장하여 클래스 없이도 상태 및 기타 기능을 사용할 수 있게 되었습니다.확장하는 대신에 React.Component 그리고 생명주기 메서드를 다룰 때는 Hooks를 호출하는 함수들을 사용합니다. useState y useEffect 내부.
useState는 함수형 컴포넌트 내에서 로컬 상태를 사용할 수 있게 해주는 훅입니다.이 메서드는 값과 해당 값을 업데이트하는 함수를 반환하며, 상태를 업데이트할 때마다 컴포넌트가 새롭게 렌더링됩니다. 이는 카운터, 폼, 로딩 플래그 등과 같은 동적 인터페이스를 구현하는 데 있어 기본이 됩니다.
반면 useEffect는 렌더링 후 보조 효과를 실행할 수 있도록 해주는 훅입니다.여기에는 데이터 요청, 웹소켓 구독, DOM 리스너, 타이머, 외부 라이브러리와의 통합 또는 다른 옵션이 없을 때의 수동 DOM 업데이트와 같은 것들이 포함됩니다.
클래스 컴포넌트를 사용해 본 경험이 있다면, useEffect는 componentDidMount, componentDidUpdate, componentWillUnmount의 조합으로 생각할 수 있습니다.하지만 이 모든 기능은 단일 선언적 API로 제공됩니다. 핵심은 별도의 "어셈블리" 또는 "업데이트" 단계가 아닌 "렌더링 후에 발생하는 효과"라는 관점에서 생각하는 것입니다.
useEffect가 개념적으로 어떻게 작동하는지
기본적인 useEffect 시그니처는 매우 간단합니다. useEffect(configuración, dependencias?)컴포넌트가 그려진 후 실행될 구성 함수와, 선택적으로 해당 효과가 반복될 시기를 결정하는 종속성 목록을 전달합니다.
설정 함수는 이펙트의 로직을 집중시킵니다. 예를 들어, 특정 이벤트 구독, 요청 실행, 인터벌 시작, DOM 변경 등을 구현할 수 있습니다. 해당 함수는 React가 작업을 되돌릴 때 실행할 또 다른 정리 함수를 반환할 수도 있습니다. 예를 들어 시간 간격 취소, 구독 취소, 요청 중단 등이 있습니다.
useEffect의 두 번째 인수는 유명한 의존성 배열입니다.여기서는 effect 내에서 사용하는 모든 "반응형" 값(props, state, 컴포넌트 본문 내에 직접 정의된 변수 또는 함수)을 나열해야 합니다. React는 이 배열을 이전 렌더링에서 가져온 배열과 비교합니다. Object.is종속성 변경 사항이 있는 경우 이전 효과를 정리하고 새 효과를 실행합니다.
의존성 배열을 전달하지 않으면 useEffect는 각 렌더링 후에 실행됩니다.빈 배열을 전달하면 []이 효과는 조립 시 한 번만 실행되고 분해 시 초기화되어 토크를 모방합니다. componentDidMount / componentWillUnmount특정 종속성을 전달하면 해당 값 중 하나가 변경될 때만 효과가 발생합니다.
첫 단계: useState와 useEffect 결합하기
두 Hooks 간의 관계를 이해하는 데 있어 대표적인 예는 문서 제목을 업데이트하는 카운터입니다.. 과 useState 클릭 횟수를 직접 관리할 수 있습니다. useEffect 너는 변한다 document.title 각 렌더링 후:
핵심은 useEffect가 컴포넌트 내부에 정의되어 있다는 점입니다.따라서 JavaScript 클로저 덕분에 현재 상태와 props를 직접 읽을 수 있습니다. effect 내에서 상태에 접근하기 위해 특별한 React API를 사용할 필요가 없으며, 모든 것이 함수의 스코프 내에 있습니다.
또한 React는 렌더링 간에 동일한 effect 함수를 재사용하지 않습니다. 렌더링할 때마다 새로운 함수를 생성합니다.이는 의도적인 것입니다. 각 이펙트는 해당 이펙트가 생성된 렌더링과 연결되어 있으며, React는 종속성 변경을 감지하면 이전 이펙트를 먼저 정리한 후 새로운 이펙트로 교체합니다. 이러한 모델을 통해 각 이펙트가 특정 시점에 어떤 값을 사용하는지 쉽게 이해할 수 있습니다.
또 하나 중요한 점은 이러한 효과가 인터페이스의 화면 표시를 방해하지 않는다는 것입니다.React는 먼저 DOM을 업데이트하고 브라우저가 렌더링하도록 한 다음 실행합니다. useEffect대부분의 경우(요청의 경우, 로그(등) 이렇게 하면 화면 전환이 더 부드럽게 느껴집니다. 사용자가 화면을 보기 직전에 무언가를 동기적으로 처리해야 하는 경우(예: 툴팁의 크기를 측정하고 위치를 조정하는 경우)에는 이 방법을 사용해야 합니다. useLayoutEffectAPI는 공유하지만, 그림을 그리기 전에 실행됩니다.
효과 유형: 청소 기능 포함 및 미포함
모든 부작용이 똑같은 것은 아닙니다. 어떤 부작용은 실행되고 나면 끝이지만, 다른 부작용은 명시적인 정리 작업이 필요합니다.이 두 가지 유형을 구분하는 것은 기억 속에 해결되지 않은 문제가 남거나 이상한 행동이 나타나는 것을 방지하는 데 중요합니다.
청소가 필요 없는 효과는 어떤 부분도 "열린 상태"로 남겨두지 않는 효과를 말합니다.예를 들어 로그에 기록하거나, 일회성 요청을 보내고 결과를 상태에 저장하거나, 자체 관리 애니메이션을 실행하거나, 일회성 DOM 수정을 수행하는 것 등이 있습니다. 수명 주기 측면에서 이러한 작업들은 오직 다음 사항에만 의존합니다. componentDidMount / componentDidUpdate 그리고 그들은 아무것도 필요로 하지 않습니다. componentWillUnmount.
대표적인 예로는 다음과 같은 것을 사용할 수 있습니다. navigator.geolocation.getCurrentPosition 컴포넌트가 렌더링된 후위치 정보를 요청하고, 위도와 경도로 상태를 업데이트하면 React가 다시 렌더링되고, 그게 전부입니다. 영구적인 리스너를 남겨두지 않으므로 컴포넌트를 언마운트할 때 아무것도 정리할 필요가 없습니다.
구성 요소가 외부 시스템에 지속적으로 연결되어 있을 때 청소 효과가 나타납니다.여기에는 웹소켓이나 채팅 API 구독, DOM 이벤트 리스너, 반복적인 간격 또는 시간 초과 등이 포함됩니다. 이러한 경우, 마운트 해제하거나 종속성을 변경할 때 정리를 하지 않으면 메모리 누수나 예기치 않은 동작이 발생할 수 있습니다.
React에서 이러한 정리 작업을 처리하는 관용적인 방법은 effect에서 함수를 반환하는 것입니다.이 함수는 컴포넌트가 DOM에서 제거되거나 종속성이 변경되어 기존 효과를 새 효과로 교체해야 할 때와 같이 효과를 "언마운트"해야 할 때마다 실행됩니다. 대표적인 예로는 카운터 컴포넌트가 있습니다. setInterval 그것은 다음으로 청소됩니다 clearInterval 효과가 되돌아오는 과정에서.
useEffect는 글로벌 이벤트 및 구독에 적용됩니다.
useEffect를 사용하는 매우 일반적인 용도 중 하나는 전역 브라우저 이벤트를 구독하는 것입니다.으로 resize 델 window o keydown 델 document이러한 경우에는 컴포넌트를 설정할 때 이벤트에 연결하고, 분해할 때 연결을 해제하여 리스너가 누적되는 것을 방지해야 합니다.
패턴은 항상 동일합니다. 이펙트 내에서 리스너를 추가하고 정리 함수에서 제거합니다.또한, 구독이 한 번만 생성되도록 하려면 빈 종속성 배열을 전달하면 됩니다. [] 모든 렌더링에서 동일한 내용이 반복되지 않도록 하기 위함입니다.
이와 동일한 접근 방식은 외부 API, 타사 SDK 또는 구성 요소가 표시되는 동안 "연결"해야 하는 모든 시스템과의 통합에도 적용됩니다.이펙트 설정 함수에서 연결하고 정리 함수에서 연결을 해제하면, 종속성이 변경될 때 React가 필요에 따라 두 함수를 모두 호출할 것이라고 믿을 수 있습니다.
개발 모드 및 엄격 모드에서 React는 마운트 직후 추가 구성 및 정리 작업을 수행합니다.이는 구성 변경 사항이 실제로 모든 작업을 되돌리는지 확인하는 일종의 스트레스 테스트입니다. 개발 환경에서만 이상한 동작이 발생하는 경우, 대개 정리 로직이 미흡하기 때문입니다.
useEffect를 사용한 HTTP 요청 및 경쟁 조건
useEffect를 사용하는 또 다른 매우 일반적인 시나리오는 속성이나 상태의 일부가 변경될 때 HTTP 요청을 보내는 것입니다.예를 들어, 사용자 데이터가 변경될 때 해당 데이터를 불러오는 것. userId 또는 소품에 있는 이름이 업데이트될 때 포켓몬에 대한 정보를 검색합니다.
기본적인 아이디어는 다음과 같습니다. 비동기 함수를 트리거하는 효과를 정의하고, 수신된 데이터로 상태를 업데이트하고, 종속성 배열에 해당 요청이 의존하는 값을 지정합니다.주행 중에만 작동하도록 하려면 이 단계를 건너뛰세요. []무언가 변경될 때마다 실행되도록 하려면 userId포함시키겠습니다. userId 배열에 있습니다.
한 가지 중요한 점은 요청에 대한 응답이 오기 전에 구성 요소가 제거될 경우 어떤 일이 발생하는지입니다.. 예 then 또는 그 후에 await 전화하다 setState 더 이상 마운트되지 않은 구성 요소와 관련하여 경고가 표시되거나, 심지어는 좀비 구성 요소에 대한 상태 업데이트가 나타날 수 있습니다.
이를 피하기 위한 일반적인 방법은 내부 플래그를 사용하는 것입니다(예: isMounted o ignore)효과에서 정의하고, 적용하는 겁니다. true그리고 청소 기능에서 다음과 같이 설정합니다. false비동기 함수 내부에서, 호출하기 직전에 setState깃발이 아직 켜져 있는지 확인합니다. true이미 그렇다면 false업데이트를 건너뛰게 됩니다. 이렇게 하면 여러 요청이 제출된 순서와 다르게 처리될 때 발생하는 경쟁 조건을 방지하는 데에도 도움이 됩니다.
또 다른 최신 옵션은 사용 중인 API가 지원하는 경우 AbortController를 사용하는 것입니다.청소 함수가 호출되도록 abort() 그리고 요청에 대한 약속은 상태에 영향을 미치려는 시도 없이 거부됩니다. 어쨌든 취소 또는 응답 무시의 논리는 결과 자체에 내재되어 있어야 합니다.
동일한 컴포넌트에서 useEffect를 여러 번 호출하는 경우
Hooks의 장점 중 하나는 로직을 어떤 라이프사이클 메서드에 배치해야 하는지가 아니라, 로직이 수행하는 작업별로 분리할 수 있다는 점입니다.여러 개를 사용하는 데에는 제한이 없습니다. useEffect 동일한 구성 요소 내에서.
즉, 문서 제목을 관리하는 효과 하나, 채팅을 구독하는 효과 하나, 스크롤 이벤트를 수신하는 효과 하나를 설정할 수 있습니다.각각 고유한 의존성과 정리 로직을 가진 React 함수들은 컴포넌트에 나타나는 순서대로 모두 실행됩니다.
클래스에서는 서로 관련 없는 로직들이 같은 라이프사이클 메서드에 뒤섞이는 경우가 많았지만, Hooks는 책임별로 로직을 분리하도록 권장합니다.하나의 효과는 하나의 특정한 목적에 부합해야 합니다. 만약 하나의 효과가 여러 가지 다른 결과를 낳기 시작한다면, 아마도 그 효과를 여러 개로 나눌 때가 된 것일 겁니다.
이러한 효과를 사용자 지정 훅으로 캡슐화하면 이 패턴은 훨씬 더 강력해집니다. (예 : useWindowSize, useChatStatus(등) 하지만 그렇게까지 하지 않더라도 하나의 거대한 효과 대신 여러 개의 작은 효과를 사용하면 가독성이 향상됩니다.
다양한 의존성 요소: 반응성 및 성능
다양한 의존성들은 useEffect를 강력하게 만드는 요소인 동시에 많은 골칫거리의 원인이기도 합니다.일반적인 규칙은 간단합니다. 이펙트 내에서 사용하는 모든 반응형 값은 해당 배열에 포함되어야 합니다. 반응형 값에는 props, state, 그리고 컴포넌트 내에서 정의된 변수 또는 함수가 포함됩니다.
이는 효과를 줄이기 위해 의존성을 "선택"하는 것이 아니라, 효과의 논리가 어떤 값에 의존하는지 솔직하게 설명하는 것입니다.린터가 eslint-plugin-react-hooks 제대로 설정되어 있으므로, 무언가를 빠뜨렸거나 불필요한 것을 추가했을 때 알려줍니다.
종속성을 제거하려면 경고를 무시하는 것이 아니라, 해당 값이 더 이상 반응형이 아니도록 코드를 수정해야 합니다.예를 들어 상수를 사용하는 경우 serverUrl 값이 절대 변하지 않으므로 컴포넌트 본문 밖으로 옮길 수 있습니다. 이렇게 하면 반응형 값이 아니게 되어 React에 오류를 일으키지 않고 배열에서 제거할 수 있습니다.
이펙트가 반응형 값을 읽을 때는 일반적으로 해당 값의 변화에 반응하도록 하려는 것입니다.즉, 배열에 추가하는 것을 의미합니다. 하지만 때로는 장바구니처럼 어떤 요소의 "현재" 값을 읽고 싶을 때가 있는데, 이때 해당 요소가 변경되어도 효과가 다시 발생하지 않도록 해야 합니다. 이러한 경우를 위해 React는 "이펙트 이벤트"라는 개념을 도입했습니다. useEffectEvent이는 의존성이 되지 않고 마지막 값을 읽을 수 있는 비반응형 코드를 캡슐화합니다.
성능을 향상시키기 위해 일반적으로는 종속성 배열을 최소한의 요소로 제한하는 방식을 사용합니다.두 번째 인자 없이 효과를 전달하는 대신(이렇게 하면 렌더링할 때마다 효과가 실행됨), 실제로 효과를 실행해야 하는 값만 포함된 배열을 전달합니다. 렌더링 간에 종속성이 변경되지 않으면 React는 정리 및 설정 작업을 건너뛰고 효과는 다시 실행되지 않습니다.
useEffect 사용 시 발생하는 일반적인 문제 해결
가장 흔한 실수 중 하나는 효과가 무한 루프에 빠지는 것입니다.이 문제는 일반적으로 이펙트 자체 내의 상태, 즉 의존성 배열의 일부인 상태를 업데이트할 때 발생합니다. 이펙트가 상태를 변경하면 상태가 렌더링을 트리거하고, 렌더링이 이펙트를 다시 실행하며, 이 과정이 반복됩니다.
그 악순환을 끊으려면 이펙트에서 해당 상태를 업데이트하는 이유를 다시 검토해 보세요.외부 시스템과 동기화하지 않는다면 useEffect가 필요 없을 수도 있습니다. 많은 부분을 렌더링에서 직접 처리할 수 있기 때문입니다. 하지만 외부 시스템과 동기화하는 경우에는 상태 업데이트가 실제 변경 사항에만 의존하도록 하고, 매번 트리거되지 않도록 해야 합니다.
또 다른 흔한 문제 원인은 사용자가 알아차리지 못하는 사이에 렌더링할 때마다 변경되는 종속성입니다.JSX에서 인라인 객체나 함수를 생성하고 이를 이펙트에서 사용하면 렌더링할 때마다 새로운 참조가 생성되므로 의존성 배열이 항상 달라집니다. 이를 방지하려면 이러한 객체 생성을 이펙트 자체로 옮기거나, 최후의 수단으로 안정화하는 방법을 사용할 수 있습니다. useMemo / useCallback.
구성 요소가 화면에 여전히 표시되어 있는 경우에도 청소 기능이 실행되는 것을 보고 놀라는 경우가 흔합니다.이는 정상적인 현상입니다. React는 종속성이 변경될 때마다 새로운 효과를 적용하기 전에 이전 효과를 정리합니다. 또한 개발 환경에서 StrictMode를 사용하면 마운트 직후 추가적인 구성 및 정리 과정을 수행합니다.
마지막으로, 효과가 시각적인 것이고 적용되기 전에 깜빡임이 보인다면,다음으로 전환해야 할 수도 있습니다. useLayoutEffect 브라우저가 화면을 그리기 전에 실행되도록 강제하는 기능입니다. 이 기능은 측정이나 위치 지정이 완료될 때까지 화면 그리기를 차단해야 할 필요가 있을 때만 신중하게 사용해야 합니다.
useEffect 및 서버 측 렌더링(SSR)
서버 측 렌더링 애플리케이션에서 중요한 점은 효과가 클라이언트에서만 실행된다는 것입니다.SSR(서버 사이드 렌더링) 과정에서 React는 초기 HTML을 생성하지만, 효과는 실행하지 않습니다. 이러한 효과는 나중에 앱이 브라우저에 로드될 때 실행됩니다.
이는 useEffect 내의 모든 로직이 브라우저 API(예: localStorage o window)는 서버에 대해 안전합니다.서버에서 직접 실행되지 않기 때문입니다. 중요한 것은 첫 번째 렌더링(서버에서 수행되고 클라이언트에서 반복되는 렌더링)이 결정론적이어야 하며 브라우저에만 존재하는 내용을 읽지 않아야 한다는 것입니다.
서버와 클라이언트에서 서로 다른 콘텐츠를 표시하려면 일반적으로 다음과 같은 상태를 사용합니다. didMount처음에는 다음과 같습니다. false, 그리고 useEffect 와 [] 당신이 그것을 입는다 true이 값이 false일 때는 컴포넌트의 "안전한" 버전을 렌더링하고, true가 되면 해당 컴포넌트에서 데이터를 읽을 수 있습니다. localStorageDOM을 수정하는 등의 작업을 할 수 있습니다. 하지만 인터넷 연결 속도가 느린 사용자에게 시각적 변화가 너무 거슬리지 않도록 주의해야 합니다.
일반적으로 컴포넌트 트리를 환경과 독립적으로 만들수록 SSR과 Hooks를 결합할 때 예상치 못한 문제가 발생할 가능성이 줄어듭니다.useEffect는 외부 시스템과의 실제 통합을 위해 예약되어 있으며, 그것이 바로 useEffect의 본래 목적입니다.
useState와 useEffect를 마스터하려면 컴포넌트가 렌더링될 때마다 "다시 생성"된다는 점, 이펙트가 특정 라이프사이클에 속한다는 점, 그리고 의존성 배열이 코드와 반응형 데이터 간의 관계를 설명한다는 점을 내면화해야 합니다.이러한 사고방식을 내면화하면, 마법처럼 느껴지던 느낌은 사라지고 React Hooks를 쉽게 사용할 수 있게 되어, 대규모 React 애플리케이션에서도 더욱 선언적이고 깔끔하며 유지보수하기 쉬운 컴포넌트를 작성할 수 있게 됩니다.
바이트와 기술 전반에 관한 세계에 대한 열정적인 작가입니다. 나는 글쓰기를 통해 내 지식을 공유하는 것을 좋아하며 이것이 바로 이 블로그에서 할 일이며 가젯, 소프트웨어, 하드웨어, 기술 동향 등에 관한 가장 흥미로운 모든 것을 보여 드리겠습니다. 제 목표는 여러분이 간단하고 재미있는 방식으로 디지털 세계를 탐색할 수 있도록 돕는 것입니다.
