angielxx 님의 상세페이지[1팀 이은지] Chapter 2-2. 디자인 패턴과 함수형 프로그래밍

배포 링크

https://angielxx.github.io/front_6th_chapter2-2/

과제의 핵심취지

  • React의 hook 이해하기
  • 함수형 프로그래밍에 대한 이해
  • 액션과 순수함수의 분리

과제에서 꼭 알아가길 바라는 점

  • 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
  • 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
  • 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
  • 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)

기본과제

  • Component에서 비즈니스 로직을 분리하기

  • 비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기

  • 뷰데이터와 엔티티데이터의 분리에 대한 이해

  • entities -> features -> UI 계층에 대한 이해

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

  • 특정 Entitiy만 다루는 함수는 분리되어 있나요?

  • 특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?

  • 데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?

심화과제

  • 재사용 가능한 Custom UI 컴포넌트를 만들어 보기

  • 재사용 가능한 Custom 라이브러리 Hook을 만들어 보기

  • 재사용 가능한 Custom 유틸 함수를 만들어 보기

  • 그래서 엔티티와는 어떤 다른 계층적 특징을 가지는지 이해하기

  • UI 컴포넌트 계층과 엔티티 컴포넌트의 계층의 성격이 다르다는 것을 이해하고 적용했는가?

  • 엔티티 Hook과 라이브러리 훅과의 계층의 성격이 다르다는 것을 이해하고 적용했는가?

  • 엔티티 순수함수와 유틸리티 함수의 계층의 성격이 다르다는 것을 이해하고 적용했는가?


과제 셀프회고

💭 과제 시작 전 고민과 목표 설정

4주차 과제에서 AI를 적극적으로 활용하며 과제를 진행했고, AI가 척척 내 요구에 맞게 코드를 짜는 모습을 보면서 희열과 함께 뇌리 저편에서 불안함이 엄습했습니다. 이쯤되니 '본전' 생각이 뇌를 떠나지 않았어요!

내가 지금 일주일에 10만원 넘는 비용을 지불하며 성장하려고 이 곳에 왔는데...정작 과제는 AI가 다하고 있네?

챕터 1에서는 기술적인 이해에 대한 얻어가는 게 확실했는데 챕터 2에서는 무엇을 어떻게 얻어갈 수 있을까?

앞으로 어떻게 과제 속에서 내 경험과 성장을 최대치로 이끌어낼 수 있을까?

이러한 고민 속에서 5주차 과제를 시작하며 일단 우선적으로 근본적인 과제 목표를 정했습니다.

  1. 테오가 과제를 통해 느껴보라고 한 것 최대한 느껴보기
  2. 과제의 결론보다는 과정을 통해 배우기
  3. 다른 항해러 코드, PR (최대한) 모두 읽어보기

1. 테오가 과제를 통해 느껴보라고 한 것 최대한 느껴보기

단일 책임 원칙을 위반한 거대한 컴포넌트가 얼마나 안 좋은지

1500줄에 달하는 메인 App.tsx를 리팩토링하면서 가장 불편했던 점은, "어디에 무엇이 있는지 모르겠다"는 점이었습니다. 컴포넌트가 추상화되어 있지 않으니 jsx 코드를 봤을 때 앱이 어떤 구성을 하고 있는지 파악이 안되고, 그래서 어디어 무엇이 있는지 알수가 없고, 결국 신규 기능 추가나 수정이 어려울 게 뻔하더라구요.

또 불편했던 점은, 함수와 로직 등 서로 간의 의존도와 결합도가 높아서 분리하려고 해도 분리할 수 없는 상태였다는 점이었습니다. 처음에 단순히 함수부터 분리하고자 함수를 코드에서 잘라내었지만, 외부 상태를 참조하고 있기 때문에 바로 분리할수도 없는 상태였습니다. 하나를 고치면 다른 부분이 깨지는 현상이 계속 발생했습니다.

엔티티를 다루는 것 vs 그렇지 않은 것의 구분

리팩토링을 진행하면서 엔티티를 다루는 것과 그렇지 않은 것을 구분하는 건 어렵지 않았습니다. 과제의 엔티티가 상품, 장바구니, 쿠폰 등으로 명시적이었기 때문에 엔티티를 다루는지 아닌지 여부를 판단하는데 어렵지 않았습니다.

