Hooks은 React 16.8버전에 새로 추가된 것으로 class를 사용하지 않고도
state와 다른 React의 기능을 지원합니다.
( 원래 React이전 버전에서는 class컴포넌트만이 state와 lifecycle메서드를 사용할 수 있었습니다. )
useEffect
는 컴포넌트가 Mount(화면에 첫 렌더링)
되었을 때,Update(다시 렌더링)
되었을 때, Unmount(화면에서 사라질 때)
특정 기능을 수행할 수 있게 합니다.
특정기능을 수행하는 함수를 'effect'라 부르고 useEffect
의 첫번째 인자로 들어갑니다.
useEffect
의 형태는 크게 다음처럼 나눌 수 있으며, 두번째 인자는 optional합니다.
useEffect(() => { //수행할 특정 기능..});
=> rendering이 될때 마다 effect가 실행됨. ( Mount + Update )
useEffect(() => { //수행할 특정 기능..},[value]);
=> 두번째 인자가 [value]인 경우 :
최초로 rendering이 되었을 때 + value값이 변경되었을 때 effect가 실행됨.
(Mount + [value] Update )
=> 두번째 인자rk 빈배열([ ])인 경우 :
최초로 rendering 되었을 때 딱 한번만 effect실행됨.
Clean-up이 필요한 effect의 경우
effect가 렌더링 이후에 실행되었다가 컴포넌트가 unmount될 때 실행되던 effect를 정리해줘야하는 경우가 있다.
예를 들어 useEffect
에 타이머를 설정하는 effect 혹은
특정 이벤트 리스너를 등록하는 effect가 있었다면 해당 컴포넌트가 보이지 않게 되었을 땐
더이상 실행될 필요가 없으므로 effect를 해제하는 작업이 필요하다.
이런 경우 effect함수에서 clean-up하는 function을 리턴해주면 React가 알아서 해당 컴포넌트가 unMount될때 effect에서 리턴된 function을 실행해서 정리되도록 한다.
따라서, useEffect
의 첫번째 인자로 들어가는 effect는 크게 2가지로 나뉜다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 같은 방식으로
useEffect(() => {
// 브라우저 API를 이용하여 문서 타이틀을 업데이트합니다.
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Effect Hook을 사용하면 function 컴포넌트에서 side effect을 수행할 수 있습니다.
🥕 side effect 이란?
위 예제의 useEffect()안의 함수를 보면
document의 title을 count횟수가 포함된 문장으로 표현되도록 하고 있습니다.
이처럼 데이터 가져오기, 구독 설정하기, 수동으로 React 컴포넌트의 DOM 수정하기 등
이런 모든 기능(operations)을 side effect (또는 effect)라고 부릅니다.
Tip ❗
React의 class Lifecycle 메서드에 익숙하다면,
useEffect
Hook을componentDidMount
와componentDidUpdate
,componentWillUnmout
가
합쳐진 것으로 생각해도 괜찮습니다.
즉, 쉽게말해 컴포넌트가 마운트 되었을 때,
컴포넌트가 업데이트 되었을 때,컴포넌트가 언마운트될 때
실행하고자 하는 기능이 있다면 function컴포넌트에서는 useEffect
을 사용하면 됩니다.
Rect컴포넌트엔 일반적으로 2종류의 side effects가 있습니다.
이 둘을 어떻게 구분하면 좋을지 알아보겠습니다.
이러한 예들은 "clean-up이 필요없는 경우들" 입니다.
얘네들은 실행 이후 신경 쓸 것이 없기 때문입니다.
class컴포넌트와 hook이 이러한 clean-up이 필요없는 side effects를 어떻게 다르게 구현하는지 비교해봅시다.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
React의 class컴포넌트에서 render
메서드 자체는
side effect를 일으키진 않습니다.
아직은 이른 시기로 일반적으로는 effects를 실행시키고자 하는 때는
React가 DOM을 업데이트 한 이후입니다.
class컴포넌트에서 side effect를 componentDidMount
와 componentDidUpdate
에 두는 것이 바로 이때문입니다.
-> 즉, render실행되어 컴포넌트가 DOM에 최초로 등록되었을 때와 DOM이 수정되었을 때
실행시키고 싶은 side effect이 있으면
componentDidMount
와 componentDidUpdate
을 사용해서 구현한다는 말!
위 예시 코드에서
두 개의 생명주기 메서드 안에
같은 코드가 중복되는 것을 눈여겨 봅시다.
이는 컴포넌트가 이제 막 마운트 된 단계인지 아니면 업데이트 되는 것인지에 상관없이
같은 side effect을 수행해야 하기 때문입니다.
개념적으로 렌더링 이후에 항상 같은 코드가 실행되길 바라는 것이죠.
하지만, class컴포넌트는 이런 메서드가 구현되어있지 않아서
위처럼 중복되게 코드를 작성할 수밖에 없습니다.
이제 useEffect
로 같은 기능을 어떻게 구현하는지 살펴보겠습니다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect가 하는 일은 무엇일까?
useEffect를 컴포넌트 안에서 불러야 하는 이유는?
count
state변수 (또는 prop에도) 접근할 수 있게 되기 때문입니다.useEffect
의 두번째 인자로 어떤것을 넣느냐에 따라 조정도 가능합니다.위에서 정리(clean-up)가 필요하지 않은 side effect를 보았지만,
정리(clean-up)가 필요한 effect도 있습니다.
import React, { useState, useEffect } from 'react';
import Timer from './component/Timer';
function App() {
const [showTimer,setShoTimer] = useState(false);
const handleToggleClick = () => {
setShowTimer(!showTimer);
};
return(
<div>
{showTimer && <Timer/>}
<button onClick={handleToggleClick}>Toggle Timer</button>
</div>
);
}
export default App;
import React, { useEffect } from 'react';
const Timer = (props) => {
//Timer컴포넌트가 처음 렌더링 되었을 때(Mount)만
// 1초마다 콘솔출력하는 effect를 등록함
useEffect(() => {
const timer = setInterval(() => {
console.log('타이머 돌아가는 중...')'
},1000);
//Timer컴포넌트가 unmount되었을 때 정리되도록 function리턴
return () => {
clearInterval(timer);
console.log('타이머가 종료되었습니다.');
};
},[]);
return(
<div>
<span>타이머를 시작합니다. 콘솔을 확인하세요!</span>
</div>
);
}
export default Timer;
useEffect
Hook을 이용하면 class컴포넌트의 Lifecyle메서드에 따라서가 아니라 "코드가 무엇을 하는지에 따라" 나눌 수 있습니다.useState
처럼 useEffect
도 여러번 사용할 수 있습니다. 따라서, 서로 관련있는 것들끼리만 useEffect
에 담아둘 수 있습니다.)useEffect
의 두번째 인자로 무엇을 전달하는 가에 따라 effect를 실행시킬 수도 건너뛰게 할 수도 있습니다.useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
참고
https://ko.reactjs.org/docs/hooks-effect.html
https://reactjs.org/docs/hooks-effect.html
https://www.youtube.com/watch?v=kyodvzc5GHU