해당 내용은 2022년 11월 08일 React Docs BETA 버전을 토대로 작성되었습니다. - 링크
👉 사전 지식 - Purity and Side Effect
useEffect란 ?
useEffect란 컴포넌트를 외부 시스템과 동기화할 수 있는 React hook이다.
컴포넌트를 외부 시스템과 동기화할 수 있다는 게 무슨 의미일까?
때때로 컴포넌트가 페이지에 표시되는 동안 네트워크, 일부 브라우저 API 또는 외부 라이브러리에 연결된 상태를 유지해야할 수 있다. 이러한 시스템들은 React에 의해 제어되지 않으므로 외부 시스템이라고 한다.
이러한 외부 시스템과 컴포넌트를 연결할 수 있는 역할을 하는 것이 useEffect hook인 것이다.
즉, 컴포넌트에서 Side Effect를 사용할 수 있게하는 것이다.
useEffect의 Effect는 React 관련 정의이며 렌더링으로 인한 Side Effect를 나타낸다.
React에서 말하는 Effect는 무엇이며 이벤트와 어떻게 다른가?
Effects를 사용하기 전에, React 컴포넌트 내부의 두가지 유형의 로직에 익숙해져야 한다.
- 컴포넌트는 순수해야한다. 수학 공식과 마찬가지로 결과만 계산해야 하며 다른 작업은 수행하지 않아야 한다.
- 이벤트 처리기는 단순히 계산하는 것이 아닌 작업을 수행하는 컴포넌트의 내부 중첩 함수이다. 입력 필드를 업데이트하거나 HTTP POST 요청을 제출하여 제품을 구매하거나 사용자가 다른 화면으로 이동할 수 있다. 이벤트 헨들러는 side effects(프로그램 상태 변경)를 포함하며 특정 사용자 액션(버튼 클릭 또는 입력 등)에 의해 발생한다.
이러한 설명들이 충분하지 않을 때가 있다. 예를 들어, 화면에 보일 때마다 채팅 서버에 연결해야하는 컴포넌트를 생각해보자. 서버에 연결하는 것은 순수한 계산이 아닌 side effect이므로 렌더링 중에 발생할 수 없다. 그렇다고 클릭과 같은 특정한 이벤트가 발생하는 것도 아니다.
이를 Effects를 사용하여 특정 이벤트가 아닌 렌더링 자체로 인해 발생하는 side effect를 지정할 수 있는 것이다. 채팅에서 메세지를 보내는 것은 사용자가 특정 버튼을 클릭하여 직접 발생하는 것이기에 이벤트이지만, 서버 연결 설정은 컴포넌트가 나타나게 된 상호작용에 관계없이 수행되어야 하기에 Effects이다. Effects는 React 컴포넌트들이 외부 시스템과 동기화하기 좋은 때인 화면이 업데이트되고 렌더링 프로세스가 끝날때 실행된다.
Effects를 작성하는 방법
1. Effects를 선언한다. 기본적으로 렌더링할 때마다 실행된다.
2. Effect dependencies를 지정하여 필요할 때만 리렌더하도록 한다. 예를 들어, 채팅방 연결 및 해제는 컴포넌트가 나타났다가 사라지거나 대화방이 변경될 때만 발생해야 한다.
3. 필요한 경우 cleanup을 추가한다. 일부 Effects는 수행 중인 작업을 중지, 실행, 취소 또는 정리하는 방법을 지정해야 한다. 예를 들어, 연결에서는 연결 해제, 구독에는 구독 취소가 필요하기에 cleanup 함수를 반환하여 이러한 작업을 수행한다.
1. 선언
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Code here will run after *every* render
});
return <div />;
}
컴포넌트가 렌더링될 때마다 React는 화면을 업데이트하고, useEffect 내부의 코드를 실행한다. 즉, useEffect는 해당 렌더링이 화면에 반영될 때까지 코드 실행을 "지연"시킨다.
Effect를 사용하여 외부 시스템과 동기화하는 방법을 살펴보자.
<VideoPlayer>컴포넌트가 있고 isPlaying props를 전달하여 재생 또는 일시 중지 여부를 제어한다.
<VideoPlayer isPlaying={isPlaying} />;
function VideoPlayer({ src, isPlaying }) {
// TODO: do something with isPlaying
return <video src={src} />;
}
VideoPlayer 컴포넌트는 내장 브라우저 <video>태그를 렌더링 한다.
그러나 <video>태그에는 isPlaying prop이 없다. 이를 제어하기 위해선 DOM요소에서 play(), pause() 메서드를 수동으로 호출해야 한다. 그렇다면 비디오 재생 여부를 알려주는 isPlaying prop의 값을 play(), pause()와 같은 명령 호출과 동기화해야 한다. 먼저 useRef를 이용해 <video> DOM 노드에 대한 참조가 필요하다. 렌더링 중에 위와 같은 명령 호출을 시도하고 싶을 순 있지만 이 옳지 않다.
App.js
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
if (isPlaying) {
ref.current.play(); // Calling these while rendering isn't allowed.
} else {
ref.current.pause(); // Also, this crashes.
}
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}
위 코드가 틀린 이유는 렌더링하는 동안 DOM 노드로 무언가를 하려고 하기 때문이다. React에서 렌더링은 JSX의 순수한 계산이어야 하며 DOM 수정과 같은 side effect는 포함하지 않아야 한다.
게다가 VideoPlayer가 처름 호출될 때 DOM은 아직 존재하지 않는다. JSX를 반환할 때까지 어떤 DOM을 생성할지 모르기에 해당 메소드를 호출할 수 없다.
해결책: useEffect를 이용해 해당 side effect를 렌더링 계산에서 빼내기 위해 래핑한다.
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
동기화한 외부 시스템은 브라우저 미디어 API이다. 컴포넌트가 렌더링될 때 먼저 React는 화면을 업데이트하여 <video> 태그가 올바른 props와 함꼐 DOM에 있는지 확인한다. 그러면 Effect를 실행할것이고 prop의 값에따라 play()나 pause()메소드를 호출한다.
2. Effect dependencies
useEffect(() => {
if (isPlaying) { // It's used here...
// ...
} else {
// ...
}
}, [isPlaying]); // ...so it must be declared here!
isPlaying의 상태가 변경함에 따라 해당 effect를 재실행한다.
useEffect(() => {
// 모든 렌더 후에 실행
});
useEffect(() => {
// 처음으로 화면에 나타날 때만 이 코드를 한번 실행
}, []);
useEffect(() => {
// 마지막 렌더링 이후 a 또는 b의 상태가 변경된 경우실행
}, [a, b]);
3. clean up
import { useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);
return <h1>Welcome to the chat!</h1>;
}