엔티티를 구분하기 위해 "실제 비즈니스에 존재하는지", "DB에 저장될 정보인지"(여기선 로컬스토리지) 를 생각해보았습니다.

hooks, util, model의 구분

테오가 느끼라고 한 것을 충분히 느끼기 위해 과제를 충실히 따라가고자 했고, 힌트로 주신 폴더를 읽어봤는데 hooks, util은 익숙한데 models 폴더가 익숙치 않아 어떤 역할을 하는 모듈의 집합인지 헷갈렸습니다.

GPT를 통해 학습한 결과 세 폴더의 차이점을 이렇게 요약했습니다.

  • hooks : React에 의존하는 상태 관리와 변경
  • utils : 도메인과 무관한 범용 도구 함수
  • models : 순수 비즈니스 로직

공용 로직, 비즈니스 로직, 그리고 커스텀 훅 각각 명시적으로 분리하여 관리하고자 했습니다. 이렇게 세가지 계층으로 로직을 분리하고 나니 각 계층의 책임이 명확해지고 어플리케이션 어디에서도 필요한 로직을 재사용할 수 있게 됐습니다.


2. 과제의 결론보다는 과정을 통해 배우기

4주동안 항해하면서 발견한 저의 고질적인 문제가 있습니다. 이력서, 블로그 등 저는 항상 "결과" 위주로 글을 쓴다는 것이었어요. 어렸을 때부터 변수가 많은 스토리 중심의 역사보다는 인풋-아웃풋이 명확한 수학, 과학을 선호했던 성향이 글쓰기에도 영향을 미친 것 같습니다. 하지만 개발자로서 소통과 공유가 중요한 시대에 이는 분명한 약점이라고 느꼈어요.

4주동안 다른 분들의 회고글을 보면 그 과정이 생동감 있게 느껴지고 적절한 비유와 재치로 기술 이야기를 재밌게 풀어내는 분들이 많더라구요. 그런 분들이 참 부러웠고 어떻게 하면 나의 단점을 개선해볼 수 있을까 고민하게 되었습니다. 그래서 이번 과제에는 과정 중심의 글을 써보고자! PR을 작성하고 있는 지금도! 신경쓰며 PR을 작성하고 있고, 이어서 이번 주차 회고 글에서도 실천해볼 계획입니다!

특히, 이번 과제를 진행하면서 각 단계별 작업 내용을 상세하게 문서로 기록하고자 했습니다. (docs/refactoring-process.md) AI가 정리해준 내용이라 미흡한 부분도 있지만, 해당 문서를 통해 각 단계의 작업을 복기할 수 있어서 추후 글을 작성할 때 큰 도움이 되겠더라구요.

이 목표는 PR과 WIL에서 실현해볼 계획이기에 현재 진행 중이고, 목표 달성 여부는 6주차가 시작되고 나서 알 수 있겠네요. (지켜봐주십쇼)


3. 다른 항해러 코드, PR (최대한) 모두 읽어보기

항해를 시작하게 된 큰 동기 중 하나가 다른 주니어 개발자들이 어떻게 하는지 보고 싶다였습니다. 회사에서도 코드 리뷰를 통해 다른 사람들의 코드를 읽어볼 때 인사이트를 많이 얻을 수 있는 것을 느끼고 있습니다. 이러한 목적을 지난 4주동안 잊고 내 과제하는데 급급해서 다른 사람들은 어떻게 하고 있는지 살펴보지 못한 게 참 아쉽습니다. 그래서 이번 주차에는 기필코 다른 분들의 코드를 최대한 모두 읽어보려고 합니다.

이 목표도 과제 제출 후에 실천할 수 있는 내용이라 아직 진행 중이네요! 얼마나 많은 분들의 코드를 읽었는지..다음 주차에 불시에 검문해주셔도 좋습니다. ㅎ_ㅎ



과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?

  1. 코드 바로 썰고 바로 볶을 수 있는 주방 만들기
  2. 확장 가능한 공통 컴포넌트 개발
  3. 개발효율성을 위한 NotificationBoundary 구현

1. 코드 바로 썰고 바로 볶을 수 있는 주방 만들기

