기존의 state 값이 "Origin State value" 였다고 가정했을 때 버튼과 같은 사용자의 입력(action, Event)으로 setState가 동작되어 state 값을 "Origin State value"로 업데이트 했다고 가정해보자.
분명히 state의 값은 바뀌었다 (setState 트리거, 동작) state 값이 바뀌면 해당 컴포넌트는 다시 렌더링된다. (=> 컴포넌트 리렌더링의 조건) 하지만 state의 값이 똑같은 값으로 바뀌었을 때도 해당 컴포넌트는 리렌더링 될까? 실습 코드를 짜보고 하나하나 console을 찍어가며 직접 눈으로 확인했다.
이번 웹 스터디할 때 "리액트 컴포넌트의 생명주기"라는 주제의 실습 코드로 채원이가 만들어 온 코드이다. 이 코드를 참고해서 useEffect 동작을 살펴본다.
// LifeCycle2.js
import React, { useEffect, useState, useRef } from 'react';
function Description2(props) {
// Constuctor 대신
// alert('자시기기기기기긱');
console.log('Func - 컴포넌트 내부');
const h1Ref = useRef();
const [content, setContent] = useState('not Props');
const [BGcolor, setBGColor] = useState('skyblue');
//componentDidMount 대신
useEffect(() => {
console.log('Func - 컴포넌트 마운트');
}, []);
//componentDidUpdate & getDerivedStateFromProps 대신
useEffect(() => {
console.log('Before : ' + BGcolor + ' / ' + content + ' / ' + props.content);
console.log('Func - 컴포넌트 업데이트 (Rendering 될때마다)');
});
useEffect(() => {
console.log('Func - 컴포넌트 업데이트 (BGcolor 수정될때마다)');
h1Ref.current.style.backgroundColor = BGcolor;
}, [BGcolor]);
useEffect(() => {
console.log('Func - 컴포넌트 업데이트 (content 수정될때마다)');
}, [content]);
useEffect(() => {
console.log('Func - 컴포넌트 업데이트 (Props 수정될때마다)');
setContent(props.content);
}, [props]);
const changeColor = () => {
if (BGcolor === 'pink') setBGColor('skyblue');
else setBGColor('pink');
};
return (
<div>
<h1 ref={h1Ref} style={{ backgroundColor: BGcolor }} onClick={changeColor}>
This is {content}
</h1>
</div>
);
}
export const LifeCycle2 = () => {
const [content1, setContent1] = useState(0);
useEffect(() => {
console.log('123');
}, [content1]);
const changeRealButton = () => {
console.log('[Real Button Clicked]');
setContent1(111111);
};
const changeFakeButton = () => {
console.log('[Fake Button Clicked]');
setContent1(0);
};
return (
<div>
<Description2 content={content1} />
<button onClick={changeRealButton}>Real Update</button>
<button onClick={changeFakeButton}>Fake Update</button>
</div>
);
};
제일 처음 페이지가 로드 되고나서 위와 같이 console이 찍힌다. Real Update 버튼을 누르면 다음과 같은 과정이 수행된다.
Real Update 버튼을 클릭 함과 동시에 onClick 이벤트로 changeRealButton 함수 호출
console.log('[Real Button Clicked]');
코드가 실행되어 콘솔이 찍히고
setContent1(111111);
코드가 실행되어 state : content1 의 값이 0에서 111111로 바뀐다.
content1의 값이 바뀌고 여기서(LifeCycle2) 할 작업은 끝
<Description2 content={content1} />
코드를 수행한다.
state 값의 변경은 컴포넌트의 리렌더링 조건에 해당하므로 컴포넌트 리렌더링
제일 위에서부터 "Func - 컴포넌트 내부" console이 찍히고
useEffect(() => {
console.log('Before : ' + BGcolor + ' / ' + content + ' / ' + props.content);
console.log('Func - 컴포넌트 업데이트 (Rendering 될때마다)');
});
useEffect의 조건이 아무것도 없으므로 컴포넌트가 렌더링 될 때마다 위의 코드가 실행될 것이다.
버튼을 누르기 전에
<Description2 content={content1} />
위의 코드에서 넘겨주는 props : content1 은 0 이었는데 state가 바뀌면서 content1 에서 props로 넘겨주는 값이 11111 로 바뀐다.
useEffect(() => {
console.log('Func - 컴포넌트 업데이트 (Props 수정될때마다)');
setContent(props.content);
}, [props]);
props의 값이 바뀌었으므로 콘솔이 찍히고 해당 컴포넌트에서 content 의 값이 "not Props" 로 바뀐다.
해당 컴포넌트의 state => content 값이 바뀌었으므로 다시 컴포넌트 리렌더링
useEffect(() => {
console.log('Func - 컴포넌트 업데이트 (content 수정될때마다)');
}, [content]);
state : content 의 값 변경에 따른 useEffect 동작
이 상태에서 만약 Real Update 버튼을 한번 더 누르면 콘솔이 어떻게 찍힐까?
두 가지 경우를 생각해볼 수 있다.
첫 번째는 state의 값이 11111 에서 11111 으로 바뀌는데 , 이는 똑같은 값이므로 useEffect 입장에서 state가 바뀌지 않았다고 판단하게 되고 그럼 위에 엄청나게 나왔던 console 들은 전부 출력되지 않을 것이다.
두 번째는 내가 그럴거라고 유추했던 경우인데 11111에서 11111으로 바뀌어도 값이 바뀌었다고 봐야 하므로 console 이 그대로 찍혀야된다.
결과는?
버튼을 아무리 눌러도 "Real Button Clicked" 라는 콘솔만 찍힌다. 이는 버튼에 걸려있는 onClick 함수이고 그 뒤의 동작들은 전혀 실행하지 않는다.
정확한 정답은 리액트 공식문서에서 찾아볼 수 있었다. 사실 정확히 동일한 값으로 바뀌는데도 리렌더링을 계속 해주면? 리액트 엔진에 엄청난 오버헤드가 걸릴 것이다.
업데이트 함수가 현재 상태와 정확히 동일한 값을 반환하면 후속 재렌더링은 완전히 건너뜁니다.