이펙트는 컴포넌트와 다른 라이프사이클을 가집니다.
라이프사이클 메서드는 컴포넌트의 상태 변화에 따라 실행되는 메서드이고, 이펙트는 렌더링 이후에 비동기 작업이나 다른 부수효과를 위해 사용합니다.
이 사이클은 시간이 지남에 따라 변하는 프로퍼티와 상태에 의존하는 Effect의 경우 여러 번 발생할 수 있습니다. React는 이펙트의 종속성을 올바르게 지정했는지 확인하는 린터 규칙을 제공합니다. 이렇게 하면 이펙트가 최신 프로퍼티와 state에 동기화됩니다.
린터규칙?
린터(Linter)는 소스 코드를 분석하여 문법 오류나 코딩 스타일 등을 검사하고, 개발자에게 경고 또는 에러 메시지를 제공하여 코드 품질을 향상시키는 도구
대표적인 린터 도구 : ESLint, JSHint, Prettier 등
린터 도구가 제공하는 규칙
1. 코딩 스타일 규칙 들여쓰기 규칙: 일관된 들여쓰기를 유지해야 함 세미콜론 규칙: 세미콜론의 사용 여부를 정해야 함 따옴표 규칙: 큰 따옴표 또는 작은 따옴표 중 어떤 것을 사용할 것인지 결정해야 함 2. 오류 규칙 정의되지 않은 변수 사용: 변수가 선언되지 않았는데 사용하는 경우 중복된 변수 이름: 같은 이름의 변수가 두 번 이상 선언되는 경우 잘못된 함수 호출: 함수를 호출할 때 인자의 개수나 타입이 잘못된 경우 3. 일반적인 오류 검사 불필요한 코드 검사: 절대 실행되지 않는 코드를 찾아서 제거 잠재적인 버그 검사: 예외 상황을 처리하지 않은 경우, 변수의 값이 undefined인 경우 등을 검사하여 잠재적인 버그를 예방
학습내용
- 이펙트의 라이프사이클이 컴포넌트의 라이프사이클과 다른 점
- 각 개별 이펙트를 분리해서 생각하는 방법
- 이펙트를 다시 동기화해야 하는 시기와 그 이유
- 이펙트의 종속성이 결정되는 방법
- 값이 반응형이라는 것의 의미
- 빈 의존성 배열이 의미하는 것
- React가 린터로 의존성이 올바른지 확인하는 방법
- 린터에 동의하지 않을 때 해야 할 일
constructor(): 컴포넌트가 생성될 때 호출되는 메서드
getDerivedStateFromProps(): props가 변경될 때 호출되는 메서드
render(): UI를 렌더링하는 메서드
componentDidMount(): 컴포넌트가 DOM에 마운트된 직후 호출되는 메서드
getDerivedStateFromProps(): props가 변경될 때 호출되는 메서드
shouldComponentUpdate(): 컴포넌트가 업데이트되어야 하는지 결정하는 메서드
render(): UI를 렌더링하는 메서드
componentDidUpdate(): 컴포넌트가 업데이트된 직후 호출되는 메서드
componentWillUnmount(): 컴포넌트가 DOM에서 언마운트되기 전에 호출되는 메서드
이펙트는 외부 시스템을 현재 prop과 state에 동기화하는 방법을 설명합니다. 코드가 변경되면 이 동기화를 더 자주 또는 덜 자주 수행해야 합니다.
이 점을 설명하기 위해 컴포넌트를 채팅 서버에 연결하는 이펙트를 예로 들어보겠습니다:
이펙트의 본문에는 동기화 시작 방법이 명시되어 있습니다:
이펙트에서 반환되는 클린업 함수는 동기화를 중지하는 방법을 지정합니다:
직관적으로 React는 컴포넌트가 마운트될 때 동기화를 시작하고 컴포넌트가 마운트 해제될 때 동기화를 중지할 것이라고 생각할 수 있습니다. 하지만 이것이 이야기의 끝이 아닙니다! 때로는 컴포넌트가 마운트된 상태에서 동기화를 여러 번 시작하고 중지해야 할 수도 있습니다.
이러한 동작이 필요한 이유와 발생 시기, 그리고 이러한 동작을 제어할 수 있는 방법을 살펴보겠습니다.
Note
일부 이펙트는 클린업 함수를 전혀 반환하지 않습니다. 대부분의 경우 함수를 반환하고 싶겠지만, 그렇지 않은 경우 React는 아무 작업도 하지 않는 빈 클린업 함수를 반환한 것처럼 동작합니다.
이 ChatRoom
컴포넌트가 사용자가 드롭다운에서 선택한 roomId
프로퍼티를 받는다고 가정해 보세요. 처음에 사용자가 "일반"
채팅방을 roomId
로 선택했다고 가정해 봅시다. 앱에 "일반"
채팅방이 표시됩니다:
UI가 표시되면 React가 Effect를 실행하여 동기화를 시작합니다. "일반"
방에 연결됩니다:
지금까지는 괜찮습니다.
나중에 사용자가 드롭다운에서 다른 방을 선택합니다(예: "여행"). 먼저 React가 UI를 업데이트합니다:
다음에 어떤 일이 일어날지 생각해 보세요. 사용자는 UI에서 "여행"이 선택된 대화방임을 알 수 있습니다. 하지만 지난번에 실행된 이펙트는 여전히 "일반" 대화방에 연결되어 있습니다. roomId 프로퍼티가 변경되었기 때문에 그 때 이펙트('일반' 방에 연결)가 수행한 작업이 더 이상 UI와 일치하지 않습니다.
이 시점에서 React가 두 가지 작업을 수행하기를 원합니다:
roomId
와의 동기화를 중지합니다('일반'
룸에서 연결 해제).roomId
와 동기화 시작 ( "여행"
객실과 연결)다행히도 여러분은 이미 이 두 가지를 수행하는 방법을 React에 가르쳤습니다! Effect의 본문은 동기화를 시작하는 방법을 지정하고, 클린업 함수는 동기화를 중지하는 방법을 지정합니다. 이제 React가 해야 할 일은 올바른 순서로 올바른 프로퍼티와 상태로 호출하기만 하면 됩니다. 정확히 어떻게 일어나는지 살펴보겠습니다.
ChatRoom 컴포넌트의 roomId 프로퍼티가 새로운 값을 받았다는 것을 기억하세요. 이전에는 "일반"이었지만 이제는 "여행"입니다. 다른 방에 다시 연결하려면 React가 Effect를 다시 동기화해야 합니다.
동기화를 중지하기 위해 React는 "일반"
방에 연결한 후 Effect가 반환한 클린업 함수를 호출합니다. roomId
가 "일반"
이므로, 정리 함수는 "일반"
방에서 연결을 끊습니다:
그러면 React는 이 렌더링 중에 여러분이 제공한 Effect를 실행합니다. 이번에는 roomId가 "travel"이므로 "travel" 채팅방과 동기화되기 시작합니다(결국 클린업 함수도 호출될 때까지):
덕분에 이제 사용자가 UI에서 선택한 방과 동일한 방에 연결됩니다. 재앙을 피했습니다!
컴포넌트가 다른 roomId
로 다시 렌더링할 때마다 이펙트가 다시 동기화됩니다. 예를 들어 사용자가 roomId
를 "travel"
에서 "music"
으로 변경한다고 가정해 봅시다. React는 다시 정리 함수를 호출하여 Effect 동기화를 중지합니다("여행"
방에서 연결을 끊습니다). 그런 다음 새 roomId
프로퍼티로 본문을 실행하여 동기화를 다시 시작합니다("음악"
방에 연결).
마지막으로 사용자가 다른 화면으로 이동하면 ChatRoom이 마운트 해제됩니다. 이제 연결 상태를 유지할 필요가 전혀 없습니다. React는 마지막으로 Effect 동기화를 중지하고 "음악" 채팅방에서 연결을 끊습니다.
state, props 변경 -> UI변경 -> 이펙트 본문 실행 -> state, props 변경 -> UI변경 -> 이펙트 클린업 함수 실행 -> 이펙트 본문 실행
ChatRoom
컴포넌트의 관점에서 일어난 모든 일을 요약해 보겠습니다:
roomId
가 일반
으로 설정된 ChatRoom
마운트여행
으로 설정된 ChatRoom
업데이트됨roomId
가 음악
으로 설정된 ChatRoom
업데이트됨ChatRoom
마운트 해제컴포넌트 라이프사이클의 각 시점에서 이펙트는 서로 다른 작업을 수행했습니다:
일반
방에 연결된 효과일반
방에서 분리되어 여행
방에 연결되었습니다.여행
룸에서 연결이 끊어지고 음악
룸에 연결된 효과음악
룸에서 연결이 끊어진 효과이제 이펙트 자체의 관점에서 무슨 일이 일어났는지 생각해 봅시다:
이 코드의 구조는 어떤 일이 일어났는지 겹치지 않는 기간의 연속으로 보는 데 영감을 줄 수 있습니다:
"일반"
방에 연결된 효과(연결이 끊어질 때까지)"여행"
방에 연결된 효과 (연결이 끊어질 때까지)"음악"
룸에 연결된 효과 (연결이 끊어질 때까지)이전에는 컴포넌트의 관점에서 생각했습니다. 컴포넌트의 관점에서 보면 이펙트를 '렌더링 후' 또는 '마운트 해제 전'과 같은 특정 시점에 실행되는 '콜백' 또는 '라이프사이클 이벤트'로 생각하기 쉬웠습니다. 이러한 사고 방식은 매우 빠르게 복잡해지므로 피하는 것이 가장 좋습니다.
대신 항상 한 번에 하나의 시작/중지 사이클에만 집중하세요. 구성 요소를 마운트, 업데이트 또는 마운트 해제하는 것은 중요하지 않습니다. 동기화를 시작하는 방법과 중지하는 방법만 설명하면 됩니다. 이 작업을 잘 수행하면 필요한 횟수만큼 이펙트를 시작하고 중지할 수 있는 탄력성을 확보할 수 있습니다.
JSX를 생성하는 렌더링 로직을 작성할 때 컴포넌트가 마운트되는지 업데이트되는지 생각하지 않는 것을 떠올리면 이해가 쉬울 것입니다. 화면에 무엇이 표시되어야 하는지 설명하면 나머지는 React가 알아서 처리합니다.
React에서 이펙트(effect)가 다시 동기화(synchronize)될 수 있는지 확인하는 가장 일반적인 방법은 useEffect 훅의 두 번째 매개변수(dependency array)를 사용하는 것입니다.
이 매개변수(dependency array)는 useEffect 훅이 의존하는 변수들의 배열로, 이 배열 안에 있는 변수가 변경될 때마다 useEffect가 실행됩니다. 이를 통해 useEffect 내부의 코드가 해당 변수에 의존하는 상태를 항상 최신 상태로 유지할 수 있게 됩니다.
따라서 만약 이펙트가 다시 동기화될 수 있는지 확인하려면, useEffect의 의존성 배열에 해당하는 변수들을 변경하면서 해당 이펙트가 제대로 작동하는지 확인해야 합니다.
그러나 때로는 이 배열에 올바른 변수를 추가하는 것이 어렵거나 불가능할 수도 있습니다. 이 경우, React 개발자 도구를 사용하여 해당 컴포넌트가 렌더링되는 시기에 이펙트가 언제 실행되는지 추적할 수 있습니다. 이를 통해 이펙트가 다시 동기화되는지 여부를 확인할 수 있습니다.
React 개발자 도구 사용법
- React 개발자 도구를 열고 디버그하고자 하는 React 애플리케이션을 실행합니다.
- 웹 페이지에서 오른쪽 클릭을 하여 "React" 항목을 선택하고, "React 개발자 도구 열기"를 선택합니다.
- React 개발자 도구에서 "Components" 탭을 선택하고, 추적하려는 컴포넌트를 선택합니다.
- "Profiler" 탭을 선택하고, "Record" 버튼을 클릭하여 프로파일링을 시작합니다.
- 애플리케이션에서 해당 컴포넌트가 렌더링되는 시기에 이펙트가 실행되는지 확인합니다.
- 프로파일링을 멈추려면 "Stop" 버튼을 클릭합니다.
- 이펙트가 실행되는 시기와 관련된 정보를 확인하려면, "Flamegraph" 차트를 클릭하고, 해당 이펙트의 이름을 선택합니다.
- "Detail" 탭을 선택하여, 이펙트가 실행되는 시간과 관련된 정보를 확인할 수 있습니다.
React에서 useEffect의 의존성 배열(dependency array)을 사용하여 이펙트가 다시 동기화될 수 있는지 확인하는 예시 코드를 보여드리겠습니다.
위 코드에서, useEffect의 의존성 배열에 count를 추가하여 이펙트가 count가 변경될 때마다 실행되도록 설정하였습니다. 이를 통해 count 상태가 변경될 때마다 이펙트가 다시 동기화되어 실행되는지 확인할 수 있습니다.
또 다른 예시로, 아래 코드에서는 useEffect의 의존성 배열에 props.id를 추가하여 props.id가 변경될 때마다 이펙트가 실행되도록 설정하였습니다.
위 코드에서, useEffect의 의존성 배열에 props.id를 추가하여 props.id가 변경될 때마다 fetchData 함수가 호출되어 데이터를 다시 가져오도록 설정하였습니다. 이를 통해 props.id가 변경될 때마다 이펙트가 다시 동기화되어 데이터가 업데이트되는지 확인할 수 있습니다.
React가 이펙트를 다시 동기화해야 한다는 것을 인식하는 방법은 다음과 같습니다.
의존성 배열이 변경될 때: useEffect의 의존성 배열에 있는 변수가 변경될 때마다 이펙트가 다시 동기화됩니다. 이는 의존성 배열에 있는 변수가 업데이트될 때마다 이펙트를 재실행하여 최신 상태를 유지할 수 있도록 해줍니다.
useEffect 내부에서 state나 props를 변경할 때: 이 경우, React는 이펙트 내부에서 발생한 변경사항이 컴포넌트의 렌더링에 영향을 미칠 수 있다고 판단하고 이펙트를 다시 동기화합니다. 따라서 useEffect 내부에서 state나 props를 변경하는 경우, 해당 변경사항이 필요한 경우에만 수행하도록 조건문을 추가하여 최적화할 수 있습니다.
useEffect에 인자로 전달된 함수 내부에서 Promise나 async/await를 사용하는 경우: Promise나 async/await를 사용하여 데이터를 가져오는 경우, 이를 위해 비동기 처리를 수행하는 함수가 실행됩니다. 이 경우, React는 이펙트가 Promise나 async/await에서 반환된 데이터를 사용하므로 해당 데이터가 변경될 때 이펙트를 다시 동기화합니다.
위의 경우 외에도, React는 이펙트의 실행 시점을 결정하기 위해 내부적으로 다양한 최적화 기법을 사용합니다. 예를 들어, 브라우저에서 발생하는 이벤트나 서버에서 데이터를 가져오는 등의 비동기 작업은 일반적으로 브라우저가 리소스를 사용할 수 있을 때만 수행됩니다. 따라서 React는 이러한 작업이 발생할 때마다 이펙트를 다시 동기화하여 최신 상태를 유지할 수 있도록 합니다.
React가 이펙트를 다시 동기화해야 한다는 것을 인식하는 예시 코드를 보여드리겠습니다.
위 코드에서, useEffect 내부에서 setCount 함수를 호출하여 count 상태를 변경하고 있습니다. 이는 useEffect가 다시 실행될 수 있는 변경사항이므로 React는 이를 인식하여 이펙트를 다시 동기화합니다.
다른 예시로, 아래 코드에서는 useEffect 내부에서 비동기 함수를 호출하여 데이터를 가져오고 있습니다.
위 코드에서, useEffect 내부에서 fetchData 함수를 호출하여 데이터를 가져오고 있습니다. 이는 비동기 작업이므로 useEffect가 다시 실행될 수 있는 변경사항이며, React는 이를 인식하여 이펙트를 다시 동기화합니다.
위의 예시 코드에서 useEffect가 다시 동기화되는 이유는 각각 다르지만, 모두 React가 이펙트의 실행 시점을 최적화하기 위해 내부적으로 다양한 기술과 알고리즘을 사용하고 있기 때문입니다.
이 로직은 이미 작성한 이펙트와 동시에 실행되어야 하므로 관련 없는 로직을 이펙트에 추가하지 마세요. 예를 들어 사용자가 방을 방문할 때 분석 이벤트를 전송하고 싶다고 가정해 봅시다. 이미 roomId에 의존하는 Effect가 있으므로 바로 거기에 분석 호출을 추가하고 싶을 수 있습니다:
하지만 나중에 이 Effect에 연결을 다시 설정해야 하는 다른 종속성을 추가한다고 가정해 보겠습니다. 이 이펙트가 다시 동기화되면 의도하지 않은 동일한 방에 대해 logVisit(roomId)도 호출하게 됩니다. 방문을 기록하는 것은 연결과는 별개의 프로세스입니다. 그렇기 때문에 두 개의 개별 효과로 작성해야 합니다:
코드의 각 이펙트는 별도의 독립적인 동기화 프로세스를 나타내야 합니다.
위의 예시에서는 한 이펙트를 삭제해도 다른 이펙트의 로직이 깨지지 않습니다. 이는 서로 다른 것을 동기화하므로 분리하는 것이 합리적이라는 것을 나타냅니다. 반면, 일관된 로직을 별도의 Effect로 분리하면 코드가 "더 깔끔해" 보일 수 있지만 유지 관리가 더 어려워집니다. 따라서 코드가 더 깔끔해 보이는지 여부가 아니라 프로세스가 동일한지 또는 분리되어 있는지를 고려해야 합니다.
이펙트는 두 개의 변수(serverUrl
및 roomId
)를 읽지만 종속성으로 roomId
만 지정했습니다:
serverUrl
이 종속성이 될 필요가 없는 이유는 무엇인가요?
이는 리렌더링으로 인해 serverUrl
이 변경되지 않기 때문입니다. 컴포넌트가 몇 번, 어떤 prop과 상태로 다시 렌더링하든 항상 동일합니다. serverUrl
은 절대 변하지 않으므로 종속성으로 지정하는 것은 의미가 없습니다. 결국, 종속성은 시간이 지남에 따라 변경될 때만 무언가를 수행합니다!
반면에 roomId
는 다시 렌더링할 때 달라질 수 있습니다. 컴포넌트 내부에서 선언된 프로퍼티, state
및 기타 값은 렌더링 중에 계산되고 React 데이터 흐름에 참여하기 때문에 반응형입니다.
serverUrl
이 상태 변수라면 반응형일 것입니다. 반응형 값은 종속성에 포함되어야 합니다:
serverUrl
을 종속성으로 포함하면 이펙트가 변경된 후 다시 동기화되도록 할 수 있습니다.
roomId
또는 serverUrl
과 같은 반응형 값을 변경할 때마다 이펙트가 채팅 서버에 다시 연결됩니다.
serverUrl과 roomId를 모두 컴포넌트 외부로 이동하면 어떻게 되나요?
이제 이펙트의 코드는 반응형 값을 사용하지 않으므로 종속성이 비어 있을 수 있습니다([]
).
컴포넌트의 관점에서 생각해보면, 빈 [] 의존성 배열은 이 Effect가 컴포넌트가 마운트될 때만 채팅방에 연결되고 컴포넌트가 마운트 해제될 때만 연결이 끊어진다는 것을 의미합니다. (React는 Effect의 로직을 스트레스 테스트하기 위해 개발 단계에서 한 번 더 동기화한다는 점을 기억하세요).
하지만 이펙트의 관점에서 생각하면 마운트 및 마운트 해제에 대해 전혀 생각할 필요가 없습니다. 중요한 것은 이펙트가 동기화를 시작하고 중지하는 작업을 지정한 것입니다. 현재는 반응형 종속성이 없습니다. 하지만 사용자가 시간이 지남에 따라 roomId 또는 serverUrl을 변경하기를 원한다면(그래서 반응형이어야 한다면) Effect의 코드는 변경되지 않습니다. 종속성에 추가하기만 하면 됩니다.
props와 state만 반응형 값인 것은 아닙니다. 이들로부터 계산하는 값도 반응형입니다. 프로퍼티나 상태가 변경되면 컴포넌트가 다시 렌더링되고 그로부터 계산된 값도 변경됩니다. 그렇기 때문에 이펙트가 사용하는 컴포넌트 본문의 모든 변수는 이펙트 종속성 목록에 있어야 합니다.
사용자가 드롭다운에서 채팅 서버를 선택할 수 있지만 설정에서 기본 서버를 구성할 수도 있다고 가정해 봅시다. 이미 설정 상태를 컨텍스트에 넣어서 해당 컨텍스트에서 설정
을 읽었다고 가정해 보겠습니다. 이제 props에서 선택한 서버와 컨텍스트에서 기본 서버를 기준으로 serverUrl
을 계산합니다:
이 예제에서 serverUrl
은 프로퍼티나 상태 변수가 아닙니다. 렌더링 중에 계산하는 일반 변수입니다. 하지만 렌더링 중에 계산되므로 재렌더링으로 인해 변경될 수 있습니다. 이것이 바로 반응형인 이유입니다.
컴포넌트 내부의 모든 값(컴포넌트 본문의 프롭, 상태, 변수 포함)은 반응형입니다. 모든 반응형 값은 다시 렌더링할 때 변경될 수 있으므로 반응형 값을 이펙트의 종속성으로 포함시켜야 합니다.
즉, 효과는 컴포넌트 본문의 모든 값에 "반응"합니다.
[DEEP DIVE] 전역 또는 변경 가능한 값이 종속성이 될 수 있나요?
변경 가능한 값(전역 변수 포함)은 반응하지 않습니다.
위치.경로명과 같은 변경 가능한 값은 종속성이 될 수 없습니다. 이 값은 변경 가능하므로 React 렌더링 데이터 흐름 외부에서 언제든지 변경할 수 있습니다. 이 값을 변경해도 컴포넌트가 다시 렌더링되지는 않습니다. 따라서 종속성에서 지정했더라도 React는 이펙트가 변경될 때 이펙트를 다시 동기화할지 알 수 없습니다. 또한 렌더링 도중(의존성을 계산할 때) 변경 가능한 데이터를 읽는 것은 렌더링의 순수성을 깨뜨리기 때문에 React의 규칙을 위반합니다. 대신,
useSyncExternalStore
를 사용하여 외부 변경 가능한 값을 읽고 구독해야 합니다.useSyncExternalStore는 React 애플리케이션에서 외부 스토어와 컴포넌트의 상태를 동기화하는 커스텀 훅입니다. 이 훅은 useEffect와 useState 훅을 사용하여 구현됩니다.
useSyncExternalStore 훅을 사용하여 React 애플리케이션에서 외부 스토어와 컴포넌트의 상태를 일관되게 유지할 수 있습니다.ref.current와 같이 변경 가능한 값이나 이 값에서 읽은 것 역시 종속성이 될 수 없습니다.
useRef
가 반환하는 ref 객체 자체는 종속성이 될 수 있지만현재
프로퍼티는 의도적으로 변경 가능합니다. 이를 통해 재렌더링을 트리거하지 않고도 무언가를 추적할 수 있습니다. 하지만 변경해도 다시 렌더링이 트리거되지 않기 때문에 반응형 값이 아니며, React는 이 값이 변경될 때 Effect를 다시 실행할지 알지 못합니다.이 페이지에서 아래에서 배우게 되겠지만, 린터는 이러한 문제를 자동으로 확인합니다.
린터가 React에 대해 구성된 경우, Effect 코드에서 사용되는 모든 반응형 값이 해당 종속성으로 선언되었는지 확인합니다. 예를 들어, roomId
serverUrl
이 모두 반응형이기 때문에 이것은 린트 오류입니다:
이것은 React 오류처럼 보일 수 있지만 실제로는 코드의 버그를 지적하는 것입니다. roomId
와 serverUrl
은 시간이 지남에 따라 변경될 수 있지만, 변경 시 Effect를 다시 동기화하는 것을 잊어버리고 있습니다. 결과적으로 사용자가 UI에서 다른 값을 선택한 후에도 초기 roomId
와 serverUrl
에 연결된 상태로 유지됩니다.
버그를 수정하려면 린터의 제안에 따라 이펙트의 종속 요소로 roomId 및 serverUrl을 지정하세요:
위의 샌드박스에서 이 수정 사항을 시도해 보세요. 지연 오류가 사라지고 필요할 때 채팅이 다시 연결되는지 확인합니다.
Note
어떤 경우에는 컴포넌트 내부에서 값이 선언되더라도 절대 변하지 않는다는 것을 React가 알고 있습니다. 예를 들어,
useState
에서 반환된 set 함수와 useRef에서 반환된 ref 객체는 재렌더링 시 변경되지 않도록 보장되는 안정적 값입니다. 안정된 값은 반응하지 않으므로 린터를 사용하면 목록에서 생략할 수 있습니다. 그러나 이러한 값을 포함하는 것은 허용됩니다. 변경되지 않으므로 상관없습니다.
이전 예제에서는 roomId
와 serverUrl
를 종속성으로 나열하여 린트 오류를 수정했습니다.
그러나 대신 이러한 값이 반응형 값이 아니라는 것, 즉 재렌더링의 결과로 변경될 수 없다는 것을 린터에 "증명"할 수 있습니다. 예를 들어 serverUrl
과 roomId
가 렌더링에 의존하지 않고 항상 같은 값을 갖는다면 컴포넌트 외부로 옮길 수 있습니다. 이제 종속성이 될 필요가 없습니다:
이펙트 내부로 이동할 수도 있습니다. 렌더링 중에 계산되지 않으므로 반응하지 않습니다:
이펙트는 반응형 코드 블록입니다. 내부에서 읽은 값이 변경되면 다시 동기화됩니다. 상호작용당 한 번만 실행되는 이벤트 핸들러와 달리 이펙트는 동기화가 필요할 때마다 실행됩니다.
종속성을 "선택"할 수 없습니다. 종속성에는 이펙트에서 읽은 모든 리액티브 값이 포함되어야 합니다. 린터가 이를 강제합니다. 때때로 이로 인해 무한 루프와 같은 문제가 발생하거나 이펙트가 너무 자주 다시 동기화될 수 있습니다. 린터를 억제하여 이러한 문제를 해결하지 마세요! 대신 시도할 수 있는 방법은 다음과 같습니다:
이펙트가 독립적인 동기화 프로세스를 나타내는지 확인하세요. 이펙트가 아무것도 동기화하지 않는다면 불필요한 것일 수 있습니다. 여러 개의 독립적인 것을 동기화하는 경우 분할하세요.
prop이나 state에 '반응'하지 않고 이펙트를 다시 동기화하지 않고 최신 값을 읽으려면 이펙트를 반응하는 부분(이펙트에 유지)과 반응하지 않는 부분(이벤트 함수라는 것으로 추출)으로 분리할 수 있습니다. 이벤트와 이펙트를 분리하는 방법에 대해 자세히 읽어보세요.
객체와 함수를 종속성으로 사용하지 마세요. 렌더링 중에 오브젝트와 함수를 생성한 다음 이펙트에서 읽으면 렌더링할 때마다 오브젝트와 함수가 달라집니다. 그러면 매번 이펙트를 다시 동기화해야 합니다. 이펙트에서 불필요한 종속성을 제거하는 방법에 대해 자세히 읽어보세요.
⚠️ Pitfall
린터는 여러분의 친구이지만 그 힘은 제한되어 있습니다. 린터는 종속성이 잘못되었을 때만 알 수 있습니다. 각 사례를 해결하는 최선의 방법은 알지 못합니다. 만약 린터가 종속성을 제안하지만 이를 추가하면 루프가 발생한다고 해서 링터를 무시해야 한다는 의미는 아닙니다. 해당 값이 반응적이지 않고 종속성이 될 필요가 없도록 Effect 내부(또는 외부)의 코드를 변경해야 한다는 뜻입니다.
기존 코드베이스가 있는 경우 이와 같이 린터를 억제하는 이펙트가 있을 수 있습니다:
useEffect(() => { // ... // 🔴 Avoid suppressing the linter like this: // eslint-ignore-next-line react-hooks/exhaustive-deps }, []);
다음 페이지에서는 규칙을 위반하지 않고 이 코드를 수정하는 방법을 알아보세요. 언제나 고칠 가치가 있습니다!
요약