일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 백준nodejs
- 백준알고리즘
- 익스프레스
- 프로그래머스JS
- 다이나믹프로그래밍
- 알고리즘
- js코테
- 백준js
- 포이마웹
- 자바스크립트
- HTML
- 코테
- css기초
- 리액트커뮤니티
- CSS
- 백준골드
- 백준구현
- 몽고DB
- HTML5
- dp알고리즘
- 프로그래머스코테
- JS
- 리액트
- 프로그래머스
- 백준
- 안드로이드 스튜디오
- JS프로그래머스
- 코딩테스트
- 백준구현문제
- 리액트댓글기능
- Today
- Total
개발새발 로그
[2024-02-08] 팀 프로젝트 개인회고 - set함수에 함수를 인수로,클래스형컴포넌트를 함수형 컴포넌트로 본문
1. Class형 컴포넌트를 함수형 컴포넌트로!
나는 Toast 컴포넌트를 만들면서 Class형 컴포넌트로 먼저 개발을 했다.
creatToast를 저장할 때 편할 것 같아서 사용했는데
모든 컴포넌트가 함수형 컴포넌트를 쓰는데 갑자기 클래스형을 쓰는 것이 이상했고,
클래스형 컴포넌트가 메모리 자원을 함수형 컴포넌트보다 더 사용,
클래스형 컴포넌트는 빌드 후 파일 크기가 함수형 컴포넌트보다 더 큼
React 공식문서에서 함수형 컴포넌트를 권장 등의 이유로 리팩토링을 결정했다.
정확한 함수형 컴포넌트의 장점은 아래와 같다
- 리랜더링 될 때의 값을 유지합니다. 즉, immutable 하다는 것. (위의 예제참고)
- 함수형 컴포넌트는 props에 따른 랜더링 결과를 보장받습니다.
- immutable한 props를 받기 때문에 결국엔 랜더링 결과가 보장된다는 것. 함수형 프로그래밍의 특징과 일맥상통함이 있습니다
- 매개변수로 받는 props의 destructuring을 활용해 가독성을 보장받을 수 있습니다.
- 함수의 모든 장점을 이용할 수 있습니다. (결국 함수니까)
- 함수형 컴포넌트를 사용했을 때 코드가 간결해지고 가독성도 좋습니다!
class Toast {
portal: HTMLElement | null = null;
createToast: ToastCreate | undefined;
constructor() {
if (typeof window !== "undefined") {
const portalId = "toast-portal";
const portalElement = document.getElementById(portalId);
if (portalElement) {
this.portal = portalElement;
return;
}
this.portal = document.createElement("div");
this.portal.id = portalId;
document.body.appendChild(this.portal);
createRoot(this.portal).render(
<ToastManager
bind={(createToast: ToastCreate) => {
this.createToast = createToast;
}}
/>
);
}
}
show(message: string, iconId: ToastIconId, duration = 2000) {
if (!this.createToast) throw new Error("ToastManager 초기화 오류");
this.createToast(message, iconId, duration);
}
}
const toastInstance = new Toast();
export default toastInstance;
위 코드가 리팩토링 이전의 코드다.
충분히 함수형 컴포넌트로 바꿀 수 있는 코드여서 리팩토링을 했다.
근데 리팩토링 도중에 문제가 생겼다.
createToast가 계속 빈값으로 할당되어 show 메서드를 실행할 때 에러를 발생하게 되는 것이다.
무슨 문제일까?
"use client";
import { createRoot } from "react-dom/client";
import ToastManager from "./ToastManager";
import { ToastCreate, ToastIconId } from "./type";
import { useEffect, useState } from "react";
const Toast = () => {
const [portal, setPortal] = useState<HTMLElement | null>(null);
const [createToast, setCreateToast] = useState<ToastCreate>();
useEffect(() => {
if (typeof window !== "undefined") {
const portalId = "toast-portal";
const portalElement = document.getElementById(portalId);
if (portalElement) {
setPortal(portalElement);
return;
} else {
const newPortalElement = document.createElement("div");
newPortalElement.id = portalId;
document.body.appendChild(newPortalElement);
setPortal(newPortalElement);
createRoot(newPortalElement).render(
<ToastManager
bind={(createToast) => {
setCreateToast(createToast); //이 부분
console.log(createToast);
}}
/>
);
}
}
}, []);
const show = (message: string, iconId: ToastIconId, duration = 2000) => {
if (!createToast) throw new Error("ToastManager 초기화 오류");
createToast(message, iconId, duration);
};
return { show };
};
export default Toast;
위 코드를 실행했을 때 ToastManager에서 creatToast함수를 잘 갖고오고 있었다.
그래서 위 컴포넌트에서 관리하는 createToast 상태에 저장해야했는데 계속해서 빈값을 넣고 있었다.
사실 이 문제 해결방법은 아주 간단했다.
<ToastManager
bind={(createToast) => {
setCreateToast(() => createToast);
}}
/>
주석으로 표기한 부분을 위처럼 바꾸면 해결되는 것이었다...
이게 왜 이렇게 되는걸까?
이걸 알기위해 간단한 실험을 해보았다.
...
const func = () => {};
setCreateToast(func);
...
이렇게 set함수에 func함수를 그대로 넣으면 어떻게 될까?
console.log(createToast);를 하게되면 아래와 같이나온다.
func함수는 void 형식이다.
내가 setCreateToast에 넣어줬던 함수도 ()=>void형식이었다.
그럼 다시 func을 업데이트 함수로넣어주면 어떻게 될까?
이렇게 함수 그대로가 createToast에 저장이된다.
set함수는 인수로 함수를 받으면 그 함수의 반환 값을 createToast에 저장한다고 한다.
이게 업데이트함수에서 적용되는 부분인데
우리는 set함수에 인수로 함수(createToast)를 넣었기때문에 createToast함수자체가 들어가는게 아니라
createToast 함수의 반환값이 들어가는 것이다
즉 void 함수니까 undefined가 들어가는 것이다.
이해가 안될 수 있다.
그럼 반환값이 있는 함수로 진행해보자
const func = () => {
return 1;
};
setCreateToast(func);
이렇게 진행한다면 어떻게 될까?
그럼 createToast에는 1이 저장이된다.
즉 func의 반환 값이 저장이 된다.
이를 통해 위에서 추측한 것이 맞다고 예상된다.(정확하지 않을 수 있습니다.)
그럼 아까처럼 업데이트함수 () => 로 set함수에 넣으면 어떻게 될까?
const func = () => {
return 1;
};
setCreateToast(()=>func);
예상한대로 함수 그 자체가 들어가게 된다.
그래서 set함수에 함수를 넣을 때는 이 부분도 꼭 고려를 해야한다.
나처럼 만약 하위 컴포넌트에서 만들어진 함수를 콜백함수로 가져오려고 할 때는 이 점을 항상 유의해야한다!!
2. 처음부터 잘못된 나의 코드..싱글톤 패턴을 제대로 알자!
기존 클래스형 컴포넌트로 구현한 Toast는 싱글톤 패턴이다.
그래서 그걸 함수형으로 바꾸면서 잘 바꿨다고 생각했는데 문제가 있었다.
만약 const newToast = new Toast()를 어떤 컴포넌트에서 만들어서 show를 실행하고,
어떤 컴포넌트에서 const newToast2 = new Toast()를 해서 show를 실행하면
toast가 하나의 인스턴스로 실행되는 것이아니고, 각자의 인스턴스를 생성해 toast가 겹치게 되는 문제가 생겼다.
그래서 다시 봐보니 createToast가 최초로 생성된 이후에도 새로운 인스턴스로 인정되어 각 다른 환경을 갖고있는 것이었다.
너무 당연한 문제였다.
그래서 아래와 같이 수정해줬다.
"use client";
import { createRoot } from "react-dom/client";
import ToastManager from "./ToastManager";
import { ToastCreate, ToastIconId } from "./type";
import { useEffect, useState } from "react";
let createToastInstance: ToastCreate | undefined;
const Toast = () => {
const portalId = "toast-portal";
const [hasPortal, setHasPortal] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
const portalElement = document.getElementById(portalId);
if (!portalElement) {
const newPortalElement = document.createElement("div");
newPortalElement.id = portalId;
document.body.appendChild(newPortalElement);
createRoot(newPortalElement).render(
<ToastManager
bind={(createToast) => {
createToastInstance = createToast;
setHasPortal(true);
}}
/>
);
} else {
setHasPortal(true);
}
}
}, []);
const show = (message: string, iconId: ToastIconId, duration = 2000) => {
if (!createToastInstance) throw new Error("ToastManager 초기화 오류");
createToastInstance(message, iconId, duration);
};
return { show, hasPortal };
};
export default Toast;
let으로 creatToast를 저장하면 모듈 수준에서 관리되기 때문에 모든 컴포넌트에서 동일한 인스턴스를 공유하게 된다.
하지만 싱글톤패턴이 마냥 좋은 것이 아니라고 모두들 말하고 있다.
그래서 이 부분도 추후 어떻게하면 하나의 인스턴스를 공유하면서 사용할 수 있을 지 고민해봐야된다고 생각됐다.
'TIL' 카테고리의 다른 글
[2024-02-11] 개인 프로젝트 회고 - 검색과 추천검색어, 디바운싱, 제어컴포넌트와 비제어 컴포넌트 (1) | 2024.02.11 |
---|---|
[2024-02-09] 팀프로젝트 개인 회고 - SSG에서 코드블록 무시 (0) | 2024.02.09 |
[2024-02-06] 사용자 이름 한글에서 영어로, yarn 사라짐 해결 (1) | 2024.02.06 |
[2024-02-05] 팀 프로젝트 회고 - Icon 재사용컴포넌트 만들기 (0) | 2024.02.05 |
[2024-01-25] 개인 프로젝트 회고 (3) | 2024.01.25 |