위 Effect는 마운트 시에만 실행되므로 콘솔에 한번 출력될 것을 예상하지만 두번 출력된다. 왜그럴까?
ChatRoom 컴포넌트가 마운트되고 connection.connect()가 호출된다. 그런 다음 사용자가 다른 화면으로 이동한다면 언마운트된다. 마지막으로 사용자는 뒤로를 클릭하고 ChatRoom을 다시 마운트한다. 이렇게 하면 두번째 연결이 설정되지만 첫 번째 연결은 절대 끊어지지 않고 사용자가 앱을 탐색할 때 연결이 계속 쌓인다.
이와 같은 버그는 광범위한 수동 테스트 없이는 놓치기 쉽다. 빠르게 발견할 수 있도록 React는 개발 단계에서 초기 마운트 직후에 모든 컴포넌트를 다시 마운트한다. 컴포넌트 마운트가 해제될 때 코드가 연결을 닫지않는 실제 문제를 발견할 수 있게 된 것이다.
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, []);
return <h1>Welcome to the chat!</h1>;
}

clean up을 구현하여 연결을 끊었다가 다시 연결하는 작업이 정확히 일어난다.
요약
이벤트와 달리 effect는 특정 상호 작용이 아닌 렌더링 자체에 의해 발생한다.
effect를 사용하면 컴포넌트를 일부 외부 시스템(타사 API, 네트워크 등)과 동기화할 수 있다.
기본적으로 effect는 모든 렌더링(초기 렌더링 포함) 후에 실행된다.
React는 모든 종속성이 마지막 렌더링 동안과 동일한 값을 갖는 경우 Effect를 건너뛴다.
Strict Mode가 켜져 있으면 React는 구성 요소를 두 번 마운트하여(개발 중인 경우에만!) 효과에 대한 스트레스 테스트를 수행한다.
리렌더로 인해 Effect가 중단되면 cleanup 기능을 구현해야 한다.
React는 다음 Effect가 실행되기 전과 마운트 해제 중에 cleanup 함수를 호출한다.
추가 내용 업로드 예정일: 22/11/17
'개발자의 공부 > React' 카테고리의 다른 글
[React]useContext (0) | 2022.11.15 |
---|---|
[React]Purity and Side Effect (0) | 2022.11.08 |
[React]useReducer (0) | 2022.10.24 |
[React]State(내용 업데이트) (0) | 2022.10.24 |
[React]Page Routing (0) | 2022.09.30 |
해당 내용은 2022년 11월 08일 React Docs BETA 버전을 토대로 작성되었습니다. - 링크
👉 사전 지식 - Purity and Side Effect
useEffect란 ?
useEffect란 컴포넌트를 외부 시스템과 동기화할 수 있는 React hook이다.
컴포넌트를 외부 시스템과 동기화할 수 있다는 게 무슨 의미일까?
때때로 컴포넌트가 페이지에 표시되는 동안 네트워크, 일부 브라우저 API 또는 외부 라이브러리에 연결된 상태를 유지해야할 수 있다. 이러한 시스템들은 React에 의해 제어되지 않으므로 외부 시스템이라고 한다.
이러한 외부 시스템과 컴포넌트를 연결할 수 있는 역할을 하는 것이 useEffect hook인 것이다.
즉, 컴포넌트에서 Side Effect를 사용할 수 있게하는 것이다.
useEffect의 Effect는 React 관련 정의이며 렌더링으로 인한 Side Effect를 나타낸다.
React에서 말하는 Effect는 무엇이며 이벤트와 어떻게 다른가?
Effects를 사용하기 전에, React 컴포넌트 내부의 두가지 유형의 로직에 익숙해져야 한다.
- 컴포넌트는 순수해야한다. 수학 공식과 마찬가지로 결과만 계산해야 하며 다른 작업은 수행하지 않아야 한다.
- 이벤트 처리기는 단순히 계산하는 것이 아닌 작업을 수행하는 컴포넌트의 내부 중첩 함수이다. 입력 필드를 업데이트하거나 HTTP POST 요청을 제출하여 제품을 구매하거나 사용자가 다른 화면으로 이동할 수 있다. 이벤트 헨들러는 side effects(프로그램 상태 변경)를 포함하며 특정 사용자 액션(버튼 클릭 또는 입력 등)에 의해 발생한다.
이러한 설명들이 충분하지 않을 때가 있다. 예를 들어, 화면에 보일 때마다 채팅 서버에 연결해야하는 컴포넌트를 생각해보자. 서버에 연결하는 것은 순수한 계산이 아닌 side effect이므로 렌더링 중에 발생할 수 없다. 그렇다고 클릭과 같은 특정한 이벤트가 발생하는 것도 아니다.
이를 Effects를 사용하여 특정 이벤트가 아닌 렌더링 자체로 인해 발생하는 side effect를 지정할 수 있는 것이다. 채팅에서 메세지를 보내는 것은 사용자가 특정 버튼을 클릭하여 직접 발생하는 것이기에 이벤트이지만, 서버 연결 설정은 컴포넌트가 나타나게 된 상호작용에 관계없이 수행되어야 하기에 Effects이다. Effects는 React 컴포넌트들이 외부 시스템과 동기화하기 좋은 때인 화면이 업데이트되고 렌더링 프로세스가 끝날때 실행된다.
Effects를 작성하는 방법
1. Effects를 선언한다. 기본적으로 렌더링할 때마다 실행된다.
2. Effect dependencies를 지정하여 필요할 때만 리렌더하도록 한다. 예를 들어, 채팅방 연결 및 해제는 컴포넌트가 나타났다가 사라지거나 대화방이 변경될 때만 발생해야 한다.
3. 필요한 경우 cleanup을 추가한다. 일부 Effects는 수행 중인 작업을 중지, 실행, 취소 또는 정리하는 방법을 지정해야 한다. 예를 들어, 연결에서는 연결 해제, 구독에는 구독 취소가 필요하기에 cleanup 함수를 반환하여 이러한 작업을 수행한다.
1. 선언
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Code here will run after *every* render
});
return <div />;
}
컴포넌트가 렌더링될 때마다 React는 화면을 업데이트하고, useEffect 내부의 코드를 실행한다. 즉, useEffect는 해당 렌더링이 화면에 반영될 때까지 코드 실행을 "지연"시킨다.
Effect를 사용하여 외부 시스템과 동기화하는 방법을 살펴보자.
<VideoPlayer>컴포넌트가 있고 isPlaying props를 전달하여 재생 또는 일시 중지 여부를 제어한다.
<VideoPlayer isPlaying={isPlaying} />;
function VideoPlayer({ src, isPlaying }) {
// TODO: do something with isPlaying
return <video src={src} />;
}
VideoPlayer 컴포넌트는 내장 브라우저 <video>태그를 렌더링 한다.
그러나 <video>태그에는 isPlaying prop이 없다. 이를 제어하기 위해선 DOM요소에서 play(), pause() 메서드를 수동으로 호출해야 한다. 그렇다면 비디오 재생 여부를 알려주는 isPlaying prop의 값을 play(), pause()와 같은 명령 호출과 동기화해야 한다. 먼저 useRef를 이용해 <video> DOM 노드에 대한 참조가 필요하다. 렌더링 중에 위와 같은 명령 호출을 시도하고 싶을 순 있지만 이 옳지 않다.
App.js
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
if (isPlaying) {
ref.current.play(); // Calling these while rendering isn't allowed.
} else {
ref.current.pause(); // Also, this crashes.
}
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}
위 코드가 틀린 이유는 렌더링하는 동안 DOM 노드로 무언가를 하려고 하기 때문이다. React에서 렌더링은 JSX의 순수한 계산이어야 하며 DOM 수정과 같은 side effect는 포함하지 않아야 한다.
게다가 VideoPlayer가 처름 호출될 때 DOM은 아직 존재하지 않는다. JSX를 반환할 때까지 어떤 DOM을 생성할지 모르기에 해당 메소드를 호출할 수 없다.
해결책: useEffect를 이용해 해당 side effect를 렌더링 계산에서 빼내기 위해 래핑한다.
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
동기화한 외부 시스템은 브라우저 미디어 API이다. 컴포넌트가 렌더링될 때 먼저 React는 화면을 업데이트하여 <video> 태그가 올바른 props와 함꼐 DOM에 있는지 확인한다. 그러면 Effect를 실행할것이고 prop의 값에따라 play()나 pause()메소드를 호출한다.
2. Effect dependencies
useEffect(() => {
if (isPlaying) { // It's used here...
// ...
} else {
// ...
}
}, [isPlaying]); // ...so it must be declared here!
isPlaying의 상태가 변경함에 따라 해당 effect를 재실행한다.
useEffect(() => {
// 모든 렌더 후에 실행
});
useEffect(() => {
// 처음으로 화면에 나타날 때만 이 코드를 한번 실행
}, []);
useEffect(() => {
// 마지막 렌더링 이후 a 또는 b의 상태가 변경된 경우실행
}, [a, b]);
3. clean up
import { useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);
return <h1>Welcome to the chat!</h1>;
}

