리랜더링이 모야?
를 얘기하기 전에 리액트의 동작 방식을 알아야 한다. 리액트는 사용자 인터페이스 구축을 위한 JS 라이브러리이다. 리액트(여기서의 리액트는 리액트 자체! ) 는 브라우저가 동작하는 것과 상관 없으며 리액트는 컴포넌트와 상태객체를 관리하고 다른 객체의 상태와 컴포넌트의 변화를 감지하고 비교하는 라이브러리이다.
모두 알겠지만 간단하게 짚고 넘어가자면 React 는 virtual Dom에 미리 한번 노드들을 비교, 배치 등등 해보고 실제 Dom에 화면을 그려준다.
어? 그럼 두번 그리는거라 비효율적인거 아닌가? 싶겠지만 virtual Dom에서 그려보고 비교하고 등등등 뭘 하는 것들은 메모리 안에서만 이루어 지기때문에 묵직한 real Dom에 실제로 그리는 것과 비용적인 측면에서 비교가 안된다.
쉽게 말해서 머릿속으로 그림을 그리는 것과 진짜 유화물감을 사서 캔버스에 그려보는거랑 비용 차이가 어마어마하게 나는걸 비교해보면 될 것같다.
자ㅡ 각설하고
구글에 리액트 리랜더링 을 검색하면
부모 컴포넌트가 재실행 되면 자식 컴포넌트도 재실행 된다~ 이런 말이 보일텐데 쫌 더 쫌....더!!!! 자세하게 봐야한다
' .....??? 뭘 자세히 봐...? ' 싶을텐데ㅋㅋㅋㅋ
솔직히 리랜더링은 어쩌구~ 재평가~ 처음에 들으면 헷갈린다... 그래서 예시 코드를 실행해 보면서 곱씹어보면 이해가 좀 더 될테니 걱정말고 해보자!
일단, 우리가 주로 사용하는 함수형 컴포넌트가
import "./App.css";
function App() {
return (
<div className="app">
<h1>Hi there!</h1>
</div>
);
}
export default App;
이렇게 생긴건 모두 알 것이다. 리액트를 잠시 접어두고 JS로만 보면 return을 해주는 함수이다.
javscript에서 함수는 결국 객체!!! 인것을 명심하자.
자, 이제 리액트를 다시 들고와서 보자. 리액트가 컴포넌트 함수를 실행 ( ==재평가 ) 시키는 조건이 state, props, context(전역객체) 가 달라졌을 때이다.
그러니까 컴포넌트 함수를 다시 실행시킨다는 것이다.
컴포넌트 함수를 실행시켜서 얻은 결과 값은? 당연히 객체일 것이다.
그럼 객체는 뭐다? 참조값이고 우리눈에 보기에 같아 보여도 컴퓨터는 다른 값이라고 인식한다.
<예시>
//원시값은 === 연산자 비교가 가능하다.
const str1 = "hi";
const str2 = "hi";
if (str1 === str2) {
console.log("같다");
} else {
console.log("다르다");
}
// => 같다
const obj1 = { say: "hi" };
const obj2 = { say: "hi" };
if (obj1 === obj2) {
console.log("같다");
} else {
console.log("다르다");
}
// => 다르다
여기까지 숙지했으면 반은 다 익힌거다.
이제 왜 컴포넌트 함수의 재평가 != 리랜더링 인지 살펴보자
<App.js>
import React, { useState } from "react";
import "./App.css";
function App() {
const [activeBtn, setActiveBtn] = useState(false);
const toggleBtnHandler = () => {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
};
// 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
console.log("APP RUNNING");
return (
<div className="app">
<h1>Hi there!</h1>
{activeBtn && <p>This is new Greeting!!</p>}
<button onClick={toggleBtnHandler}> 눌러보세요 </button>
</div>
);
}
export default App;
간단하게 버튼을 누르면 useState로 상태를 변경하고 그 값에 따라 문장이 보이고 안보이는 컴포넌트를 만들었다.
그리고 console.log로 App() 함수가 실행 될 때마다 확인할 수 있도록 했다.
그리고 브라우저에 띄워서 확인해보면?
최초로 띄울때 한번 App()가 실행되고 그다음 버튼을 누를때마다 함수가 실행되는걸 확인 할 수 있다.
그럼 이제 자식 컴포넌트를 추가해 부모 컴포넌트가 재평가를 하게된다면 자식도 재평가 되는지 확인해보자
<Demoouput.js> 추가
import React from "react";
// props로 받은 값에 따라 조건부로 랜더링 할 컴포넌트
const Demooutput = (props) => {
console.log("Demooutput 실행");
return <p>{props.show ? "This is new!" : ""}</p>;
};
export default Demooutput;
<App.js>
import React, { useState } from "react";
import "./App.css";
import Demooutput from "./components/Demo/DemoOutput";
function App() {
const [activeBtn, setActiveBtn] = useState(false);
// 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
console.log("APP RUNNING");
const toggleBtnHandler = () => {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Demooutput show={activeBtn}></Demooutput>
<button onClick={toggleBtnHandler}>눌러보세요</button>
</div>
);
}
export default App;
자식 컴포넌트를 추가하고 console.log로 자식 컴포넌트 함수가 실행될 때 마다 확인할 수 있게 했다.
이제 띄워서 확인해보자
맨 처음 랜더링 될때 한번 실행되고 그 다음 버튼을 누를때마다 셋트마냥 두 컴포넌트 함수가 실행되는 걸 확인 할 수 있다.
그리고 이제 F12를 눌러서 개발자 도구를 켠 다음 Element 탭으로 가서
실제로 DOM에 그려지는 요소를 확인해보자
버튼을 누를때마다 반짝 거리는게 실제 DOM에 변경이 있는 요소이다.
잘.... 반짝거리는걸... 찾아보면.....
<p>This is new!</p>
요 부분이 반짝반짝거리는걸 찾을 수 있을 것이다!
오호... 그럼 이제 콘솔창의 내용과 합쳐서 결론을 도출해보면
함수가 새로 계속 실행(==재평가)되는데 실제DOM에 새롭게 그려지는 것은 실제로 state값에 따라 나타나는 <p> 태그 밖에 없는 것을 확인해 볼 수 있다.
아~ 근데 아쉬운게 지금은 간단한 코드들 밖에 없지만 만약 규모가 커진다면?
실제 DOM에 그려지는건 아니지만 모든 컴포넌트 함수들이 재실행된다면 어플리케이션 속도가 느려질 것이 눈에 선하다...
그래서 나온 대안! React.memo()!!!
- 인자로 받은 컴포넌트에 어떤 props가 입력되는지 확인하고, 입력되는 모든 새 props의 값을 확인해 이를 기존의 props 값과 비교하도록 react에게 전달한다. 그리고 props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 평가하도록 한다.
일단 사용해보자
< Demooutput.js >
import React from "react";
// props로 받은 값에 따라 조건부로 랜더링 할 컴포넌트
const Demooutput = (props) => {
console.log("Demooutput 실행");
return <p>{props.show ? "This is new!" : ""}</p>;
};
export default React.memo(Demooutput);
< App.js >
import React, { useState } from "react";
import "./App.css";
import Button from "./components/UI/Button/Button";
import Demooutput from "./components/Demo/DemoOutput";
function App() {
const [activeBtn, setActiveBtn] = useState(false);
// 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
console.log("APP RUNNING");
const toggleBtnHandler = () => {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Demooutput show={false}></Demooutput>
<button onClick={toggleBtnHandler}>눌러보세요</button>
</div>
);
}
export default App;
<Demooutput> 컴포넌트로 전달하는 props를 false 고정값으로 바꿔줬다. 이제 브라우저에 가서 콘솔을 확인해보자
맨 처음 랜더링 됐을 때를 빼고 버튼을 아무리 눌러도 Demooutput 컴포넌트 함수가 실행되지 않는 것을 확인했다!
React.memo() 가 이전 props 와 실행될때 넣어준 props를 비교해 값에 변화가 없다면 실행을 하지 않기 때문이다.
오.... 그럼 이제 끝인가???? 싶지만
하나! 짚고 넘어가야 할 것이 있다.
<Button.js>
import React from "react";
import classes from "./Button.module.css";
const Button = (props) => {
console.log("BUTTON RUNNING");
return (
<button
type={props.type || "button"}
className={`${classes.button} ${props.className}`}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
);
};
export default React.memo(Button);
< Demooutput.js >
import React from "react";
// props로 받은 값에 따라 조건부로 랜더링 할 컴포넌트
const Demooutput = (props) => {
console.log("Demooutput 실행");
return <p>{props.show ? "This is new!" : ""}</p>;
};
export default React.memo(Demooutput);
<App.js>
import React, { useState } from "react";
import "./App.css";
import Button from "./components/UI/Button/Button";
import Demooutput from "./components/Demo/DemoOutput";
function App() {
const [activeBtn, setActiveBtn] = useState(false);
// 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
console.log("APP RUNNING");
const toggleBtnHandler = () => {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Demooutput show={false}></Demooutput>
<Button onClick={toggleBtnHandler}>Show What she Saying</Button>
</div>
);
}
export default App;
이번엔 버튼을 커스텀해서 컴포넌트로 만들어주었고 props로 함수를 넘겨 받는다.
그리고 React.memo() 로 버튼도 재평가를 막아두었다.
그럼 이제 브라우저로 넘어가서 확인해보자
결과는???
에엥....? 분명 React.memo()로 재평가를 막아두었는데 계속해서 버튼 컴포넌트 함수도 재평가되고 있었다.
=> 위에서 JS에 대한 이야기를 괜히 짚고온게 아니다!!!
잘 생각해보자. 일단 App() 도 어쨌든 함수이기 때문에 재실행된다. 그렇다는건...?
App() 안에 선언해 둔 함수들도 모두!!! 매번!!! 재생성 된다는 뜻이 된다. false도 매번 새로 만들고 toggleBtnHandler 라는 함수도 매번 새로 만들어서 props로 전달해준다.
그러니까 false라는 원시 값이 props로 전달된 <Demooutput> 컴포넌트는 재평가하지 않지만
toggleBtnHandler 라는 함수를 props 로 넘겨받는 <Button> 은 우리 눈에는 같아보이지만 다른 props를 넘겨받고 있다고 인식한 것이다. (toggleBtnHandler 의 리턴값으로 상수를 지정해둔게 아니니 결국 객체가 나올 것이다.)
그리고 원시값과 참조값의 비교는 맨 처음 짚고 내려온걸 떠올려보면 왜 이런 결과가 나왔는지 알 수 있다.
그래서 React.memo()를 썼을때 예상과 달라서 종종 오류가 발생하는 경우가 될 수 있겠다.
그럼 이건 어떻게 보완하지...? 에 대한 답은 다음 글에서 정리 하겠다!
'React & React-native' 카테고리의 다른 글
< React > React.memo() / useCallback() 이란? / useMemo() vs useCallback() 의 차이 (0) | 2023.07.04 |
---|---|
[ React ] react 생명주기란? - 생명주기 테스트 코드 (0) | 2023.06.26 |
[ React ] react 생명주기란? - 생명주기 함수들 (0) | 2023.06.26 |
<google-map-api> 어플에 지도 띄우기 (0) | 2022.12.16 |
< React-native > / expo-camera보다 가벼운 expo-imagepicker / 핸드폰 카메라 권한 설정하기 / 앨범에 있는 사진 올리기/ 권한 설정 (0) | 2022.12.15 |