Effect와 동기화하기
일부 컴포넌트는 외부 시스템과 동기화해야 합니다. 예를 들어, React 상태를 기반으로 비 React 컴포넌트를 제어하거나, 서버 연결을 설정하거나, 컴포넌트가 화면에 나타날 때 분석 로그를 보내고 싶을 수 있습니다.Effect를 사용하면 렌더링 후 일부 코드를 실행하여 컴포넌트를 React 외부의 시스템과 동기화할 수 있습니다.
배울 내용
- Effect란 무엇인가
- Effect가 이벤트와 어떻게 다른가
- 컴포넌트에서 Effect를 선언하는 방법
- 불필요하게 Effect를 다시 실행하지 않는 방법
- 개발 환경에서 Effect가 두 번 실행되는 이유와 해결 방법
Effect란 무엇이며 이벤트와 어떻게 다른가요?
Effect를 이해하기 전에 React 컴포넌트 내부의 두 가지 논리 유형에 익숙해져야 합니다:
- 렌더링 코드 (UI 설명하기에서 소개됨)는 컴포넌트의 최상위 레벨에 위치합니다. 여기서 props와 state를 가져와 변환하고 화면에 표시할 JSX를 반환합니다.렌더링 코드는 순수해야 합니다. 수학 공식처럼 결과만 계산해야 하며 다른 작업을 해서는 안 됩니다.
- 이벤트 핸들러 (상호작용 추가하기에서 소개됨)는 컴포넌트 내부에 중첩된 함수로, 단순히 계산하는 것이 아니라 무언가를수행합니다. 이벤트 핸들러는 입력 필드를 업데이트하거나, 제품 구매를 위해 HTTP POST 요청을 제출하거나, 사용자를 다른 화면으로 이동시킬 수 있습니다. 이벤트 핸들러에는 특정 사용자 작업(예: 버튼 클릭 또는 입력)으로 인해 발생하는"부수 효과"(프로그램 상태를 변경함)가 포함됩니다.
때로는 이것만으로 충분하지 않습니다. 화면에 표시될 때마다 채팅 서버에 연결해야 하는ChatRoom 컴포넌트를 생각해 보세요. 서버에 연결하는 것은 순수한 계산이 아니므로(부수 효과임) 렌더링 중에 발생할 수 없습니다. 그러나 ChatRoom이 표시되도록 하는 클릭과 같은 단일 특정 이벤트는 없습니다.
Effect는 특정 이벤트가 아닌 렌더링 자체에 의해 발생하는 부수 효과를 지정할 수 있게 해줍니다.채팅에서 메시지를 보내는 것은 사용자가 특정 버튼을 클릭하는 것에 직접적으로 원인이 있으므로이벤트입니다. 그러나 서버 연결 설정은 컴포넌트가 나타나게 만든 상호작용이 무엇이든 상관없이 발생해야 하므로Effect입니다. Effect는 화면이 업데이트된 후 커밋단계의 끝에서 실행됩니다. 이는 React 컴포넌트를 네트워크나 서드파티 라이브러리와 같은 외부 시스템과 동기화하기에 좋은 시기입니다.
참고
여기와 이후 본문에서 대문자로 시작하는 "Effect"는 위에서 정의한 React 특유의 개념, 즉 렌더링에 의해 발생하는 부수 효과를 가리킵니다. 더 넓은 프로그래밍 개념을 지칭할 때는 "부수 효과(side effect)"라고 말하겠습니다.
Effect가 필요하지 않을 수도 있습니다
컴포넌트에 Effect를 서둘러 추가하지 마세요.Effect는 일반적으로 React 코드에서 "벗어나" 브라우저 API, 서드파티 위젯, 네트워크 등과 같은외부시스템과 동기화하기 위해 사용된다는 점을 기억하세요. Effect가 다른 상태에 기반하여 일부 상태만 조정한다면,Effect가 필요하지 않을 수 있습니다.
Effect 작성 방법
Effect를 작성하려면 다음 세 단계를 따르세요:
- Effect를 선언하세요.기본적으로 Effect는 모든커밋후에 실행됩니다.
- Effect 의존성을 지정하세요.대부분의 Effect는 모든 렌더링 후가 아니라필요할 때만다시 실행되어야 합니다. 예를 들어, 페이드 인 애니메이션은 컴포넌트가 나타날 때만 트리거되어야 합니다. 채팅방에 연결하고 연결을 끊는 것은 컴포넌트가 나타나거나 사라질 때, 또는 채팅방이 변경될 때만 발생해야 합니다.의존성
- 필요한 경우 정리(cleanup)를 추가하세요.일부 Effect는 수행 중인 작업을 중지, 취소 또는 정리하는 방법을 지정해야 합니다. 예를 들어, "연결"에는 "연결 해제"가, "구독"에는 "구독 취소"가, "가져오기(fetch)"에는 "취소" 또는 "무시"가 필요합니다.정리 함수를 반환하여 이를 수행하는 방법을 배우게 될 것입니다.
이 각 단계를 자세히 살펴보겠습니다.
1단계: Effect 선언하기
컴포넌트에서 Effect를 선언하려면 React에서useEffect Hook을 가져오세요:
그런 다음 컴포넌트의 최상위 레벨에서 호출하고 Effect 내부에 일부 코드를 넣으세요:
컴포넌트가 렌더링될 때마다 React는 화면을 업데이트한후 useEffect내부의 코드를 실행합니다. 다시 말해,useEffect는 코드 조각이 해당 렌더링이 화면에 반영될 때까지 실행을 "지연"시킵니다.
Effect를 사용하여 외부 시스템과 동기화하는 방법을 살펴보겠습니다.<VideoPlayer> React 컴포넌트를 생각해 보세요. isPlayingprop을 전달하여 재생 중인지 일시 정지 상태인지 제어할 수 있다면 좋을 것입니다:
사용자 정의VideoPlayer컴포넌트는 브라우저 내장<video>태그를 렌더링합니다:
그러나 브라우저<video> 태그에는 isPlayingprop이 없습니다. 이를 제어하는 유일한 방법은 DOM 요소에서play() 및 pause()메서드를 수동으로 호출하는 것입니다.비디오가 현재 재생되어야 하는지 여부를 알려주는isPlaying prop의 값을 play() 및 pause()와 같은 호출과 동기화해야 합니다.
먼저 ref를 가져와 <video>DOM 노드에 접근해야 합니다.
렌더링 중에 play()또는pause()를 호출하려고 시도할 수 있지만, 이는 올바르지 않습니다:
이 코드가 올바르지 않은 이유는 렌더링 중에 DOM 노드에 대해 무언가를 시도하기 때문입니다. React에서렌더링은 순수한 계산이어야 하며 DOM을 수정하는 것과 같은 부수 효과를 포함해서는 안 됩니다.
게다가, VideoPlayer가 처음 호출될 때는 아직 DOM이 존재하지 않습니다!play()나 pause()를 호출할 DOM 노드가 아직 없습니다. 왜냐하면 React는 JSX를 반환하기 전까지 생성할 DOM이 무엇인지 알지 못하기 때문입니다.
여기서 해결책은 부수 효과를로 감싸서 렌더링 계산 밖으로 옮기는 것입니다:
DOM 업데이트를 Effect로 감싸면 React가 먼저 화면을 업데이트하게 합니다. 그런 다음 여러분의 Effect가 실행됩니다.
여러분의VideoPlayer컴포넌트가 렌더링될 때(처음이거나 재렌더링되는 경우), 몇 가지 일이 발생합니다. 먼저, React는 화면을 업데이트하여<video>태그가 올바른 props와 함께 DOM에 있도록 합니다. 그런 다음 React는 여러분의 Effect를 실행합니다. 마지막으로, 여러분의 Effect는isPlaying값에 따라play()나 pause()를 호출합니다.
재생/일시정지 버튼을 여러 번 눌러보고 비디오 플레이어가isPlaying값과 동기화된 상태를 유지하는지 확인하세요:
이 예시에서 React 상태와 동기화한 "외부 시스템"은 브라우저 미디어 API입니다. 유사한 접근법으로 레거시 비-React 코드(예: jQuery 플러그인)를 선언적 React 컴포넌트로 감쌀 수 있습니다.
실제로 비디오 플레이어를 제어하는 것은 훨씬 더 복잡합니다.play()호출이 실패할 수 있고, 사용자가 내장된 브라우저 컨트롤을 사용하여 재생 또는 일시정지할 수 있습니다. 이 예시는 매우 단순화되고 불완전합니다.
주의사항
기본적으로 Effect는모든렌더링 후에 실행됩니다. 이것이 다음과 같은 코드가무한 루프를 생성하는 이유입니다:
Effect는 렌더링의결과로실행됩니다. 상태 설정은 렌더링을유발합니다. Effect에서 즉시 상태를 설정하는 것은 전원 콘센트를 자기 자신에 꽂는 것과 같습니다. Effect가 실행되고, 상태를 설정하며, 이는 재렌더링을 유발하고, 이는 Effect를 다시 실행하게 하며, 상태를 다시 설정하고, 이는 또 다른 재렌더링을 유발하는 식으로 계속됩니다.
Effect는 일반적으로 컴포넌트를외부시스템과 동기화해야 합니다. 외부 시스템이 없고 다른 상태를 기반으로 일부 상태만 조정하려는 경우,Effect가 필요하지 않을 수 있습니다.
2단계: Effect 의존성 지정하기
기본적으로 Effect는모든렌더링 후에 실행됩니다. 종종 이것은원하는 바가 아닙니다:
- 때로는 느립니다. 외부 시스템과 동기화하는 것은 항상 즉각적이지 않으므로, 필요하지 않으면 생략하고 싶을 수 있습니다. 예를 들어, 모든 키 입력마다 채팅 서버에 재연결하고 싶지 않을 것입니다.
- 때로는 잘못된 경우입니다. 예를 들어, 모든 키 입력마다 컴포넌트 페이드 인 애니메이션을 트리거하고 싶지 않을 것입니다. 애니메이션은 컴포넌트가 처음 나타날 때만 한 번 재생되어야 합니다.
문제를 설명하기 위해, 몇 개의 console.log호출과 부모 컴포넌트의 상태를 업데이트하는 텍스트 입력이 추가된 이전 예시가 있습니다. 타이핑이 Effect를 다시 실행하게 만드는 방식을 확인하세요:
React에게불필요하게 Effect를 다시 실행하지 않도록 건너뛰라고 지시하려면 의존성 배열을 useEffect호출의 두 번째 인수로 지정하면 됩니다. 위 예시의 14번째 줄에 빈[]배열을 추가하는 것으로 시작하세요:
다음과 같은 오류가 표시될 것입니다:React Hook useEffect has a missing dependency: 'isPlaying':
문제는 Effect 내부의 코드가 수행할 작업을 결정하기 위해의존하는 isPlayingprop에 의존하지만, 이 의존성이 명시적으로 선언되지 않았다는 점입니다. 이 문제를 해결하려면 의존성 배열에isPlaying을 추가하세요:
이제 모든 의존성이 선언되었으므로 오류가 없습니다. 의존성 배열로[isPlaying]을 지정하면
의존성 배열은 여러 의존성을 포함할 수 있습니다. React는 지정한 의존성모두가 이전 렌더링과 정확히 동일한 값을 가질 때만 Effect 재실행을 건너뜁니다. React는Object.is비교를 사용하여 의존성 값을 비교합니다. 자세한 내용은useEffect 레퍼런스를 참조하세요.
의존성을 "선택"할 수 없다는 점에 주목하세요.지정한 의존성이 Effect 내부 코드를 기반으로 React가 예상하는 것과 일치하지 않으면 린트 오류가 발생합니다. 이는 코드의 많은 버그를 잡는 데 도움이 됩니다. 일부 코드가 다시 실행되지 않도록 하려면,Effect 코드 자체를 수정하여 해당 의존성을 "필요로 하지 않도록" 하세요.
주의사항
의존성 배열이 없는 경우와 빈[]의존성 배열을 가진 경우의 동작은 다릅니다:
다음 단계에서 "마운트"가 무엇을 의미하는지 자세히 살펴보겠습니다.
Step 3: 필요한 경우 정리(cleanup) 추가하기
다른 예시를 생각해 보세요. 채팅 서버에 연결해야 하는ChatRoom 컴포넌트를 작성하고 있습니다. connect()와 disconnect()메서드를 가진 객체를 반환하는createConnection()API가 주어졌습니다. 컴포넌트가 사용자에게 표시되는 동안 연결을 유지하려면 어떻게 해야 할까요?
먼저 Effect 로직을 작성합니다:
매번 다시 렌더링할 때마다 채팅에 연결하는 것은 느릴 수 있으므로, 의존성 배열을 추가합니다:
Effect 내부 코드는 props나 state를 사용하지 않으므로, 의존성 배열은[](빈 배열)입니다. 이는 React에게 이 코드를 컴포넌트가 "마운트"될 때, 즉 화면에 처음 나타날 때만 실행하라고 알려줍니다.
이 코드를 실행해 봅시다:
이 Effect는 마운트 시에만 실행되므로 콘솔에"✅ Connecting..."가 한 번만 출력될 것으로 예상할 수 있습니다.하지만 콘솔을 확인해 보면,"✅ Connecting..."가 두 번 출력됩니다. 왜 이런 일이 발생할까요?
다양한 화면이 있는 더 큰 앱의 일부로ChatRoom 컴포넌트가 있다고 상상해 보세요. 사용자는 ChatRoom페이지에서 여정을 시작합니다. 컴포넌트가 마운트되고connection.connect()를 호출합니다. 그런 다음 사용자가 다른 화면(예: 설정 페이지)으로 이동한다고 가정해 보세요.ChatRoom컴포넌트가 언마운트됩니다. 마지막으로 사용자가 뒤로 가기를 클릭하면ChatRoom이 다시 마운트됩니다. 이렇게 하면 두 번째 연결이 설정되지만, 첫 번째 연결은 절대 파괴되지 않았습니다! 사용자가 앱을 탐색할수록 연결이 계속 쌓이게 됩니다.
이런 버그는 철저한 수동 테스트 없이는 놓치기 쉽습니다. 이를 빠르게 발견하도록 돕기 위해, 개발 환경에서 React는 모든 컴포넌트를 초기 마운트 직후에 한 번씩 다시 마운트합니다.
콘솔 로그"✅ Connecting..."가 두 번 출력되는 것을 보면 실제 문제를 알아차릴 수 있습니다: 컴포넌트가 언마운트될 때 연결을 닫지 않는 코드입니다.
이 문제를 해결하려면 Effect에서클린업 함수를 반환하세요:
React는 Effect가 다시 실행되기 전마다, 그리고 컴포넌트가 언마운트될 때(제거될 때) 최종적으로 한 번씩 클린업 함수를 호출합니다. 클린업 함수가 구현되었을 때 어떤 일이 발생하는지 살펴보겠습니다:
이제 개발 환경에서 세 개의 콘솔 로그를 얻습니다:
"✅ Connecting...""❌ Disconnected.""✅ Connecting..."
이는 개발 환경에서 올바른 동작입니다.컴포넌트를 다시 마운트함으로써 React는 화면을 벗어났다가 돌아와도 코드가 깨지지 않는지 확인합니다. 연결을 끊었다가 다시 연결하는 것은 정확히 일어나야 할 일입니다! 클린업을 잘 구현하면 Effect를 한 번 실행하는 것과 실행하고, 클린업하고, 다시 실행하는 것 사이에 사용자가 눈치챌 수 있는 차이가 없어야 합니다. React가 개발 환경에서 버그를 탐지하기 위해 추가적인 연결/연결 해제 쌍을 호출하기 때문에 한 번 더 연결/연결 해제가 발생합니다. 이것은 정상입니다—이를 없애려고 하지 마세요!
프로덕션 환경에서는 "✅ Connecting..."가 한 번만 출력됩니다.컴포넌트를 다시 마운트하는 것은 클린업이 필요한 Effect를 찾도록 돕기 위해 개발 환경에서만 발생합니다.Strict Mode를 끄면 개발 환경 동작을 선택 해제할 수 있지만, 계속 켜 두는 것을 권장합니다. 이렇게 하면 위와 같은 많은 버그를 발견할 수 있습니다.
개발 환경에서 Effect가 두 번 실행되는 것을 어떻게 처리하나요?
React는 마지막 예제와 같은 버그를 찾기 위해 의도적으로 개발 환경에서 컴포넌트를 다시 마운트합니다.올바른 질문은 "Effect를 한 번만 실행하는 방법"이 아니라 "Effect를 다시 마운트한 후에도 작동하도록 수정하는 방법"입니다.
일반적으로 답은 클린업 함수를 구현하는 것입니다. 클린업 함수는 Effect가 수행하던 작업을 중지하거나 취소해야 합니다. 경험 법칙은 사용자가 Effect가 한 번 실행되는 것(프로덕션 환경에서처럼)과설정 → 클린업 → 설정순서(개발 환경에서 보게 될)를 구별할 수 없어야 한다는 것입니다.
작성할 대부분의 Effect는 아래의 일반적인 패턴 중 하나에 맞을 것입니다.
주의사항
Effect가 실행되는 것을 막기 위해 ref를 사용하지 마세요
개발 환경에서 Effect가 두 번 실행되는 것을 막기 위한 일반적인 함정은ref를 사용하여 Effect가 한 번 이상 실행되지 않도록 하는 것입니다. 예를 들어, 위의 버그를useRef로 "수정"할 수 있습니다:
이렇게 하면 개발 환경에서"✅ Connecting..."를 한 번만 볼 수 있지만, 버그를 수정하지는 않습니다.
사용자가 화면을 벗어나도 연결은 여전히 닫히지 않으며, 돌아오면 새로운 연결이 생성됩니다. 사용자가 앱을 탐색할수록 연결이 계속 쌓이게 되며, "수정" 전과 동일한 문제가 발생합니다.
버그를 수정하려면 Effect를 한 번만 실행하도록 만드는 것만으로는 충분하지 않습니다. Effect는 다시 마운트된 후에도 작동해야 하며, 이는 위의 해결책처럼 연결을 클린업해야 한다는 것을 의미합니다.
일반적인 패턴을 처리하는 방법은 아래 예제를 참조하세요.
비 React 위젯 제어하기
때로는 React로 작성되지 않은 UI 위젯을 추가해야 할 때가 있습니다. 예를 들어, 페이지에 지도 컴포넌트를 추가한다고 가정해 보세요. 지도에는setZoomLevel()메서드가 있으며, React 코드의zoomLevel상태 변수와 확대/축소 수준을 동기화하고 싶습니다. Effect는 다음과 비슷하게 보일 것입니다:
이 경우에는 정리 함수가 필요하지 않습니다. 개발 환경에서 React는 Effect를 두 번 호출하지만,setZoomLevel을 같은 값으로 두 번 호출해도 아무런 작업을 수행하지 않기 때문에 문제가 되지 않습니다. 약간 느려질 수는 있지만, 프로덕션 환경에서는 불필요하게 다시 마운트되지 않으므로 중요하지 않습니다.
일부 API는 연속으로 두 번 호출하는 것을 허용하지 않을 수 있습니다. 예를 들어, 내장showModal메서드는 두 번 호출하면 오류를 발생시킵니다. 정리 함수를 구현하고 다이얼로그를 닫도록 만드세요:
개발 환경에서 Effect는showModal()을 호출한 다음 즉시close()를 호출하고, 다시showModal()을 호출합니다. 이는 프로덕션 환경에서 볼 수 있는 것처럼showModal()을 한 번 호출하는 것과 동일한 사용자 가시적 동작을 가집니다.
이벤트 구독하기
Effect가 무언가를 구독한다면, 정리 함수는 구독을 취소해야 합니다:
개발 환경에서 Effect는addEventListener()를 호출한 다음 즉시removeEventListener()를 호출하고, 같은 핸들러로 다시addEventListener()를 호출합니다. 따라서 한 번에 활성화된 구독은 하나만 존재하게 됩니다. 이는 프로덕션 환경에서처럼addEventListener()를 한 번 호출하는 것과 동일한 사용자 가시적 동작을 가집니다.
애니메이션 트리거하기
Effect가 무언가를 애니메이션으로 표시한다면, 정리 함수는 애니메이션을 초기 값으로 재설정해야 합니다:
개발 환경에서는 불투명도가1로 설정된 다음0으로, 그리고 다시1로 설정됩니다. 이는 프로덕션 환경에서 발생할 것처럼 직접1로 설정하는 것과 동일한 사용자 가시적 동작을 가져야 합니다. 트위닝을 지원하는 서드파티 애니메이션 라이브러리를 사용하는 경우, 정리 함수는 타임라인을 초기 상태로 재설정해야 합니다.
데이터 가져오기
Effect가 무언가를 가져오는(fetch) 경우, 클린업 함수는 가져오기를중단(abort)하거나 그 결과를 무시해야 합니다:
이미 발생한 네트워크 요청을 '실행 취소'할 수는 없지만, 클린업 함수는더 이상 관련 없는가져오기가 애플리케이션에 계속 영향을 미치지 않도록 해야 합니다. 만약userId가 'Alice'에서'Bob'로 변경되면, 클린업은'Alice'에 대한 응답이 'Bob'이후에 도착하더라도 무시되도록 보장합니다.
개발 환경에서는 네트워크 탭에서 두 번의 가져오기가 표시됩니다.이는 문제가 되지 않습니다. 위의 접근 방식에 따르면, 첫 번째 Effect는 즉시 클린업되므로 해당 Effect의ignore 변수 복사본은 true로 설정됩니다. 따라서 추가 요청이 있더라도 if (!ignore)확인 덕분에 상태에 영향을 미치지 않습니다.
프로덕션 환경에서는 요청이 하나만 있을 것입니다.개발 환경에서의 두 번째 요청이 신경 쓰인다면, 최선의 접근 방식은 요청을 중복 제거하고 그 응답을 컴포넌트 간에 캐시하는 솔루션을 사용하는 것입니다:
이는 개발 경험을 향상시킬 뿐만 아니라 애플리케이션을 더 빠르게 느껴지게 할 것입니다. 예를 들어, 사용자가 뒤로 가기 버튼을 누를 때 데이터가 캐시되어 있기 때문에 다시 로드되기를 기다릴 필요가 없습니다. 이러한 캐시를 직접 구축하거나, Effect에서 수동으로 가져오는 대신 사용할 수 있는 많은 대안 중 하나를 사용할 수 있습니다.
분석 전송
페이지 방문 시 분석 이벤트를 전송하는 다음 코드를 고려해 보세요:
개발 환경에서는 모든 URL에 대해logVisit이 두 번 호출되므로, 이를 수정하려고 할 수 있습니다.이 코드를 있는 그대로 유지하는 것을 권장합니다.이전 예제들과 마찬가지로, 한 번 실행하는 것과 두 번 실행하는 것 사이에는사용자에게 보이는 동작 차이가 없습니다. 실용적인 관점에서, logVisit은 개발 환경에서는 아무 작업도 하지 않아야 합니다. 개발 머신의 로그가 프로덕션 지표를 왜곡하지 않도록 하기 위해서입니다. 컴포넌트 파일을 저장할 때마다 컴포넌트가 다시 마운트되므로, 어쨌든 개발 환경에서는 추가 방문을 기록하게 됩니다.
프로덕션 환경에서는 중복 방문 로그가 없을 것입니다.
전송 중인 분석 이벤트를 디버그하려면, 앱을 스테이징 환경(프로덕션 모드로 실행)에 배포하거나, 일시적으로Strict Mode와 개발 전용 재마운트 검사를 옵트아웃할 수 있습니다. 또한 Effect 대신 라우트 변경 이벤트 핸들러에서 분석을 전송할 수도 있습니다. 더 정밀한 분석을 위해,교차 관찰자는 어떤 컴포넌트가 뷰포트에 있고 얼마나 오래 보이는지 추적하는 데 도움이 될 수 있습니다.
Effect가 아닙니다: 애플리케이션 초기화
일부 로직은 애플리케이션이 시작될 때 한 번만 실행되어야 합니다. 컴포넌트 외부에 배치할 수 있습니다:
이렇게 하면 브라우저가 페이지를 로드한 후에 이러한 로직이 한 번만 실행되도록 보장됩니다.
Effect가 아닙니다: 제품 구매
때로는 클린업 함수를 작성하더라도 Effect를 두 번 실행하는 것의 사용자에게 보이는 결과를 방지할 방법이 없습니다. 예를 들어, Effect가 제품 구매와 같은 POST 요청을 전송할 수 있습니다:
제품을 두 번 구매하고 싶지는 않을 것입니다. 그러나 이것이 바로 이 로직을 Effect에 넣어서는 안 되는 이유이기도 합니다. 사용자가 다른 페이지로 이동한 뒤 뒤로 가기 버튼을 누르면 어떻게 될까요? Effect가 다시 실행될 것입니다. 사용자가 페이지를방문할 때 제품을 구매하고 싶은 것이 아니라, 사용자가 구매 버튼을클릭할 때 구매하고 싶은 것입니다.
구매는 렌더링에 의해 발생하는 것이 아닙니다. 특정 상호작용에 의해 발생합니다. 사용자가 버튼을 누를 때만 실행되어야 합니다.Effect를 삭제하고/api/buy요청을 구매 버튼 이벤트 핸들러로 옮기세요:
이는 리마운트가 애플리케이션의 로직을 깨뜨린다면, 이는 일반적으로 기존 버그를 드러낸다는 것을 보여줍니다.사용자 관점에서, 페이지를 방문하는 것과 페이지를 방문한 후 링크를 클릭하고 뒤로 가기를 눌러 페이지를 다시 보는 것은 달라서는 안 됩니다. React는 개발 환경에서 컴포넌트를 한 번 리마운트하여 여러분의 컴포넌트가 이 원칙을 준수하는지 확인합니다.
모든 내용 종합하기
이 플레이그라운드는 Effect가 실제로 어떻게 동작하는지 "느껴보는" 데 도움이 될 수 있습니다.
이 예제는 setTimeout을 사용하여 Effect가 실행된 후 3초 후에 입력된 텍스트와 함께 콘솔 로그가 나타나도록 예약합니다. 클린업 함수는 보류 중인 타임아웃을 취소합니다. "컴포넌트 마운트" 버튼을 눌러 시작하세요:
처음에 세 개의 로그가 표시됩니다:Schedule "a" log,Cancel "a" log, 그리고 다시Schedule "a" log입니다. 3초 후에는 a라고 표시되는 로그도 있습니다. 앞서 배운 것처럼, 추가적인 schedule/cancel 쌍은 React가 개발 환경에서 클린업을 잘 구현했는지 확인하기 위해 컴포넌트를 한 번 리마운트하기 때문입니다.
이제 입력란을 abc로 수정하세요. 충분히 빠르게 입력하면,Schedule "ab" log가 즉시 Cancel "ab" log와 Schedule "abc" log뒤에 나타나는 것을 볼 수 있습니다.React는 항상 다음 렌더링의 Effect 전에 이전 렌더링의 Effect를 클린업합니다.이것이 입력란에 빠르게 입력하더라도 한 번에 최대 하나의 타임아웃만 예약되는 이유입니다. 입력란을 몇 번 수정하고 콘솔을 관찰하여 Effect가 어떻게 클린업되는지 느껴보세요.
입력란에 무언가를 입력한 후 즉시 "컴포넌트 언마운트" 버튼을 누르세요. 언마운트가 마지막 렌더링의 Effect를 어떻게 클린업하는지 확인하세요. 여기서는 마지막 타임아웃이 실행되기 전에 취소합니다.
마지막으로, 위 컴포넌트를 수정하고 클린업 함수를 주석 처리하여 타임아웃이 취소되지 않도록 하세요. 빠르게abcde를 입력해 보세요. 3초 후에 어떤 일이 일어날 것이라고 예상하시나요? 타임아웃 내부의console.log(text)가 최신text를 출력하고 다섯 개의abcde로그를 생성할까요? 직감을 확인해 보세요!
3초 후에, 다섯 개의 a,ab,abc,abcd, 그리고abcde로그가 아닌 일련의 로그(
요약
- 이벤트와 달리, Effect는 특정 상호작용이 아닌 렌더링 자체에 의해 발생합니다.
- Effect를 사용하면 컴포넌트를 외부 시스템(서드파티 API, 네트워크 등)과 동기화할 수 있습니다.
- 기본적으로 Effect는 모든 렌더링 후(초기 렌더링 포함) 실행됩니다.
- 의존성 값이 이전 렌더링과 모두 동일하다면 React는 Effect를 건너뜁니다.
- 의존성을 임의로 "선택"할 수 없습니다. 의존성은 Effect 내부의 코드에 의해 결정됩니다.
- 빈 의존성 배열(
[])은 컴포넌트가 "마운트"되는 시점, 즉 화면에 추가되는 시점에 해당합니다. - Strict Mode에서 React는 개발 환경에서만 컴포넌트를 두 번 마운트하여 Effect를 스트레스 테스트합니다.
- Effect가 재마운트로 인해 문제가 발생한다면 정리 함수를 구현해야 합니다.
- React는 다음에 Effect가 실행되기 전과 언마운트 시에 정리 함수를 호출합니다.
Try out some challenges
Challenge 1 of 4:Focus a field on mount #
In this example, the form renders a <MyInput /> component.
Use the input’s focus() method to make MyInput automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn’t quite work. Figure out why it doesn’t work, and fix it. (If you’re familiar with the autoFocus attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.)
To verify that your solution works, press “Show form” and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press “Hide form” and “Show form” again. Verify the input is highlighted again.
MyInput should only focus on mount rather than after every render. To verify that the behavior is right, press “Show form” and then repeatedly press the “Make it uppercase” checkbox. Clicking the checkbox should not focus the input above it.
