useEffect를 다 안다고 생각했지만 막상 다른 사람에게 useEffect를 설명하는데 한계를 느꼈다. 그래서 이번 기회에 useEffect에 대해서 남들에게 설명할 수 있게 정리를 하려고 한다.
리액트에서 useState와 같은 훅 중에 하나로 리액트 외부 시스템을 컴포넌트 내에서 동기화를 하거나 리액트 생명 주기에 맞는 기능들을 수행하기 위해서 사용한다.
함수형 컴포넌트는 클래스형 컴포넌트와 달리 componentDidMount, componentDidUpdate, componentWillUnmount 등 리액트 생명 주기에 대응하는 함수가 존재하지 않는다. 이를 useEffect 훅을 이용하여 컨트롤할 수 있도록 도와주는 역할을 한다.
또한, 특정 이벤트가 아닌 렌더링 자체로 인해 발생하는 부수 효과들(side effects)을 지정할 수 있는데 예를 들어서 채팅 메시지를 보내는 것은 사용자가 특정 버튼을 클릭하여 발생하는 이벤트에 해당하지만, 서버 연결 설정은 컴포넌트가 표시(add, mount)되는 원인이 되는 상호작용에 관계없이 발생해야 하므로 부수효과에 해당한다. 이렇듯 네트워크 또는 서드파티 라이브러와 동기화 하기에 좋다.
그리고 useEffect는 한 개가 아닌 2개 이상이 사용이 가능한데 같은 로직들을 하나로 묶어서 볼 수 있다라는 장점이 있다.
예를 들어 시점은 componentDidMount 시점에 실행을 해야하는데 하나는 API 호출, 하나는 setInterval같은 리액트에서 관리하지 않는 시스템이라면 2개의 useEffect를 이용하여 둘의 로직을 분리할 수 있다.
useEffect(setup, dependecies?)
function setup() {
//logic
return function cleanup() {
}
}
부수 효과 로직(API 호출, 네트워크 설정 등)이 들어가는 함수로 이 함수는 cleaup 함수를 반환하는 형태로 구성돼있다. 이때 cleanup 함수는 항상 반환할 필요는 없다.
컴포넌트의 각 생명 주기에 따라서 아래처럼 실행한다.
setup 함수 내에서 관련 돼 있는 props, state, 컴포넌트 내부에 선언된 모두 변수들과 함수들과 같은 반응하는 값(reactive value)의 리스트로 여기에 추가된 reactive value들이 변하게 되면 setup 함수를 다시 실행한다. 리액트는 각각의 value를 Object.is 비교를 통해서 같은지를 확인한다. 만약에 이 값이 없게 된다면 (리스트 자체가 존재하지 않는다면) 매 랜더링 마다 setup 함수를 실행하게 된다.
Object.is는 두 개의 값들이 동일한 값인지 아닌지를 확인하는 메서드로 "두 개의 값들이 같다"는 아래와 같은 내용들 중 하나를 만족한다는 얘기이다.
// 출처 react.dev 공식문서
import {useEffect,useState} 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])
}
혹은 setTimeout, setInterval과 같은 리액트로 제어되지 않는 코드를 넣어서 활용할 수 있다.
useEffect(() => {
let timer = setTimeout(() => {},1000);
return () => {
clearTimeout(timer);
}
},[]);
useEffect(() => {
const handleMove = () => {};
window.addEventListener('pointermove', handleMove);
return () => {
window.removeEventListener('pointermove',handleMove);
}
},[]);
function Component() {
const [count,setCount] = useState(0);
useEffect(() => {
document.title = `업데이트 횟수: ${count};
});
}
개인 과제 todo list를 만들던 중 desktop 처럼 꾸미고 싶어서 특정 아이콘을 눌르면 ApplicationWrapper 컴포넌트가 나올 수 있도록 구현을 했는데, trasition이 컴포넌트가 추가되면서 width,height가 제대로 적용이 되질 않아서 원하는대로 되지 않았다. 그래서 일단 DOM에 추가하고나서 조금의 딜레이를 줘서 원하는 transition이 발생할 수 있도록 구현을 하였다. 그 과정에서 처음에 did mount가 될 때, setTimeout을 통해서 일정 시간 딜레이를 주는 방식으로 구현하기 위해서 useEffect를 이용하였다.
const applicationWrapperRef = useRef(null);
useEffect(() => {
let timer = setTimeout(() => {
applicationWrapperRef.current.classList.add('active');
},10);
return () => {
clearTimeout(timer);
}
},[]);
다시 한번 정리를 하고 나니 useEffect에 대해서 조금 더 잘 이해할 수 있게 됐다. 이름 그대로 부수효과를 발생하는 훅인 만큼 신중하게 사용을 해야겠다라는 생각을 하였다.