여기까지 오기도 힘들었읍니다.. ㅜ 쭈륵..
useEffect 이 쫘식을 공부해 봅쉬다!
이 포스팅을 보기전에 이해가 잘되지 않는다면, 아래의 포스팅을 봐주시면 감사하겠읍니다.
● 클래스형 컴포넌트와 함수형 컴포넌트의 차이점?
● 클래스형 컴포넌트와 함수형 컴포넌트의 차이점 함수형 컴포넌트는 렌더링된 값들을 고정시킨다. 이 둘의 가장 큰 차이점은 props, state의 값들을 고정시키느냐 안시키느냐에 있습니다. App.js i
ddaeunbb.tistory.com
● useEffect 이 쉑기 언제쓰나요?
useEffect는 언제 쓰는 것이 좋을까요?
훅은 함수형 컴포넌트에서 쓰이는 것이기 때문에, 클래스의 라이프사이클 메서드를 사용할 수 없습니다. 클래스의 componentDidMount, componenetDidUpdate, componentWillUnmount 메서드를 사용할 수 없습니다. 따라서 useEffect는 이들을 흉내내는 흉내쟁이 라고 생각하면 되겠습니다.
먼저 useEffect 의 사용법을 알아보겠습니다.
useEffect Hook의 형태는 아래와 같습니다. (공식문서에서 따온 사용법)
useEffect(setup, dependencies?)
- useEffect로 componentDidMount 따라해보기 ㅋ
import { useState, useEffect } from 'react';
const App = ()=>{
useEffect(()=>{
console.log('한번만 출력되지롱ㅋ')
}, [])
return(
<div>
<h1>예시 페이지입니다.</h1>
</div>
)
}
export default App;
의존성배열을 빈 배열로 넣어주면 컴포넌트가 마운트 되었을 때, 한번만 호출됩니다.
- useEffect로 componentDidUpdate 따라해보기 ㅋ
import { useState, useEffect } from "react";
const App = () => {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log("넘버가 변했구려");
}, [number]);
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {setNumber(number + 1);}}>
증가시키기
</button>
</div>
);
};
export default App;
componentDidUpdate는 이전 props와 state를 매개변수로 하기 때문에 이전 값들을 보여줄 수 있었는데요, useEffect로도 실현가능함ㅋ
return (clean up) 함수를 반환해주면 된다. 그럼 이전 값을 보여줌
useEffect(() => {
console.log("넘버가 변했구려");
return ()=>{
console.log(`이전넘버ㅋ : ${number}`)
}
}, [number]);
보면 클린업 함수가 먼저 콘솔로 보이고 그다음 useEffect가 실행되는 것을 볼 수 있습니다.
- useEffect로 componentWillUnmount 따라해보기 ㅋ
먼저 앞으로 마운트되고 언마운트될 컴포넌트 하나를 만들었습니다. App.js에서 버튼을 누르면 Unmount 컴포넌트가 보여지게 되고 버튼을 한번더 누르면 Unmount 함수가 언마운트 됩니다.
App.js
import { useState} from "react";
import Unmount from './Unmount';
const App = () => {
const [show, setShow] = useState(false);
return (
<div>
<button
onClick={() => {setShow(!show)}}>
증가시키기
</button>
{ show && <Unmount/>}
</div>
);
};
export default App;
Unmount.js
import { useEffect } from 'react';
const Unmount = ()=>{
useEffect(() => {
console.log("마운트 됏음다 ");
return ()=>{
console.log("언마운트 될거삼")
}
}, []);
return(
<div>
저는 언마운트 컴포넌트 임니다.
</div>
)
}
export default Unmount;
이때 의존성 배열을 빈배열로 주어야합니다.
위와 같이 마운트 되면 useEffect 함수가 실행되고, 언마운트 될 때는 clean up 함수가 실행되는 것을 볼 수 있습니다.
여기까지가 기본적인 useEffect의 사용법이었는데요. 그렇다면 더 깊게 useEffect에 함 다이브 해봅시댜.
● 함수형 컴포넌트는 렌더링마다 고유한 이펙트를 가진다.
import { useState, useEffect} from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
};
export default App;
예시를 보면 버튼을 누를 때마다 state가 증가되고, useEffect가 실행됩니다. useEffect는 어떻게 최신 상태의 count를 읽어들일까요?
함수형 컴포넌트는 렌더링마다 고유의 state, props를 가지는 것처럼 이펙트 함수도 렌더링마다 별도로 존재하게 됩니다.
각 이펙트 함수는 자신이 속한 렌더링의 값을 '보는' 것입니다.
function Counter() {
// ...
useEffect(
// 첫 번째 랜더링의 이펙트 함수
() => {
console.log(`You clicked ${0} times`);
}
);
// ...
}
// 클릭하면 함수가 다시 호출된다
function Counter() {
// ...
useEffect(
// 두 번째 랜더링의 이펙트 함수
() => {
console.log(`You clicked ${1} times`);
}
);
// ...
}
// 또 한번 클릭하면, 다시 함수가 호출된다
function Counter() {
// ...
useEffect(
// 세 번째 랜더링의 이펙트 함수
() => {
console.log(`You clicked ${2} times`);
}
);
// ..
}
● useEffect는 렌더링 이후에 실행된다.
리액트는 제공한 이펙트 함수를 기억해 놨다가 DOM의 변화를 처리하고 브라우저가 스크린에 그리고 난 뒤 실행합니다.
첫번째 렌더링을 표현하자면,
- 리액트: state가 0 일 때의 UI를 보여줘.
- 컴포넌트
- 여기 랜더링 결과물로 <p>You clicked 0 times</p> 가 있어.
- 그리고 모든 처리가 끝나고 이 이펙트를 실행하는 것을 잊지 마: () => {console.log('You clicked 0 times' )}.
- 리액트: 좋아. UI를 업데이트 하겠어. 이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해.
- 브라우저: 좋아, 화면에 그려줄게.
- 리액트: 좋아.이제 컴포넌트 네가 준 이펙트를 실행할거야.
- () => { console.log('You clicked 0 times') } 를 실행하는 중.
그럼 버튼을 클릭하면 어떤 일이 벌어지는지 복습해 보시죠.
- 컴포넌트: 이봐 리액트, 내 상태를 1 로 변경해줘.
- 리액트: 상태가 1 일때의 UI를 줘.
- 컴포넌트
- 여기 랜더링 결과물로 <p>You clicked 1 times</p> 가 있어.
- 그리고 모든 처리가 끝나고 이 이펙트를 실행하는 것을 잊지 마: () => { console.log('You clicked 1 times' )}.
- 리액트: 좋아. UI를 업데이트 하겠어. 이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해.
- 브라우저: 좋아, 화면에 그려줄게.
- 리액트: 좋아 이제 컴포넌트 네가 준 이펙트를 실행할거야.
- () => { console.log('You clicked 1 times') } 를 실행하는 중.
● 함수형 컴포넌트에서 이전값, 최신값에 접근하려면?
- useRef 사용하기
const App = () => {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
latestCount.current = count;
setTimeout(()=>{
console.log(`You clicked ${latestCount.current} times`);
},3000)
});
...
};
export default App;
- cleanup함수 사용하기
import { useState, useEffect} from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log({count})
return ()=>{
console.log({count})
}
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
};
export default App;
보면 이전 값들도 보여지는걸 알 슈잇습니다.
근데 cleanup함수는 어떻게 이전 state, props를 볼 수 있는 것일까? 새로운 컴포넌트가 렌더링 되기 이전에 실행되기 때문 일까?
리액트는 브라우저가 리페인트 되고 나서 이펙트를 실행합니다.
이렇게 하여 대부분의 이펙트가 스크린 업데이트를 가로막지 않기 때문에 앱을 빠르게 만들어줍니다. 마찬가지로 이펙트의 클린업도 미뤄집니다. 이전 이펙트는 새 prop과 함께 리랜더링 되고 난 뒤에 클린업됩니다.
만약 버튼을 처음으로 눌렀다면,
- 리액트가 {1} 을 가지고 UI를 랜더링한다.
- 브라우저가 실제 그리기를 한다. 화면 상에서 {1} 이 반영된 UI를 볼 수 있다.
- 리액트는 {0} 에 대한 이펙트를 클린업한다.
- 리액트가 {1} 에 대한 이펙트를 실행한다.
만약 두번째로 버튼을 눌렀다면?
- 리액트가 {2} 을 가지고 UI를 랜더링한다.
- 브라우저가 실제 그리기를 한다. 화면 상에서 {2} 이 반영된 UI를 볼 수 있다.
- 리액트는 {1} 에 대한 이펙트를 클린업한다.
- 리액트가 {2} 에 대한 이펙트를 실행한다.
그런데, 어떻게 클린업함수는 이전 값들을 '볼 수' 있는 걸까요?
그 이유는 클린업 함수는 자신이 정의 되었을 때를 기억하기 때문입니다.
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log({count})
return ()=>{
console.log({count}) //클린업 함수는 카운트가 0 일때 정의 되었다.
}
...
});
const App = () => {
const [count, setCount] = useState(1);
useEffect(() => {
console.log({count})
return ()=>{
console.log({count}) //클린업 함수는 카운트가 1 일때 정의 되었다.
}
});
우리는 클린업 함수가 컴포넌트가 unmount될 때 실행된다고 배웠습니다. 따라서 언마운트 될 때 실행되고 렌더링이 되는 것이 아니라 렌더링이 되고나서 클린업 함수실행 > effect함수가 실행 되는 것을 알 수 있습니다.
참고자료
useEffect 완벽 가이드
이펙트는 데이터 흐름의 한 부분입니다.
overreacted.io