개발할 때 동선을 최소화할 수 있는 폴더 구조로 모듈을 관리하고자 했습니다. 처음 과제를 시작할 때는 아래와 같은 기본적인 폴더 구조로 프로젝트를 구성했습니다.

AS-IS: 역할 기반 구조

src/
├── components/   // 재사용 가능한 UI 컴포넌트들을 모아두는 폴더
├── constants/    // 상수값이나 설정값을 관리하는 폴더 (예: calculation.ts 등)
├── pages/        // 라우팅되는 각 페이지 컴포넌트를 담는 폴더
├── hooks/        // 커스텀 React 훅을 정의하는 폴더
├── utils/        // 유틸리티 함수들을 모아두는 폴더
├── types/        // TypeScript 타입 정의를 위한 폴더
└── App.tsx       // 애플리케이션의 루트 컴포넌트 파일

이 구조는 관심사의 분리를 통해 유지보수성과 확장성을 높이고, 각 기능별로 코드를 체계적으로 관리할 수 있도록 도와줍니다.

이 구조는 모듈 역할 기반으로 폴더가 분리되어 있기 때문에 관심사 분리가 명확하고 각 폴더의 역할이 직관적입니다. 하지만 기능 단위로 개발을 진행할 때 기능 단위로 모듈이 인접해있지 않기 때문에 여러 폴더를 오가며 작업해야하는 단점이 있었습니다.

장바구니 기능을 수정하려면:

  1. components/ 폴더에서 CartItem.tsx 찾기
  2. hooks/ 폴더에서 useCart.ts 찾기
  3. utils/ 폴더에서 cart.util.ts 찾기
  4. constants/ 폴더에서 관련 상수 cart.ts 찾기

마치 요리할 때 재료가 냉장고, 창고, 다락방에 흩어져있는 느낌이랄까...?! 각 유틸, 상수 등을 네임스페이스로 관리하고자 하여 import문은 깔끔하고 가독성이 좋았지만 개발 편의성이나 확장성은 아쉬웠습니다.

준일코치님과의 멘토링에서 이 폴더구조에 대한 피드백을 받고 일명 '개발 동선 최소화'를 위한 새로운 폴더 구조로 개선하고자 했습니다. 처음엔 FSD(Feature-Sliced Design)도 고려했는데, 러닝 커브가 있고 짧은 과제 기간에는 부담스러워서 익숙한 Feature-based 구조를 선택했습니다.

TO-BE: Feature-based 구조

도메인 단위로 폴더 재구성하여 서로 영향 범위가 같은 코드들끼리 인접하도록 해 '바로 야채를 썰고 볶을 수 있게' 개발 동선을 최적화하고자 했습니다.

최상위 구조

src/advanced/
├── pages/           # 페이지 (HomePage, AdminPage)
├── features/        # 도메인별 기능 모듈
└── shared/          # 공통 자원

주요 기능 도메인

features/
├── admin/          # 관리자 기능
├── cart/           # 장바구니
├── coupon/         # 쿠폰 시스템
├── discount/       # 할인 처리
├── notification/   # 알림
├── product/        # 상품 관리
└── search/         # 검색

각 기능별 내부 구조

feature/
├── atoms/          # Jotai 상태 관리
├── components/     # UI 컴포넌트
├── hooks/          # 커스텀 훅
├── models/         # 비즈니스 로직
├── types/          # 타입 정의
├── constants/      # 상수
├── data/           # 초기 데이터
└── utils/          # 유틸리티

공통 자원

shared/
├── components/     # 공통 UI (icons, layout, ui)
├── constants/      # 공통 상수
├── hooks/          # 공통 훅
├── utils/          # 공통 유틸리티
└── errors/         # 에러 처리

이렇게 폴더 구조를 변경하고 나니 어떤 기능을 수정할 때 이동하는 폴더와 파일 범위가 확실히 좁아졌다는 걸 느꼈습니다. 각 기능 별로 일관된 하위 폴더 구조를 유지하고 있으니 신규 도메인이나 기능이 생길 때 feature 단위로 폴더를 추가하면 돼서 개발편의성도 훨씬 좋아졌습니다.


2. 확장 가능한 공통 컴포넌트 개발

Icon 컴포넌트

