useEffect 와 side effect 개념
side effect 란 우리말로 번역하면 부작용이라고 해석되지만 프로그래밍에서는 부정적인 의미가 아니다. 요구되어지는 이펙트 이외에 다른 이펙트가 발생하는 현상이라고 생각하면 된다.
Effect 훅은 side effect 를 수행하는역할이다. side effect 를 줄여서 그냥 effect 라고 하고 그래서 훅의 이름은 useEffect 가 된다.
useState 예제에서 클릭하여 카운트를 증가하였는데 side effect로 document title을 변경하고 싶다면 이 때 useEffect 를 사용할 수 있다.
1 2 3 4 |
// componentDidMount 와 componentDidUpdate 와 유사 useEffect(() => { document.title = `You clicked ${count} times`; }); |
이 코드는 당연히 펑션 컴포넌트의 top 레벨에서 사용이 되어야 한다. Dom을 사용해서 문서의 제목을 업데이트하며 클래스 라이프 사이클에서 사용되었던 componentDidMount 와 componentDidUpdate와 유사하다.
componentDidMount 는 문서가 로딩된 직후에 호출되고 componentDidUpdate 는 문서가 업데이트될 때 마다 호출되는 라이프 사이클 메서드다.
버튼 클릭 등의 어떤 특정 동작이 일어났을 때 실행하는것이 아니라, useEffect 는 문서가 로딩된 직후 혹은 문서가 렌더링 될 때 마다 effect 를 실행한다.
클래스 컴포넌트와 비교 한다면 아래와 동일하다.
1 2 3 4 5 6 7 |
componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } |
만일 componentWillUnmount 라이프 사이클을 구현할려면 펑션을 return 하는 구문을 작성해야 한다.
Q) 페이지가 최초 생성시, 그리고 카운트가 증가할 때마다 다큐먼트 title을 갱신하고 콘솔 로그를 찍도록 useEffect를 사용하시오.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function App() { const [count, setCount] = React.useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ReactDOM.render(<App />, document.getElementById('root')); |
A)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function App() { const [count, setCount] = React.useState(0); // Similar to componentDidMount and componentDidUpdate: React.useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; console.log(document.title); }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ReactDOM.render(<App />, document.getElementById('root')); |
CodePen
See the Pen react Hook useEffect example by LeeDongKee (@eastflag) on CodePen.default
Stopwatch 에 hooks 적용
class 컴포넌트를 function 컴포넌트로 바꾸고 useState 훅을 먼저 적용한다.
useEffect() 의 두번째 파라메터는 dependency로 두번째 파라메터 값을 watch 하고 있다가 이 값이 변할때만 side effect 를 실행한다.
만일 componentDidMount()와 같이 단 한번만 실행한다면 두번째 파라메터를 []으로 빈 배열로 선언하면 이 값이 변할리 없기 때문에 단 한번만 실행된다.
또한, componentWillUnmount()와 같이 종료되기 직전에 effect를 실행하고 싶다면 return에 이펙트를 실행할 펑션을 리턴해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import React, {useEffect, useState} from 'react'; const Stopwatch = (props) => { const [isRunning, setIsRunning] = useState(false); const [timer, setTimer] = useState(0); const tick = () => { console.log('tick: ', isRunning); // isRunning이 true이면 timer를 1씩 증가 if (isRunning) { setTimer(timer + 1) } } const handleStopwatch = () => { setIsRunning(!isRunning); } const handleReset = () => { setTimer(0); } useEffect(() => { let tickRef = setInterval(tick, 1000); return () => { clearInterval(tickRef); } }, []); return ( <div className="stopwatch"> <h2>Stopwatch</h2> <span className="stopwatch-time">{timer}</span> <button onClick={handleStopwatch}>{isRunning ? 'Stop' : 'Start'}</button> <button onClick={handleReset}>Reset</button> </div> ) } export default Stopwatch; |
stop, start 버튼은 제대로 실행되었지만 카운트가 제대로 동작하지 않는다. 콘솔에 찍히는 것을 확인하면 isRunning이 계속 false 로 찍힌다.
setInterval에 설정된 tick 함수는 초기값을 가져가서 독립적으로 실행되면서 연결이 되지 않기 때문이다.