위 Effect는 마운트 시에만 실행되므로 콘솔에 한번 출력될 것을 예상하지만 두번 출력된다. 왜그럴까?
ChatRoom 컴포넌트가 마운트되고 connection.connect()가 호출된다. 그런 다음 사용자가 다른 화면으로 이동한다면 언마운트된다. 마지막으로 사용자는 뒤로를 클릭하고 ChatRoom을 다시 마운트한다. 이렇게 하면 두번째 연결이 설정되지만 첫 번째 연결은 절대 끊어지지 않고 사용자가 앱을 탐색할 때 연결이 계속 쌓인다.
이와 같은 버그는 광범위한 수동 테스트 없이는 놓치기 쉽다. 빠르게 발견할 수 있도록 React는 개발 단계에서 초기 마운트 직후에 모든 컴포넌트를 다시 마운트한다. 컴포넌트 마운트가 해제될 때 코드가 연결을 닫지않는 실제 문제를 발견할 수 있게 된 것이다.
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, []);
return <h1>Welcome to the chat!</h1>;
}

clean up을 구현하여 연결을 끊었다가 다시 연결하는 작업이 정확히 일어난다.
요약
이벤트와 달리 effect는 특정 상호 작용이 아닌 렌더링 자체에 의해 발생한다.
effect를 사용하면 컴포넌트를 일부 외부 시스템(타사 API, 네트워크 등)과 동기화할 수 있다.
기본적으로 effect는 모든 렌더링(초기 렌더링 포함) 후에 실행된다.
React는 모든 종속성이 마지막 렌더링 동안과 동일한 값을 갖는 경우 Effect를 건너뛴다.
Strict Mode가 켜져 있으면 React는 구성 요소를 두 번 마운트하여(개발 중인 경우에만!) 효과에 대한 스트레스 테스트를 수행한다.
리렌더로 인해 Effect가 중단되면 cleanup 기능을 구현해야 한다.
React는 다음 Effect가 실행되기 전과 마운트 해제 중에 cleanup 함수를 호출한다.
추가 내용 업로드 예정일: 22/11/17
'개발자의 공부 > React' 카테고리의 다른 글
[React]useContext (0) | 2022.11.15 |
---|---|
[React]Purity and Side Effect (0) | 2022.11.08 |
[React]useReducer (0) | 2022.10.24 |
[React]State(내용 업데이트) (0) | 2022.10.24 |
[React]Page Routing (0) | 2022.09.30 |