Icon 컴포넌트를 일관적인 인터페이스로 쉽게 새로운 아이콘을 추가하고 수정할 수 있도록 설계하고자 했습니다. "가장 단순하면서도 효과적인 방법이 뭘까?" 생각해보다가 모든 SVG 아이콘의 구조가 반복적이고 외부 svg 태그 또한 동일한 형태로 반복되고 있는 걸 발견했습니다.

이를 통해 공통 인터페이스를 가지고 있는 감싸는 메인 Icon 컴포넌트를 만들고, 아이콘 종류마다 각기 다른 path를 가지도록 설계했습니다.

// 개별 아이콘 정의
const PlusIcon = (props: IconProps) => <path />
const MinusIcon = (props: IconProps) => <path />
const CartIcon = (props: IconProps) => <path />

// 아이콘 타입 정의
const ICONS: Record<IconType, React.FC<IconProps>> = {
  cart: CartIcon,
  shop: ShopIcon,
  shopThin: ShopThin,
  minus: MinusIcon,
  image: ImageIcon,
  close: CloseIcon,
  plus: PlusIcon,
  trash: TrashIcon,
} as const;

export default Icon;

// 메인 아이콘 컴포넌트
const Icon: React.FC<IconProps> = ({
  size = 6,
  color = "text-gray-700",
  className = "",
  onClick,
  disabled = false,
  type = "cart",
}) => {
  // ...중략

  const IconComponent = ICONS[type];

  return (
    <svg
      className={`${baseClasses} ${disabledClasses} ${interactiveClasses} ${color}`}
      fill="none"
      stroke="currentColor"
      viewBox="0 0 24 24"
      onClick={handleClick}
    >
      <IconComponent />
    </svg>
  );
};

// 사용
<Icon type="cart" size={5} />

Tabs, AdminSection

합성 패턴을 기반으로 재사용성이 높은 컴포넌트를 구현하고자 했습니다.

// src/advanced/features/admin/components/AdminTabs.tsx
// 합성 패턴 기반 재사용 가능한 Tabs
<Tabs defaultValue="products" onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Trigger value="products">상품 관리</Tabs.Trigger>
    <Tabs.Trigger value="coupons">쿠폰 관리</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="products">
    <ProductAdmin />
  </Tabs.Content>
  <Tabs.Content value="coupons">
    <CouponAdmin />
  </Tabs.Content>
</Tabs>

3. 개발효율성을 위한 NotificationBoundary 구현

addNotification이 모든 도메인에 걸쳐 사용되어 거의 대부분의 컴포넌트가 props로 받아야 하는 상황이었습니다. 알림을 추가하는 것을 각 사용부에서 처리하는 것이 아니라 외부의 한 곳에서 모아 처리할 방법이 없을까 고민했습니다.

알림을 발생시키는 곳에서는 에러 형태를 던지기만 하고 외부에서 그걸 캐치해서 처리할 수 없을까 고민하게 되었고, 그렇게 떠오른 패턴이 에러 바운더리 구조였습니다.

아키텍처

NotificationBoundary 컴포넌트

// src/basic/features/notification/components/NotificationBoundary.tsx
// 전역 에러 이벤트 리스너로 NotificationError를 캐치
export function NotificationBoundary({ children }: PropsWithChildren) {
  const [notifications, setNotifications] = useState<Notification[]>([]);

  useEffect(() => {
    const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
      if (event.reason instanceof NotificationError) {
        event.preventDefault();

       // ...알림 추가 로직

        return;
      }

      throw event;
    };

    const handleGlobalError = (event: ErrorEvent) => {
      if (event.error instanceof NotificationError) {
        event.preventDefault();

        // ...알림 추가 로직

        return;
      }

      throw event;
    };

    window.addEventListener("error", handleGlobalError);
    window.addEventListener("unhandledrejection", handleUnhandledRejection);

    return () => {
      window.removeEventListener("error", handleGlobalError);
      window.removeEventListener(
        "unhandledrejection",
        handleUnhandledRejection
      );
    };
  }, []);

  const removeNotification = useCallback((id: string) => {
    setNotifications((prev) => prev.filter((n) => n.id !== id));
  }, []);

  return (
    <div>
      // 알림 렌더링
      <div className="fixed top-20 right-4 z-50 space-y-2 max-w-sm">
        {notifications.map((notification) => (
          <NotificationItem
            key={notification.id}
            notification={notification}
            removeNotification={removeNotification}
          />
        ))}
      </div>
      {children}
    </div>
  );
}

