리액트에는 메모제이션이라는 기능이 있다. React.memo
,useCallback
, useMemo
세가지 기능으로 컴포넌트의 재실행을 방지 함으로서 여러번 실행되는 시간을 줄이고 리렌더링을 최소화 하는 것이다.
React.memo
이 기능은 상위 컴포넌트에 사용할 수록 좋다,
기본적으로 리액트는 상태변수라고 정의된 변수가 변경되면 컴포넌트가 재실행되며 변경된 상태를 컴포넌트에 반영한다.
이때 상위 컴포넌트에서 이러한 과정이 실행된다면 그아래 종속되어있는 하위 컴포넌트 모두가 재실행된다.
따라서 하위 컴포넌트에서는 상위컴포넌트에서 넘겨받는 props들이 변경이 일어나지 않는다면 굳이 재실행하지 않아도 된다.
이러한 개념에서 React.memo
는 해당 컴포넌트를 감싼다. 그리고 부모로 부터 넘겨받는 props들이 변경되었는지 안되었는지
얕은 비교를 통해서 비교한다.
이러한 얕은 비교를 제대로 하기위해 useCallback
, useMemo
가 사용된다.
자세한 동작과정
위 두개의 코드에서 PostModal은 자식 컴포넌트에게 props를 거의 넘겨준다. 즉, 자식 컴포넌트 입장에서는 PostModal
이 바뀌면 자식컴포넌트에 전달되는 props
들이 매번 바뀌게 된다. 이렇게 되면 굳이 props들을 비교하는 의미가 없다. 의미 없는 얕은 비교연산을 수행하는것이다. 이럴때에는 사용할 필요가 없다.
PostModal.tsx
const PostModal: React.FC<PostModalProps> = (props) => {
const now = new Date();
const dateArr = props.regDate.substring(0, 10).split("-");
const stDate = new Date(
Number(dateArr[0]),
Number(dateArr[1]),
Number(dateArr[2])
);
const endDate = new Date(
now.getFullYear(),
now.getMonth() + 1,
now.getDate()
);
const pastDays =
(endDate.getTime() - stDate.getTime()) / (1000 * 60 * 60 * 24);
const repCountry = props.repCountry;
const repLanguage = LocaleLanguage[props.repLanguage] || "";
const repHopeLanguage = LocaleLanguage[props.repHopeLanguage] || "";
const country = props.country;
const language = useMemo(() => {
return props.language.map((item) => LocaleLanguage[item] || "");
}, [props.language]);
const hopeLanguage = useMemo(() => {
return props.hopeLanguage.map((item) => LocaleLanguage[item] || "");
}, [props.hopeLanguage]);
return (
<PostModalBox>
<PostModalImage src={`${props.imageUrl}`} />
<PostUserName>{props.name}</PostUserName>
<PostModalActive id={props.id} isLike={props.isLike} />
<PostModalInfoList
title="I am from"
values={country}
mainValue={repCountry}
/>
<PostModalInfoList
title="I can speak"
values={language}
mainValue={repLanguage}
/>
<PostModalInfoList
title="I want to speak"
values={hopeLanguage}
mainValue={repHopeLanguage}
/>
<PostModalContent content={props.content} />
<PostModalSocialList
title="Contact to me"
faceBookUrl={props.facebookUrl}
instagramUrl={props.instagramUrl}
/>
<PostModalInfo
regDate={String(pastDays)}
views={props.views}
likes={props.likes}
/>
</PostModalBox>
);
};
export default React.memo(PostModal);
LikeAlarm.tsx
const MLikeAlarm: React.FC = () => {
const newLikes = useAppSelector((state) => state.chat.newLikes);
const alarmlikeMessages = newLikes.map((item) => {
return (
<AlarmChatBox key={item.regDate + item.name}>
<ChatBox
imageUrl={`/Layout/heart.png`}
regDate={item.regDate}
sender={item.likeType}
context={`${item.name}님이 ${item.likeType} 하셨습니다.`.substring(
0,
20
)}
type={LIKEALARM_TYPE}
/>
</AlarmChatBox>
);
});
return (
<PushAlarmBox>
{newLikes.length > 0 ? (
alarmlikeMessages
) : (
<EmptyAlarm title="No new likes Messages" />
)}
</PushAlarmBox>
);
};
const LikeAlarm = React.memo(MLikeAlarm);
export { LikeAlarm };
위 코드를 보면 props
로 아무것도 받지 않는다. 즉, 상위 컴포넌트가 재실행되어도 굳이 재실행될 필요가 없다. 재실행되도 같은 실행이 반복되기 때문이다.
이럴경우 React.memo를 사용해서 얕은 비교를 통해 상위 컴포넌트가 재실행되어도 재실행되지 않도록 하자.
useCallback
은 함수의 참조값을 저장한다. 기본적으로 자바스크립트는 함수또한 객체이기 때문에 참조값을 저장한다. 따라서 컴포넌트가 재실행되면 함수에 대한 참조값이 바뀌게 된다. 이 참조값을 메모한다음에 컴포넌트가 재실행될때 deps 배열의 값이 바뀌지 않는다면 새로 함수를 생성하지 않고 기존에 있던 참조값 불러와 함수를 실행시킨다.
위에서 말했듯이 React.memo
는 얕은 비교를 한다. 만약 함수를 자식컴포넌트의 props
로 넣게 되면 이 함수는 매번 재실행되어 새로운 참조값을 가지기 때문에 같은 함수이더라도 새로운 참조값을 가진 새로운 함수로서 자식컴포넌트에 전달된다. React.memo
를 씌운 얕은 비교를 하는 자식 컴포넌트 입장에서는 변하지도 않는 함수때문에 재실행을 해야하는 것이다. 이때 useCallback
을 사용할 수 있다.
ChatRoom.tsx
const handleChangeInput: ChangeEventHandler<HTMLTextAreaElement> =
useCallback((e) => {
setMessage(e.target.value);
}, []);
const scrollToBottom = useCallback(() => {
if (!chatMessageBox.current) return;
chatMessageBox.current.scrollIntoView({
behavior: "auto",
block: "end",
inline: "nearest",
});
}, []);
useEffect(() => {
scrollToBottom();
}, [mainChatMessages.length, scrollToBottom]);
return (
<Outline>
<Inner>
<ChatRoomHeader
selectLanguage={selectLanguage}
setSelectLanguage={setSelectLanguage}
/>
{isViewChatList ? (
<ChatListWrapper>
<ChatList />
</ChatListWrapper>
) : (
<>
<MessageContainer>{messageBoxContent}</MessageContainer>
<MessageInput
onChange={handleChangeInput}
sendMessage={sendMessage}
value={message}
/>
</>
)}
</Inner>
</Outline>
);
위 코드를 보면 MessageInput
에 props
를 넘기는데 handleChangeInput
을 넘긴다. 이 매번 재실행하는 것을 방지하기 위해서 MessageInput 컴포넌트에 함수 값을 저장하기 위해서 useCallback을 사용한다.
또한 useEffect()
에서도 마찬가지다. useEffect안에서 함수를 사용하면 deps에 함수를 넣어야 하는데, 굳이 함수가 매번같은 함수라면 함수값을 저장해서 useEffect
의 콜백함수가 쓸데없이 재실행되는 것을 막아야한다.
이런 비슷한 경우로 useCallback 로 묶은 함수안에 함수를 사용할때에도 마찬가지 이유로 안의 함수를 useCallback으로 묶는다.
함수의 매개변수가 매번 바뀌는 경우 어짜피 매개변수 때문에 함수는 useCallback을 씌우더라도 재생성 되기 때문에 저장할 필요가 없다. 어짜피 재생성되어버리는 경우에는 사용하지 않아도 된다.
객체는 참조값을 가지므로 매번 새로운 값을 가진다.
ChangeLanguageDropDown.tsx
const MChangeLanguageDropDown: React.FC<ChangeLanguageDropDownProps> = (
props
) => {
const { bodyBackgroundColor, bodyFontColor } = useContext(ThemeContext);
const initialValue = LanguageLocale[props.selectLanguage[0]].toUpperCase();
const options = useMemo(() => ["Korean", ...Object.keys(LanguageLocale)], []);
return (
<ChangeLanguageDropDownBox>
<DropDown
fontWeight="600"
fontSize="1em"
width="110px"
height="30px"
options={options}
initialValue={initialValue}
setValues={props.setSelectLanguage}
value={initialValue}
index={0}
type={NOMAL_TYPE}
backgroundColor={bodyBackgroundColor}
color={bodyFontColor}
>
<ImEarth />
</DropDown>
</ChangeLanguageDropDownBox>
);
};
const ChangeLanguageDropDown = React.memo(MChangeLanguageDropDown);
위 코드를 보면 매번 options는 정해진 값을 갖는다. 따로 다른 값에 의존해서 변하는 값도 아니므로 매번 새로운 참조값을 생성해 낼 필요는 없다.
따라서 객체를 useMemo로 감싸 참조값을 저정한다. 그러면 자식 컴포넌트에서 받을때 React.memo
에 의해 재실행을 방지 할 수 있다.
리덕스와 메모제이션을 적용하면서 성능향상을 측정하고자 크롬의 LightHouse
를 사용했다. 이부분중에서 Performance는 6가지의 다양한 기준으로 측정을 한다.
길게 말하기 보단 함축적으로 정의해서 사용자에게 보여지는 렌더링 시간을 측정한다. 만약 내가 메모제이션을 잘 적용했다면 성능향상으로 이부분에서 측정이 될 것이다.
이부분에서 많은 개선을 했다.
시각장애인들이 웹페이지를 사용할때 페이지 리더를 사용한다. 이때 해당 태그들에 대한 설명을 리더가 제대로 수행할 수 없다면 접근성의 저하로 나타난다.
등등...
❗️ 배경색과 텍스트의 명암비를 강조하는데 이부분은 신뢰적이지 않은 것 같다. 주황생이랑 흰색이면 엄청 구분잘되는데...
❗️ unload 이벤트를 사용한적이 없는데 왜자꾸 바꾸라는 건지 모르겠다.
_app.tsx
<DefaultSeo
title={"ChawChaw 언어를 교환합시다.🗣"}
description={"대학내 교환학생 언어교환 채팅 어플리케이션입니다."}
canonical="https://www.chawchaw.vercel.app"
openGraph={{
type: "website",
locale: "en_IE",
title: "ChawChaw 언어를 교환합시다.🗣",
description: "대학내 교환학생 언어교환 채팅 어플리케이션입니다.",
images: [
{
url: "https://i.ibb.co/m0NY7yQ/image.jpg",
width: 800,
height: 600,
alt: "ChawChaw 소개 이미지",
},
],
url: "https://www.chawchaw.vercel.app",
site_name: "ChawChaw",
}}
twitter={{
handle: "@chawchawTwitter",
site: "chawchaw.vercel.app",
cardType: "summary",
}}
additionalLinkTags={[
{
type: "image/png",
sizes: "32x32",
href: "/Layout/chaw.png",
rel: "icon",
},
]}
additionalMetaTags={[
{
name: "viewport",
content:
"viewport-fit=cover, width=device-width, initial-scale=1",
},
]}
/>
<Component {...pageProps} />
_document.tsx
<Html lang="kr">
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@200;300;400;600;700;900&display=swap"
rel="stylesheet"
/>
<link rel="canonical" href="https://www.chawchaw.vercel.app" />
</Head>
<body>
<div id="root">
<Main />
<NextScript />
</div>
<div id="notification"></div>
</body>
</Html>
즉, 성능향상 전과 후를 비교하면 모든 점수를 더 한후 향상치를 %화 해본다면...!!!
두구구둑두구두구구
와 같다.