리액트 앱을 개발하면서 특정 컴포넌트의 렌더링 직전에 특정 작업을 해야 한다던지, 업데이트 된 후에 처리해야 한다던지 하는 일이 있을 것이다.
이런 상황에서 유용하게 쓰일 수 있는 useEffect
훅의 개념과 사용법과 의존성 배열에 대해서 알아본다.
어떤 컴포넌트가 화면에 첫 렌더링이 되는 것을 마운트(mount), 다시 렌더링 되는 것을 업데이트(Update), 화면에서 사라지는 것을 언마운트(Unmount)라고 한다. 각각의 상황에서 특정 작업을 실행시키고 싶다면 useEffect
를 사용할 수 있다.
useEffect
는 두 가지 형태만 알고 있으면 된다.
useEffect(() => { /* 원하는 작업 */ });
useEffect(() => { /* 원하는 작업 */ }, [value]);
첫 번째 형태는 useEffect
의 인자로 하나의 콜백함수가 들어가는 형태, 두 번째는 콜백함수와 배열을 받는 형태이다. 이 배열은 다른 이름으로 dependency array
, 의존성 배열이라고도 한다.
첫 번째 형태는 컴포넌트가 렌더링 될 때마다 매번 콜백함수가 실행된다. 즉 컴포넌트가 맨 처음에 마운트 될 때, 업데이트 될 때마다 실행된다.
두 번째 형태는 매번 렌더링이 될 때마다 실행하는 것이 아니라, 컴포넌트가 맨 처음 화면에 렌더링이 될 때, 그리고 의존성 배열 요소값이 바뀔 때만 실행이 된다. 의존성 배열은 콜백함수가 실행되는 일종의 조건을 거는 기능을 한다고 볼 수도 있다.
useEffect(() => {}, []);
위와 같이 의존성 배열에 빈 배열을 전달한다면 배열의 요소가 변할 일이 없으므로 맨 처음 컴포넌트가 렌더링될때만 콜백함수가 실행된다.
우리가 useEffect
를 사용해서 누군가를 팔로우하는 기능을 넣었다면, 이후에 언팔로우를 할 수도 있다. 또는 어떤 이벤트리스너를 등록했다가 더이상 필요가 없어지면 이 이벤트리스너를 정리해주는 작업을 해야 한다. 이런 정리 작업을 하려면 useEffect
의 return 값으로 함수를 넣어주면 된다.
useEffect(()=>{
// 팔로우
return () => {
// 언팔로우
}
}, [])
이 콜백 함수 안에서 우리가 원하는 정리작업을 처리해주면 된다. 이렇게 함수를 리턴해주면 해당 컴포넌트가 언마운트 될 때, 혹은 다음 렌더링 시 불릴 useEffect
가 실행되기 이전에 이 함수가 실행된다.
헷갈리니까 예제코드를 보면서 사용법을 좀 더 익혀보자.
import { useState } from 'react';
function App() {
const [num, setNum] = useState(1);
const handleNumUpdate = () => {
setNum(num+1);
}
return (
<div>
<button onClick = { handleNumUpdate }> update </button>
<span> num: { num }</span>
</div>
);
}
export default App;
업데이트 버튼을 누르면 num
라는 state를 1씩 증가시키는 페이지를 만들었다. 그러면 이제 useEffect
를 사용해보자.
import { useState, useEffect } from 'react'; // import 하기
function App() {
const [num, setNum] = useState(1);
const handleNumUpdate = () => {
setNum(num+1);
}
// 렌더링 될 때마다 실행됨
useEffect(()=>{
console.log("useEffect✨");
}![](https://velog.velcdn.com/images/hyun/post/9c79f3fc-784f-42d0-9c19-521fb0c00355/image.gif)
)
return (
<div>
<button onClick = { handleNumUpdate }> update </button>
<span> num: { num }</span>
</div>
);
}
export default App;
가장 기본적인 useEffect
의 형태를 하나 추가해 주었다. useEffect
는 컴포넌트가 렌더링될 때마다 실행된다고 했으므로 확인하기 위해 콜백 함수 내에 console.log
를 넣어 주었다.
우리가 업데이트 버튼을 누를 때마다 setNum
이 호출되고, num
이라는 state 값이 변하므로 컴포넌트가 리렌더링 될 것이다. 함수형 컴포넌트는 state값이 변할 때마다 다시 렌더링 되기 때문이다. 즉 렌더링이 될 때마다 useEffect
가 정상적으로 불려지는 것을 확인했다.
이번에는 state를 하나 더 추가해보자.
import logo from './logo.svg';
import './App.css';
import { useState, useEffect } from 'react';
function App() {
const [num, setNum] = useState(1);
const [text, setText] = useState('');
const handleNumUpdate = () => {
setNum(num+1);
}
const handleInputChange = (e) => {
setText(e.target.value);
}
// 렌더링 될 때마다 실행됨
useEffect(()=>{
console.log("useEffect✨");
})
return (
<div>
<button onClick = { handleNumUpdate }> update </button>
<span> num: { num }</span>
<br/>t
<input type="text" value={ text } onChange={ handleInputChange }/>
<span> { text } </span>
</div>
);
}
export default App;
text
state를 추가해주고, input 창에 입력되는 값을 span에 바로 보여지도록 만들었다. 입력창에 무언가를 입력할 때마다 handleInputChange
가 호출되고, 그 안에서 setText
가 호출되어 text
state가 변경될 것이다. 그러면 글자를 입력할 때마다 컴포넌트가 렌더링되고 useEffect
가 불릴 것이다.
컴포넌트가 매번 렌더링 될 때마다 useEffect
를 사용하니까 너무 많이 불리게 되어서, 이 안에서 무거운 작업을 하게 된다면 굉장히 부하가 많이 걸릴 것이다. 이번에는 num
이 업데이트 될 때만 useEffect
가 실행되도록 의존성 배열을 이용해서 조건을 걸어 볼 것이다.
import { useState, useEffect } from 'react';
function App() {
const [num, setNum] = useState(1);
const [text, setText] = useState('');
const handleNumUpdate = () => {
setNum(num+1);
}
const handleInputChange = (e) => {
setText(e.target.value);
}
// num이 업데이트 될 때마다 실행됨
useEffect(()=>{
console.log("num이 업데이트 될 때만 실행");
}, [num])
return (
<div>
<button onClick = { handleNumUpdate }> update </button>
<span> num: { num }</span>
<br/>
<input type="text" value={ text } onChange={ handleInputChange }/>
<span> { text } </span>
</div>
);
}
export default App;
useEffect
의 두 번째 인자인 의존성 배열에 num
을 넣어 주었다.
우리가 원하는 대로, text
를 변경할 때에는 컴포넌트가 렌더링 되어도 useEffect
가 호출되지 않는다. 컴포넌트가 처음 마운트 될 때, 그리고num
이 변경될 때만 useEffect
가 실행된다.
이번에는 useEffect
의 의존성 배열에 빈 배열을 넣어보자. 위 코드의 useEffect
부분을 아래와 같이 바꿔보았다.
// 처음 마운트 될 때만 실행됨
useEffect(()=>{
console.log("마운트 될 때만 실행");
}, [])
처음 컴포넌트가 마운트 될 때만 콘솔이 찍히고 아무리 num
, text
state를 변경해도 반응이 없다.
// component/Timer.js
import React, { useEffect } from "react";
const Timer = (props) => {
useEffect(()=>{
const timer = setInterval(()=>{
console.log("타이머 작동중...");
}, 1000);
}, [])
return(
<div>
<span> 타이머입니다⏱ </span>
</div>
)
}
export default Timer;
이번에는 Timer
라는 컴포넌트를 만들어 보았다. 이 컴포넌트는 마운트 되면 1초마다 "타이머 작동중..."이라는 콘솔을 찍어주는 기능을 한다.
// App.js
import Timer from './component/Timer';
import { useState, useEffect } from 'react';
function App() {
const [showTimer, setShowTimer] = useState(false);
return (
<>
{showTimer && <Timer />}
<button onClick = { () => { setShowTimer(!showTimer) }}> { showTimer ? "타이머 끄기" : "타이머 켜기"}</button>
</>
);
}
export default App;
App
컴포넌트에서는 Timer
컴포넌트를 마운트하고, 버튼을 만들어 타이머를 켜고 끌 수 있게 해주었다.
잘 작동이 된다. 그런데, 타이머 끄기 버튼을 눌러서 Timer
컴포넌트가 언마운트 되었는데도, 타이머가 꺼지지 않고 계속 작동하고 있다. 타이머를 끄기 위해서 useEffect
를 clean up 해보자.
// component/Timer.js
import React, { useEffect } from "react";
const Timer = (props) => {
useEffect(()=>{
const timer = setInterval(()=>{
console.log("타이머 작동중...");
}, 1000);
return () => {
// clean up
clearInterval(timer);
console.log("Clean up ");
}
}, [])
return(
<div>
<span> 타이머입니다⏱ </span>
</div>
)
}
export default Timer;
useEffect
를 clean up 하려면, useEffect
의 첫 번째 인자인 콜백함수의 리턴에서 코드를 작성하면 된다. 우리가 해야 할 작업은 타이머를 끝내는 것이므로 return하는 함수 안에서 타이머를 종료해 주었다.
타이머가 잘 꺼지는 것을 확인할 수 있다.