- Effect는 컴포넌트와 다른 생명주기를 갖는다.
- Effect는 컴포넌트와 달리 동기화를 시작하고 나중에 동기화를 중지하는 두 가지 작업만 할 수 있는데, 이 사이클은 props와 state에 의존하는 Effect의 경우 여러 번 발생할 수 있다.
The lifecycle of an Effect (Effect의 생명주기)
- React의 모든 컴포넌트는 동일한 생명주기를 거친다.
1. 컴포넌트는 화면에 추가될 때 마운트 된다.
2. 컴포넌트는 새로운 props나 state를 받으면 업데이트 된다.
3. 화면에서 제거되면 컴포넌트가 마운트 해제된다.
- 각 Effect를 컴포넌트의 생명주기와 독립적으로 생각해라.
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
// 동기화 시작
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// 동기화 중지
connection.disconnect();
};
}, [roomId]);
// ...
}
- 위 예제는 채팅 서버에 연결하는 컴포넌트다.
- Effect 본문에는 동기화 시작과 Effect에서 반환하는 클린업 함수에는 동기화를 중지하는 방법이 지정되어 있다.
- 위 예제에서 컴포넌트가 마운트될 때 동기화를 시작하고, 마운트 해제될 때 동기화를 중지한다.
Why synchronization may need to happen more than once(동기화가 두 번 이상 수행되어야 하는 이유)
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
// 동기화 시작
const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
connection.connect();
// 동기화 중지
return () => {
connection.disconnect(); // Disconnects from the "general" room
};
}, [roomId]);
return <h1>Welcome to the {roomId} room!</h1>;
}
- 위 예제에서 roomId가 처음 "general"일 때, UI가 표시되고 Effect가 실행되어 "general" 방에 연결된다.
- 사용자가 "general"이 아닌, "travel" 방을 선택했을 때, roomId prop은 "travel"로 바뀌고 React는 UI를 업데이트 한다.
- 그리고 roomId prop이 변경되었기 때문에 이전에 Effect("general" 연결)가 수행한 작업이 UI와 일치하지 않기 때문에, React는 "general" 방의 동기화를 중지하고, "travel"방의 동기화를 시작한다.
How React re-synchronizes your Effects(React가 Effect를 재동기화 하는 방법)
- 위의 예제에서처럼 동기화를 중지하기 위해 React는 "general" 방에 연결한 후 Effect가 반환하는 클린업 함수를 호출한다.
- 그리고 React는 "travel" 채팅방과 동기화를 시작한다.
- 만약 사용자가 "travel"에서 "music"으로 roomId를 변경했을 때에도, 동일한 방법으로 이전의 동기화를 중지하고, 새로운 동기화를 시작한다.
- 사용자가 다른 화면으로 이동하면 ChatRoom 컴포넌트가 마운트 해제되고, React는 Effect의 동기화를 중지하고 채팅방 연결을 끊는다.
Thinking from the Effect's perspective(Effect의 관점에서 생각하기)
- 컴포넌트의 관점에서 생각하면 Effect를 "렌더링 후" 또는 "마운트 해제 전"과 같은 특정 시점에 실행되는 "콜백" 또는 "생명주기 이벤트"로 생각하기 쉽지만, 이러한 사고 방식은 매우 빠르게 복잡해지므로 피하는게 좋다.
- 대신, 컴포넌트를 마운트, 업데이트 또는 마운트 해제하는 것이 아닌, 항상 한 번에 하나의 시작/중지 사이클에 집중하자.
- 동기화를 시작하는 방법과 중지하는 방법만 생각하면 필요한 횟수만큼 Effect를 시작하고 중지할 수 있는 탄력성을 확보할 수 있다.
How React verifies that your Effect can re-synchronize(React가 Effect의 재동기화 가능 여부를 확인하는 방법)
- 개발 모드에서 React는 즉시 강제로 동기화를 수행하여 Effect가 다시 동기화될 수 있는지 확인한다. 즉, Effect를 한 번 더 시작하고 중지하여 클린업 함수를 잘 구현했는지 확인한다.
How React knows that it needs to re-synchronize the Effect(React가 Effect의 재동기화 필요성을 인식하는 방법)
- React는 컴포넌트가 다시 렌더링될 때마다 사용자가 전달한 의존성 배열을 보고, 의존성 배열의 값 중 하나라도 이전 렌더링 중에 전달한 동일한 지점의 값과 다르면 React는 Effect를 다시 동기화한다.
Each Effect represents a separate synchronization process(각각의 Effect는 별도의 동기화 프로세스를 나타냅니다.)
- 코드의 각 Effect는 별도의 독립적인 동기화 프로세스를 나타내야 한다.
- 즉, 서로 다른것을 동기화한다면 Effect를 분리하는 것이 합리적이다.
- 다만, 일관된 로직을 별도의 Effect로 분리하면 코드가 "더 깔끔해" 보일 수 있지만 유지 관리가 더 어려워진다.
- 즉, Effect 분리 여부는 프로세스가 동일한지 또는 분리되어 있는지를 고려해야 한다.
Effects "react" to reactive values(Effect는 "반응형 값"에 반응합니다)
- Effect의 의존성에는 컴포넌트가 리렌더링이 일어날 때도 항상 동일한 값이 아닌 렌더링 중에 계산되고 React 데이터 흐름에 참여하는 반응형 값, 즉 컴포넌트 내부에 선언된 props, state 및 기타 값이 들어가야 한다.
// serverUrl은 변하지 않는 값으로 의존성 배열에 추가할 필요가 없다.
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
// serverUrl을 state 변수일때, 즉, 반응형 값일때는 의존성에 포함되어야 한다.
function ChatRoom({ roomId }) { // Props change over time
// props는 시간에 따라 바뀜
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State may change over time
// state는 바뀔 수 있음
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads props and state
// Effect는 props와 state를 읽음
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So you tell React that this Effect "depends on" on props and state
// 이 Effect가 props 및 state에 의존함을 React에 알림
// ...
}
All variables declared in the component body are reactive(컴포넌트 본문에서 선언된 모든 변수는 반응형입니다)
- props와 state만 반응형 값이 아니라, 이들로부터 계산하는 값들 역시 반응형이다.
- props나 state가 변경되면 컴포넌트가 다시 렌더링되고 그로부터 계산된 값도 변경되기 때문에, Effect가 사용하는 컴포넌트 본문의 모든 변수는 Effect 의존성 목록에 있어야 한다.
function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive
const settings = useContext(SettingsContext); // settings is reactive
const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes!
// ...
}
- 위 예제에서 serverUrl은 prop이나 state 변수는 아니지만, 렌더링 중에 계산하는 일반 변수인데, 렌더링 중에 계산되므로 리렌더링으로 인해 변경될 수 있다. 따라서, serverUrl은 반응형 변수이기 때문에 의존성 배열에 추가해야 한다.
- 즉, 컴포넌트 내부의 모든 값(컴포넌트 본문의 props, state, 변수 포함)은 반응형이고, 반응형 값은 다시 렌더링할 때 변경될 수 있으므로 반응형 값을 Effect의 의존성에 포함시켜야 한다.
* Can global or mutable values be dependencies?(전역 또는 변이 가능한 값이 의존성이 될 수 있나요?)
- 변이 가능한 값(전역 변수 포함)은 반응하지 않는다.
- React는 변이 가능한 값이 변경될 때 리렌더링을 촉발하지 않기 때문에, Effect를 다시 동기화해야 하는지 알 수 없고, 렌더링 도중(의존성을 계산할 때) 변경 가능한 데이터를 읽는 것은 렌더링의 순수성을 깨뜨리기 때문에 React 규칙을 위반하는 것이기도 하다.
- 즉, 변이 가능한 값은 의존성이 될 수 없고, 대신 useSyncExternalStore를 사용하여 외부 변경 가능한 값을 읽고 구독해야 한다.
What to do when you don't want to re-synchronize(재동기화를 원치 않는 경우엔 어떻게 해야 하나요?)
- 의존성을 "선택"할 수는 없다. 의존성에는 Effect에서 읽은 모든 반응형 값이 포함되어야 한다.
- 만약 Effect가 독립적인 동기화 프로세스를 나타내고 있다면, 즉 아무 것도 동기화하지 않는다면 불필요한 것일 수도 있다.
- "반응"하지 않고 Effect를 재동기화하지 않으면서 props나 state의 최신 값을 읽으려면 Effect를 반응하는 부분과 반응하지 않는 부분으로 분리할 수 있다.
- 객체와 함수를 의존성으로 사용하지 않아야 한다.
* 참고 : React 공식문서(https://react-ko.dev/learn)
'이것저것 스터디📚 > React - 공식 문서' 카테고리의 다른 글
React 공식문서 -Removing Effect Dependencies(Effect 의존성 제거하기) (0) | 2023.08.23 |
---|---|
React 공식문서 -Separating Events from Effects(이벤트와 Effect 분리하기) (0) | 2023.08.23 |
React 공식문서 -You Might Not Need an Effect(Effect가 필요하지 않을 수도 있습니다) (0) | 2023.08.17 |
React 공식문서 -Synchronizing with Effects(Effect와 동기화 하기) (0) | 2023.08.17 |
React 공식문서 -Manipulating the DOM with Refs(ref로 DOM 조작하기) (0) | 2023.08.16 |