이 글은 평소와 같이 유튜브를 보다가 굉장히 도움 될만한 정보라고 생각해서 소개할겸 저 스스로도 리마인드 할겸 블로그에 저장해두려고 합니다. 이 글의 주제는 제목과 같이 useEffect
를 사용할 때에 쉬이 간과할 수 있는 부분을 소개합니다.
혹시라도 저의 글이 아닌 원본을 보시고 싶다면 여기로 가셔서 보시면 될 것 같습니다. 시작하겠습니다.
function App() {
const [number, setNumber] = useState(0)
useEffect(() => {
console.log("rerendered : ", number)
})
console.count("render")
return (
<div>
<button onClick={() => setNumber(prev => prev+1)}>Increase</button>
</div>
)
}
다음과 같이 코드를 작성하면 Increase
버튼을 누를때 마다 컴포넌트가 리렌더링 되고 console.count
와 number
가 같은 숫자가 될 것입니다.
useEffect
는 인자로 콜백함수와 dependency
라는 것을 함께 받습니다. dependency
가 가질 수 있는 값은 대표적으로 3가지로 나눌 수 있습니다.
useEffect
의 콜백함수가 실행됩니다.mount
그리고 unmount
될 때만 실행됩니다.mount
컴포넌트가 처음 실행될 때
unmount
컴포넌트가 화면에서 사라질때
그럼 다음의 예를 한번 보겠습니다.
function App() {
const [number, setNumber] = useState(0)
const [value, setValue] = useState('')
useEffect(() => {
console.log('rerendered : ', value)
}, [value])
console.count('render')
return (
<div>
<input type='text' onChange={(e) => setValue(e.target.value)} />
<button type='button' onClick={() => setNumber((prev) => prev + 1)}>
Increase
</button>
</div>
)
}
다음과 같이 useEffect
의 dependency
를 설정해준다면 input
창에 입력값이 바뀐다면 useEffect
의 콜백이 실행이 되지만 button
을 통해 number
의 값을 변경한다면 useEffect
의 콜백이 실행되지 않습니다.
그럼 이제 간단하게 useEffect
의 기본에 대해 알아 보았으니 좀 더 심화로 들어가 보겠습니다.
function App() {
const [name, setName] = useState('')
const [state, setState] = useState({
name: '',
selected: false,
})
useEffect(() => {
console.log('The state has changed, useEffect runs!')
}, [state])
const handleAdd = () => {
setState((prev) => ({ ...prev, name }))
}
const handleSelect = () => {
setState((prev) => ({ ...prev, selected: true }))
}
return (
<div>
<input type='text' onChange={(e) => setName(e.target.value)} />
<button type='button' onClick={handleAdd}>
Add Name
</button>
<button type='button' onClick={handleSelect}>
Select
</button>
{`{name:${state.name} , selected:${state.selected}}`}
</div>
)
다음과 같은 코드를 한번 봅시다.
일단 input
의 입력값에 따라 name
변수가 변하고요
Add
버튼을 누르면 state.name
의 값이 name
과 같아집니다.
Select
버튼을 누르면 state.select
를 true
로 변경합니다.
그리고 useEffect
는 state
를 dependency
로 가집니다.
그럼 상황을 가정해봅시다.
input
에 입력값을 입력하면 useEffect
가 동작할까요? 동작하지 않겠죠? 왜냐면 name
변수에 의존하지 않기 때문입니다.
그럼 Add
버튼을 클릭한다면? useEffect
의 콜백함수가 실행될 것입니다.
그럼 Select
번트을 클릭한다면? useEffect
의 콜백함수가 실행되겠죠. 그런데 좀 이상합니다. Select
의 버튼을 한번 클릭할때는 state.select
가 바뀌었으니 useEffect
의 콜백함수가 실행되는 것은 알겠지만 여러번 클릭하면 state.select
가 변하지 않음에도 불구하고 useEffect
의 콜백함수가 계속해서 실행됩니다.
우선 리액트의 비교 매커니즘에 대해 알아 보겠습니다. 리액트는 변경에 민감합니다. 변경이 되지 않은 컴포넌트와 변경이 된 컴포넌트를 잘 구분하기 때문에 리액트 또한 좋은 성능을 가진다고 할 수 있죠. 그래서 리액트는 변경을 감지하기 위해 객체 비교 또한 아주 빠르게 비교할 수 있는 얕은 비교를 사용합니다.
얕은 비교란?
얕은 비교란 객체의 프로퍼티가 아닌 객체가 가진 주소만으로 객체를 비교하는 것을 의미하는 것입니다.
그럼 얕은 비교를 하는 것이 왜 이런 결과를 낳았을까요? useState
의 훅을 위해 객체를 변경하게 되면 리액트 내에서는 불변성을 유지하면서 기존 객체의 프로퍼티를 교체하는것이 아닌 새 객체를 만들어서 저장하게 됩니다. useEffect
는 얕은 비교를 통해 실질적인 객체의 프로퍼티는 변경되지 않았지만 속만 같은 다른 객체를 감지하게 되는 것입니다. 그럼 useEffect
는 아! 객체가 변경된것이구나 하고 콜백함수를 실행하게 하는 것입니다.
그럼 이를 막기 위해서는 여러가지 방법이 있습니다.
// 1
const user = useMemo(() => {
return {...state}
}, [state.name, state.select])
useEffect(()=>{
}, [user])
이 방법은 React가 제공하는 useMemo
를 이용한 방법으로 state.name
과 state.select
가 변할때만 user가 새로운 객체로 바뀝니다. 그럼 useEffect
또한 user가 바뀌지 않으면 콜백함수를 실행하지 않겠죠?
// 2
useEffect(() => {
},[state.name, state.select])
물론 useEffect
에 직접 dependency
를 설정해주어도 괜찮습니다. 하지만 이 방법은 컴포넌트가 복잡해졌을때 dependency
를 너무 어지럽힐 수 있으니 그럴때는 1번 방법을 이용하면 됩니다.
오늘은 여기까지이며 내일에는 useEffect
의 cleanup Function
에 대해 알아보겟습니다.