2. NotificationError 클래스

// 커스텀 에러 클래스로 알림 타입과 메시지를 포함
export class NotificationError extends Error {
  constructor(
    public message: string,
    public type: NotificationType
  ) {
    super(message);
    this.name = "NotificationError";
    this.type = type;
  }
}

3. throwNotificationError 유틸리티

// src/basic/features/notification/utils/notificationError.util.ts
export const throwNotificationError: Record<
  NotificationType,
  (message: string) => never
> = {
  [NOTIFICATION.TYPES.ERROR]: (message: string): never => {
    throw new NotificationError(message, NOTIFICATION.TYPES.ERROR);
  },

  [NOTIFICATION.TYPES.SUCCESS]: (message: string): never => {
    throw new NotificationError(message, NOTIFICATION.TYPES.SUCCESS);
  },

  [NOTIFICATION.TYPES.WARNING]: (message: string): never => {
    throw new NotificationError(message, NOTIFICATION.TYPES.WARNING);
  },
} as const;

일반 에러와 알림 에러를 구분하여 모두 캐치하여 핸들링 할 수 있고, 알림 관련 로직을 모두 NotificationBoundary에서 처리할 수 있어 관심사가 명확히 분리됐습니다. 알림을 발생시키는 구조도 간편하고 새로운 알림 타입을 추가하기도 좋은 구조지만, 구현하는 과정에서 문제점을 발견했습니다.

어쨌든 알림을 발생시키지만 에러 타입이기 때문에 알림 에러가 발생한 시점에 로직의 흐름이 중단된다는 것이었습니다. 이 문제 때문에 아무데서나 알림을 발생시켜선 안되고 알림을 발생시키전 필요한 로직은 모두 선행시킨 후 마지막에 알림을 발생시켜야 했습니다.


과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?

함수 리팩토링

아직 개별 함수 단위에서 깔끔하지 못한 코드들이 많은데 시간이 좀 더 있었다면 함수 단위로 리팩토링을 더 진행하고 싶습니다.

비동기 알림 시스템

앞서 언급했던 NotificationError 특성 상 로직을 중단하는 문제를 개선하고 싶습니다. 가능할지는 구현해봐야 알겠지만, Claude가 추천한 방식은 Promise로 감싸서 즉시 에러를 던지지 않게 하는 방법입니다.

동기적 에러에서 비동기 에러로 개선

Promise로 에러를 발생시켜 현재 실행 컨텍스트가 끝난 후 다음 이벤트 루프에서 처리하도록 유도하여 메인 로직이 중단되지 않습니다.

// 즉시 에러를 던지지 않고 Promise로 감싸서 처리
export const notifyAsync = {
  success: (message: string) => {
    Promise.reject(new NotificationError(message, 'SUCCESS'));
  },
  error: (message: string) => {
    Promise.reject(new NotificationError(message, 'ERROR'));
  },
  warning: (message: string) => {
    Promise.reject(new NotificationError(message, 'WARNING'));
  },
};

// 사용 예시
const handleSubmit = () => {
  // 필요한 로직 실행
  updateData();
  saveToStorage();
  
  // 마지막에 알림 (로직 중단 없음)
  notifyAsync.success('저장이 완료되었습니다!');
};

AS-IS: 동기적 에러로 즉시 중단

// 현재 상황 (동기적 에러)
export const throwNotificationError = {
  success: (message: string): never => {
    throw new NotificationError(message, 'SUCCESS'); // 즉시 실행 중단
  }
};

// 사용 시
const handleSubmit = () => {
  updateData();        // 실행됨
  throwNotificationError.success('완료!'); // 여기서 즉시 중단
  saveToStorage();     // 실행되지 않음!
};

TO-BE: Promise만 호출하고 계속 진행

// Promise.reject 방식
export const notifyAsync = {
  success: (message: string) => {
    Promise.reject(new NotificationError(message, 'SUCCESS'));
    // 함수는 여기서 정상 종료됨 (에러를 던지지 않음)
  }
};

// 사용 시
const handleSubmit = () => {
  updateData();        // 실행됨
  notifyAsync.success('완료!'); // Promise.reject만 호출하고 계속 진행
  saveToStorage();     // 실행됨!
};

