-
Notifications
You must be signed in to change notification settings - Fork 2
Description
참고 자료: https://ko.react.dev/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect
TL;DR
- 컴포넌트는 마운트, 업데이트, 마운트 해제할 수 있습니다.
- 각 effect는 주변 컴포넌트와 별도의 생명주기를 가집니다.
- 각 effect는 시작 및 중지할 수 있는 별도의 동기화 프로세스를 설명합니다.
- effect를 작성하고 읽을 때는 컴포넌트의 관점(마운트, 업데이트 또는 마운트 해제 방법)이 아닌 개별 effect의 관점(동기화 시작 및 중지 방법)에서 생각하세요.
- 컴포넌트 본문 내부에 선언된 값은 “반응형”입니다.
- 반응형 값은 시간이 지남에 따라 변경될 수 있으므로 effect를 다시 동기화해야 합니다.
- 린터는 effect 내부에서 사용된 모든 반응형 값이 종속성으로 지정되었는지 확인합니다.
- 린터에 의해 플래그가 지정된 모든 오류는 합법적인 오류입니다. 규칙을 위반하지 않도록 코드를 수정할 방법은 항상 있습니다.
effect의 생명주기
컴포넌트의 생명주기(마운트, 마운트 해제)와 독립적으로 effect를 생각해야 합니다. effect를 '렌더링 후' 또는 '마운트 해제 전'과 같은 특정 시점에 실행되는 '콜백' 또는 '생명주기 이벤트'로 생각하는 것은 매우 빠르게 복잡해집니다.
useEffect(() => {
// roomId로 지정된 방에 연결된 effect...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// ...연결이 끊어질 때까지
connection.disconnect();
};
}, [roomId]);- effect가 "general" 대화방에 연결됨
- "general" 방에서 연결이 끊어지고 "travel" 방에 연결된 effect
- "travel" 방에서 연결이 끊어지고 "music" 방에 연결된 effect
- "music" 방에서 연결이 끊어진 effect
대신 항상 한 번에 하나의 시작/중지 사이클에만 집중하세요. 동기화를 시작하는 방법과 중지하는 방법만 설명하면 됩니다.
React가 effect를 다시 동기화해야 한다는 것을 인식하는 방법
우리는 알고 있습니다. effect의 의존성 배열에 기재해주면 됩니다.
컴포넌트가 다시 렌더링 될 때마다 React는 전달한 의존성 배열을 살펴봅니다. 배열의 값 중 하나라도 이전 렌더링 중에 전달한 동일한 지점의 값과 다르면 React는 effect를 다시 동기화합니다. (이 값들은 Object.is로 비교합니다.)
각 effect는 별도의 동기화 프로세스를 나타냅니다.
function ChatRoom({ roomId }) {
useEffect(() => {
**logVisit(roomId);**
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}위 로직은 이미 작성한 effect와 동시에 실행되어야 하므로 관련 없는 로직을 effect에 추가하지 마세요. 두 개의 개별 effect로 작성하세요.
function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
}, [roomId]);
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
// ...
}, [roomId]);
// ...
}코드의 각 effect는 별도의 독립적인 동기화 프로세스를 나타내야 합니다.
반응형 값에 "반응"하는 effect
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}effect에서 두 개의 변수(serverUrl 및 roomId)를 읽지만 종속성으로 roomId만 지정했습니다.
serverUrl이 종속성이 될 필요가 없는 이유는 무엇인가요? 이는 재 렌더링으로 인해 serverUrl이 변경되지 않기 때문입니다. serverUrl은 절대 변경되지 않으므로 종속성으로 지정하는 것은 의미가 없습니다. 결국 종속성은 시간이 지남에 따라 변경될 때만 무언가를 수행합니다!
serverUrl이 state 변수라면 반응형일 것입니다. 반응형 값은 종속성에 포함되어야 합니다.
컴포넌트 본문에서 선언된 모든 변수는 반응형입니다.
props와 state만 반응형 값인 것은 아닙니다. 이들로부터 계산하는 값도 반응형입니다. props나 state가 변경되면 컴포넌트가 다시 렌더링 되고 그로부터 계산된 값도 변경됩니다. 이 때문에 effect에서 사용하는 컴포넌트 본문의 모든 변수는 effect 종속성 목록에 있어야 합니다.
function ChatRoom({ roomId, selectedServerUrl }) { // roomId는 반응형입니다.
const settings = useContext(SettingsContext); // settings는 반응형입니다.
const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl는 반응형입니다.
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // effect는 roomId 와 serverUrl를 읽습니다.
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // 따라서 둘 중 하나가 변경되면 다시 동기화해야 합니다!
// ...
}React는 모든 반응형 값을 종속성으로 지정했는지 확인합니다.
린터가 React에 대해 구성된 경우, effect의 코드에서 사용되는 모든 반응형 값이 종속성으로 선언되었는지 확인합니다.
function ChatRoom({ roomId }) { // roomId는 반응형입니다.
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl는 반응형입니다.
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // <-- 여기 무언가 잘못되었습니다!
}이것은 React 오류처럼 보일 수 있지만 실제로는 코드의 버그를 지적하는 것입니다.
📓 중요합니다!
어떤 경우에는 컴포넌트 내부에서 값이 선언되더라도 절대 변하지 않는다는 것을 React가 알고 있습니다. 예를 들어, useState에서 반환되는 set 함수와 useRef에서 반환되는 ref 객체는 안정적이며, 다시 렌더링해도 변경되지 않도록 보장됩니다. 안정된 값은 반응하지 않으므로 목록에서 생략할 수 있습니다. 이러한 값은 변경되지 않으므로 포함해도 상관없습니다.
다시 동기화하지 않으려는 경우 어떻게 해야 하나요?
이전 예시에서 roomId와 serverUrl을 종속성으로 나열하여 린트 오류를 수정했습니다.
그러나 대신 이러한 값이 반응형 값이 아니라는 것, 즉 재 렌더링의 결과로 변경될 수 없다는 것을 린터에 "증명"할 수 있습니다. 예를 들어 두 값이 렌더링에 의존하지 않고 항상 같은 값을 갖는다면 컴포넌트 외부로 옮길 수 있습니다.
const serverUrl = 'https://localhost:1234'; // serverUrl는 반응형이 아닙니다.
const roomId = 'general'; // roomId는 반응형이 아닙니다.
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ 선언된 모든 종속성
// ...
}effect 내부로 이동할 수도 있습니다. 렌더링 중에 계산되지 않으므로 반응하지 않습니다.
function ChatRoom() {
useEffect(() => {
const serverUrl = 'https://localhost:1234'; // serverUrl는 반응형이 아닙니다.
const roomId = 'general'; // roomId는 반응형이 아닙니다.
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ 선언된 모든 종속성
// ...
}effect는 반응형 코드 블록입니다. 내부에서 읽은 값이 변경되면 다시 동기화됩니다. 상호작용당 한 번만 실행되는 이벤트 핸들러와 달리 effect는 동기화가 필요할 때마다 실행됩니다.
종속성을 "선택"할 수 없습니다. 종속성에는 effect에서 읽은 모든 반응형 값이 포함되어야 합니다. 린터가 이를 강제합니다. 때때로 이에 따라 무한 루프와 같은 문제가 발생하거나 effect가 너무 자주 다시 동기화될 수 있습니다. 린터를 억제하여 이러한 문제를 해결하지 마세요! 대신 시도할 방법은 다음과 같습니다.
- effect가 독립적인 동기화 프로세스를 나타내는지 확인하세요. effect가 아무것도 동기화하지 않는다면 불필요한 것일 수 있습니다. 여러 개의 독립적인 것을 동기화하는 경우 분할하세요.
- props나 state에 "반응"하지 않고 effect를 다시 동기화하지 않고 최신 값을 읽으려면 effect를 반응하는 부분(effect에 유지할 것)과 반응하지 않는 부분(effect 이벤트라고 하는 것으로 추출할 수 있는 것)으로 분리하면 됩니다.
- 객체와 함수를 종속성으로 사용하지 마세요. 렌더링 중에 오브젝트와 함수를 생성한 다음 effect에서 읽으면 렌더링할 때마다 오브젝트와 함수가 달라집니다. 그러면 매번 effect를 다시 동기화해야 합니다.
⚠️ 주의하세요!
린터는 종속성이 잘못되었을 때만 알 수 있습니다. 각 사례를 해결하는 최선의 방법은 알지 못합니다. 만약 린터가 종속성을 제안하지만 이를 추가하면 루프가 발생한다고 해서 린터를 무시해야 한다는 의미는 아닙니다. 해당 값이 반응적이지 않고 종속성이 될 필요가 없도록 effect 내부(또는 외부)의 코드를 변경해야 합니다.
기존 코드베이스가 있는 경우 이처럼 린터를 억제하는 effect가 있을 수 있습니다.
useEffect(() => {
// ...
// 🔴 이런 식으로 린트를 억누르지 마세요.
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);