기존의 Rendering의 문제점
1. 컴포넌트가 렌더링 된다는 것은 누군가가 그 함수(컴포넌트)를 호출하여서 실행되는 것을 말한다. 함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 또다른 함수 등)도 매번 다시 선언되어 사용된다.
2. 컴포넌트는 자신의 state가 변경되거나, 부모에게서 받은 props가 변경되었을 때마다 리렌더링 된다. ( 심지어 하위 컴포넌트에 최적화 설정을 해주지 않으면 부모에게서 받은 props가 변경되지 않았더라도 리렌더링 되는게 기본이다. )
이제 이를 이해하기 위한 간단한 예시를 보자
/src/component/test.tsx
import React, { useState } from "react";
import axios from "axios";
import Blog from "../../pages/blog/index";
import Info from "./info";
const Test = () => {
const [color, setColor] = useState("");
const [movie, setMovie] = useState("");
const onColorInput = (e: any) => {
setColor(e.target.value);
};
const onMovieInput = (e: any) => {
setMovie(e.target.value);
};
return (
<div>
<div>
<label>
What is your favorite color of rainbow ?
<input id="color" value={color} onChange={onColorInput}></input>
</label>
</div>
<div>
What is your favorite movie among these ?
<label>
<input
type="radio"
name="movie"
value="Marriage Story"
onChange={onMovieInput}
/>
Marriage Story
</label>
<label>
<input
type="radio"
name="movie"
value="The Fast And The Furious"
onChange={onMovieInput}
/>
The Fast And The Furious
</label>
<label>
<input
type="radio"
name="movie"
value="Avengers"
onChange={onMovieInput}
/>
Avengers
</label>
<Info color={color} movie={movie} />
</div>
</div>
);
};
export default Test;
// src/component/info.tsx
//@ts-ignore
const getColorKor = (color) => {
console.log("getColorKor");
switch (color) {
case "red":
return "빨강";
case "orange":
return "주황";
case "yellow":
return "노랑";
case "green":
return "초록";
case "blue":
return "파랑";
case "navy":
return "남";
case "purple":
return "보라";
default:
return "레인보우";
}
};
//@ts-ignore
const getMovieGenreKor = (movie) => {
console.log("getMovieGenreKor");
switch (movie) {
case "Marriage Story":
return "드라마";
case "The Fast And The Furious":
return "액션";
case "Avengers":
return "슈퍼히어로";
default:
return "아직 잘 r모름";
}
};
//@ts-ignore
const Info = ({ color, movie }) => {
const colorKor = getColorKor(color);
const movieGenreKor = getMovieGenreKor(movie);
return (
<div className="info-wrapper">
제가 가장 좋아하는 색은 {colorKor} 이고, <br />
즐겨보는 영화 장르는 {movieGenreKor} 입니다.
</div>
);
};
export default Info;
위컴포넌트를 local에서 확인하니 글자 하나를 바꿀 때마다 첫 mount될 때 getColorKor와 getMovieGenreKor가 찍히고 color값만 수정되었는데 두 함수가 모두 호출되어 getColorKor와 getMovieGenreKor가 찍히는 것을 볼 수 있다.
useMemo
이제 상위 컴포넌트에서 받아온 값중 color의 값이 바뀌면 getColorKor하나만 movie의 값이 바뀌면 getMovieGenreKor함수 하나만 호출되게 useMemo를 통해 바꾸어 보자.
// src/component/info.tsx
import { useMemo } from "react";
//@ts-ignore
const getColorKor = (color) => {
console.log("getColorKor");
switch (color) {
case "red":
return "빨강";
case "orange":
return "주황";
case "yellow":
return "노랑";
case "green":
return "초록";
case "blue":
return "파랑";
case "navy":
return "남";
case "purple":
return "보라";
default:
return "레인보우";
}
};
//@ts-ignore
const getMovieGenreKor = (movie) => {
console.log("getMovieGenreKor");
switch (movie) {
case "Marriage Story":
return "드라마";
case "The Fast And The Furious":
return "액션";
case "Avengers":
return "슈퍼히어로";
default:
return "아직 잘 r모름";
}
};
//@ts-ignore
const Info = ({ color, movie }) => {
const colorKor = useMemo(() => getColorKor(color), [color]);
const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);
return (
<div className="info-wrapper">
제가 가장 좋아하는 색은 {colorKor} 이고, <br />
즐겨보는 영화 장르는 {movieGenreKor} 입니다.
</div>
);
};
export default Info;
useMemo를 사용하면 의존성 배열에 넘겨준 값이 변경되었을 때만 메모리제이션 된 값을 다시 계산한다. 예제코드를 직접 변경하여 color값이 바뀔 때는 getColorKor함수만, movie값이 바뀔 때는 getMovieGenreKor함수만 호출되는 것을 확인할 수 있다.
지금 나는 부모 컴포넌트에게 props를 전달 받는 상황으로 설명했지만, 하나의 컴포넌트에서 두 개 이상의 state가 있을 때도 활용할 수 있다.
useCallback
useCallback은 useMemo와 비슷하지만 핵심은 메모리제이션 된 함수를 반환한다라는 문장이 핵심이다. 컴포넌트가 렌더링 될 때마다 내부에 선언되어 있던 표현식(변수, 또다른 함수 등)도 매번 다시 선언되어 사용된다. 라고 이야기 했다. useMemo를 설명하고 있는 같은 예제에서 같은 test.tsx의 onColorInput함수와 onMovieInput함수가 color와 movie의 값이 바뀔 때마다 재선언 된다는 것을 의미한다. 하지만 위 두 함수는 파라미터로 전달받은 이벤트 객체(e)의 target.id값에 따라 setState를 실행해주기만 하면 되기 때문에, 첫 마운트 될 떄 한번만 선언하고 재사용하면 될 것이다.
// src/component/test.tsx
import React, { useState, useCallback } from "react";
import axios from "axios";
import Blog from "../../pages/blog/index";
import Info from "./info";
const Test = () => {
const [color, setColor] = useState("");
const [movie, setMovie] = useState("");
const onColorInput = useCallback((e: any) => {
setColor(e.target.value);
}, []);
const onMovieInput = useCallback((e: any) => {
setMovie(e.target.value);
}, []);
return (
<div>
<div>
<label>
What is your favorite color of rainbow ?
<input id="color" value={color} onChange={onColorInput}></input>
</label>
</div>
<div>
What is your favorite movie among these ?
<label>
<input
type="radio"
name="movie"
value="Marriage Story"
onChange={onMovieInput}
/>
Marriage Story
</label>
<label>
<input
type="radio"
name="movie"
value="The Fast And The Furious"
onChange={onMovieInput}
/>
The Fast And The Furious
</label>
<label>
<input
type="radio"
name="movie"
value="Avengers"
onChange={onMovieInput}
/>
Avengers
</label>
<Info color={color} movie={movie} />
</div>
</div>
);
};
export default Test;
와 같이 useCallback함수를 통해서 바꾸면, 첫 마운트 될 때만 메모리에 할당되었는지 확인하기는 어렵겠지만 한번만 선언되었음을 알 수 있다. 예시와 같은 이벤트 핸들러 함수나 api를 요청하는 함수를 주로 useCallback으로 선언하는 코드를 꽤 많이 보았다.
하지만 비싼 계싼이 아니라면 useMemo사용을 권장하지 않는 것처럼 이정도 수준의 함수 재선언을 막기 위해 useCallback을 사용하는 것도 크게 의미있어 보이지는 않는다. 공식문서를 보면
This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders(e.g. shouldComponentUpdate)
와 같은 말이 나온다. 만약 하위 컴포넌트가 React.memo()와 같은 것으로 최적화 되어 있고 그 하위 컴포넌트에게 callback함수를 props로 넘길 때, 상위 컴포넌트에서 useCallback으로 함수를 선언하는 것이 유용하다라는 의미이다. 함수가 매번 재선언되면 하위 컴포넌트는 넘겨 받은 함수가 달라졌다고 인식하기 때문이다.
- React.memo()로 함수형 컴포넌트 자체를 감싸면 넘겨 받는 props가 변경되지 않았을 때는 상위 컴포넌트가 메모리제이션된 함수형 컴포넌트(이전에 렌더링된 결과)를 사용하게 된다.
- 함수는 오로지 자기 자신만이 동일하기 때문에 상위 컴포넌트에서 callback함수를 (같은 함수이더라도) 재선언한다면 props로 callback함수를 넘겨 받는 하위 컴포넌트 입장에서는 props가 변경되었다고 인식한다
React.memo
React.memo는 High-Order Components(HOC)이다.
High-Order Component(HOC)란 컴포넌트를 인자로 받아 새로운 컴포넌트를 다시 return해주는 함수이다.
'React > ReactJs' 카테고리의 다른 글
SPA기반 SSR 구현하기 - 2 (0) | 2022.02.19 |
---|---|
SPA기반 SSR구현하기 - 1 (0) | 2022.02.19 |
React - Code-spliting, Lazy Reloading (0) | 2022.02.19 |
Webpack을 통한 React개발환경 구축하기 (0) | 2022.02.19 |
Webpack 설정 (0) | 2022.02.17 |