NotificationBoundary에서 Promise 감지하는 법

Promise.reject()를 호출하면 이 Promise가 catch되지 않으면 브라우저에서 'unhandledrejection' 이벤트 발생시킵니다.

export function NotificationBoundary({ children }: PropsWithChildren) {
  const [notifications, setNotifications] = useState<Notification[]>([]);

  useEffect(() => {
    // Promise.reject()로 생성된 rejected Promise를 감지
    const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
      console.log('감지된 rejected Promise:', event.reason);
      
      if (event.reason instanceof NotificationError) {
        event.preventDefault(); // 브라우저 콘솔 에러 방지
        
        // ...알림 추가 로직
        return;
      }
    };

    window.addEventListener("unhandledrejection", handleUnhandledRejection);
    
    return () => {
      window.removeEventListener("unhandledrejection", handleUnhandledRejection);
    };
  }, []);

  // ... 렌더링 로직
}

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

컴포넌트 분리 수준에 대한 피드백

제가 선택한 컴포넌트 분리 수준이 적절한지 궁금합니다. 더 세분화해야 하는 부분이나, 반대로 과도하게 분리한 부분이 있을까요?

Icon 사용 불편함

아이콘 추가될 때마다 type과 객체에 추가해줘야 하는 것이 불편합니다. 더 효율적으로 아이콘 컴포넌틀를 관리할 수 있는 설계 구조가 없을지 추천해주세요!

NotificationBoundary 패턴의 실용성

제가 시도했던 에러 기반 알림 시스템이 실무에서도 시도해볼 만한 패턴인지, 혹은 다른 더 나은 전역 알림 처리 방법이 있는지 궁금합니다.

과제 피드백

안녕하세요 은지님! 5주차 과제 너무 잘 진행해주셨네요!! 여태까지는 결과 위주의 글을 작성했다고 말씀해주셨는데, 과정이 정말 디테일하게 담겨있어서 깜짝 놀랐답니다 ㅎㅎ 기승전결이 다 녹아있다고 느껴졌어요.

제가 선택한 컴포넌트 분리 수준이 적절한지 궁금합니다. 더 세분화해야 하는 부분이나, 반대로 과도하게 분리한 부분이 있을까요?

무척 분리를 잘 해주셨어요 ㅎㅎ 여기서에서 조금 더 나아가보자면, Primitive Component 를 추출하는 과정이 필요할 수도 있다고 생각해요. 다만 필수는 아닙니다!

가령, Input, Button 처럼 제일 하위에서 쓰일 수 있는 컴포넌트랄까!? 지금 icon이 이런 역할을 수행하고 있네요!

아이콘 추가될 때마다 type과 객체에 추가해줘야 하는 것이 불편합니다. 더 효율적으로 아이콘 컴포넌틀를 관리할 수 있는 설계 구조가 없을지 추천해주세요!

svgr 이라는 키워드로 찾아보시면 좋답니다 ㅎㅎ 가령, vite에서는 https://www.npmjs.com/package/vite-plugin-svgr 을 사용해볼 수 있어요. svg 파일을 아예 컴포넌트로 관리할 수 있도록 해주는거죠.

혹은 디자인 시스템들이 어떻게 구현되어있는지 참고해보면 좋답니다!

https://www.chakra-ui.com/docs/components/icon-button https://www.chakra-ui.com/docs/components/icon#custom-svg

제가 시도했던 에러 기반 알림 시스템이 실무에서도 시도해볼 만한 패턴인지, 혹은 다른 더 나은 전역 알림 처리 방법이 있는지 궁금합니다.

굉장히 체계적으로 설계를 잘 해주셨어요! 제가 이해한 부분은, throw를 통해 NotficiationBoundary 에 메세지를 쌓아가는 방식이라고 이해했어요. 다만, 조금 아쉬운 부분은 success를 처리하는 부분도 throw로 처리하고 있다는점!?

그래서 context를 조금 엮어서 사용해주면 어떨까 싶어요! throw를 통해 쌓아가기보단, 아예 context api를 통해 상위 계층으로 메세지를 보내서 알림을 쌓아가는 방식으로 해결할 수 있지 않을까 싶네요 ㅎㅎ

결국 "에러를 에러처럼 사용 하는 것" 이 제일 중요하다고 생각해요!