useEffect는 외부 시스템과 컴포넌트를 동기화하는 React Hook
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
import { useEffect } from 'react';
// 최상단에 이펙트훅을 임포트해준다.
function MyComponent() {
useEffect(() => {
// 이곳의 코드는 *모든* 렌더링 후에 실행됨
});
return <div />;
}
React에게 Effect를 불필요하게 재실행하지 않도록 하려면 useEffect 두 번째 인자로 의존성(dependencies) 배열을 지정한다.
의존성 배열에는 여러 개의 종속성을 포함할 수 있다.
의존성을 선택할 수 없다. 의존성 배열에 지정한 종속성이 Effect 내부의 코드를 기반으로 React가 기대하는 것과 일치하지 않으면 린트 에러가 발생하게 됨 (이건 처음 알게 되서 신기했다.)
useEffect(() => {
// 모든 렌더링 후에 실행됩니다
});
useEffect(() => {
// 마운트될 때만 실행됩니다 (컴포넌트가 나타날 때)
}, []);
useEffect(() => {
// 마운트될 때 실행되며, *또한* 렌더링 이후에 a 또는 b 중 하나라도 변경된 경우에도 실행됩니다
}, [a, b]);
cleanup 함수는 컴포넌트가 언마운트(unmount)될 때 호출된다. 이 과정은 컴포넌트가 사라질 때 컴포넌트가 생성했던 side effect를 정리하는 데 매우 중요한데, 예를 들어 컴포넌트가 WebSocket 연결을 설정하거나 setInterval을 사용하여 반복 작업을 설정한 경우, 컴포넌트가 언마운트되면 이들 작업을 정리하지 않으면 메모리 누수가 발생할 수 있다.
cleanup 함수는 새로운 side effect를 실행하기 전에도 호출된다. 이는 useEffect의 의존성 배열(dependency array)에 있는 값이 변경될 때마다 발생합니다. 이로 인해 이전에 설정된 side effect를 청소하고, 새로운 side effect를 실행하여 최신의 상태를 반영할 수 있습니다.
컴포넌트가 언마운트되었을 때 이벤트 리스너를 제거하지 않으면 이벤트 리스너가 콜백 함수를 계속 호출하려고 시도하면서 문제가 발생할 수 있기 때문에 제거해주는 것이 좋다.
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>채팅에 오신걸 환영합니다!</h1>;
}
React에 제어되지 않는 네트워크나 브라우저 API, 또는 서드파티 라이브러리와의 연결 등을 외부 시스템(external)으로 부르고 이를 유지하기 위해 이펙트 훅을 사용하게 된다.
공식 문서에서 설명하고 있는 예시로는 ① 채팅 서버와 연결, ②전역 브라우저 이벤트 감시, ③ 애니메이션 동작 ④ 모달 제어하기 ⑤ 요소의 가시성 (브라우저 DOM 조작 시 뷰포트 내에서 보이는 부분 확인하기) 이 있다.
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);
Effect는 서버에서는 실행되지 않는다. 이는 초기 서버 렌더링 된 HTML이 데이터가 없는 state를 먼저 포함한 후 렌더링한 다음 데이터를 받아오기 위해 또 로드하기 때문에 효율적이지는 않다.
Effect 내부에서 직접 페칭을 하는 것은 네트워크 폭포(network waterfalls)가 생성되기 쉽다. 부모 컴포넌트 렌더링 후 일부 데이터를 페칭하고 나서 자식 컴포넌트가 렌더링 되고 데이터를 페칭하기 시작하기 때문에 모든 데이터를 병렬로 가져오는 것보다 훨씬 느린 방법이다.
Effect 내부에서 직접 데이터를 페칭하는 것은 일반적으로 데이터를 미리 로드하거나 캐싱하지 않기 때문에 컴포넌트가 다시 마운트되었을 때 데이터를 다시 가져와야 한다. 사용하기 불편하다.
++ 4. 이건 내 개인적인 의견이지만 유즈이펙트 안에서 페칭 관련 코드를 작성할 경우 코드양이 늘어난다.(게다가 매번 데이터를 불러올 때마다 같은 코드를 작성해야 함) 효율성 측면에서 이 부분은 로직을 따로 빼서 임포트하는 식으로 작성 하는 것이 훨씬 효율적이라고 생각한다.
==> 보통 팀 프로젝트에서 유즈이펙트에 직접 데이터를 페칭해서 사용하는 식으로 로직을 많이 짰었는데 직접 데이터 페칭을 권장하지 않는단 걸 보니 꽤나 충격..! 이었다. 곧 리액트 토이플젝을 하나 만들 것 같은데 거기서는 내장된 데이터 페칭 기능을 활용하거나 따로 훅을 만들어서 적용해봐야겠다.
기본적으로 Effect는 모든 렌더링 후에 실행된다.
React에서는 렌더링이 DOM 수정과 같은 부수 효과를 포함해서는 안된다.
Effect는 client 환경에서만 동작한다. 서버 렌더링에서는 동작하지 않는다.
Effect가 무한 반복되려면 다음 두 가지 조건이 충족되어야 한다.
이 경우에는 useEffect훅의 쓰임새를 확인해야 한다. 외부 시스템이 없다면 Effect를 제거해서 로직을 단순화할 수 있는지 고민해볼 수 있다.
useLayoutEffect로 대체할 수 있다. 대부분은 Effect에는 필요하지 않은 방법이지만 브라우저 페인팅 이전에 Effect를 실행하는 것이 중요한 경우(사용자가 보기 전에 툴팁의 위치를 측정하고 지정해야 하는 경우)에는 사용할 수 있다. 리스트가 변경될 때 state 변수를 업데이트하는 Effect를 작성하는 대신, 컴포넌트의 최상위 레벨에서 모든 데이터를 변환하고 props나 state가 변경될 때마다 해당 코드가 자동으로 다시 실행되도록 만들 수 있다.
구매 버튼 클릭 이벤트 핸들러에서는 정확히 어떤 일이 일어났는지 알 수 있지만 useEffect가 실행될 때까지 사용자가 무엇을 했는지 (예: 어떤 버튼을 클릭 했는지) 알 수 없다. 그렇기 때문에 일반적으로 해당되는 이벤트 핸들러에서 사용자 이벤트를 핸들링하는 것이 더 적절하다.