24.08.23

강연주·2024년 8월 23일

📚 TIL

목록 보기
31/186

완성은 아니지만 완료는 되어버린 최종프로젝트.

처음 캠프에 합류하고, 이 과정을 통해 소중한 동기들을 얻어가게 될 거라는 말을 들었을 때는, 아 훈훈한 분위기 만들고 싶으신가 보다 했는데
그게 찐이었다 찐찐찐!
실명을 언급하려니까 갑자기 조심스러운 마음이 든다.
모두모두모두 (튜터님, 디자이너님 포함) 감사드립니다.
덕분에 완주할 수 있었습니다.

생애 가장 긴 시간 집중적으로 코딩에 몰입하면서,
개발자의 삶이 어떤 건지 조금이나마 맛 볼 수 있었고
이 습관을 잃고 싶지 않다는 의지 또한 정말 값진 트로피다.

늘어지는 몸과 마음과는 달리 할일이 줄줄이 기다리고 있다 보니,
오늘 내가 어떤 새로운 걸 배우고 적을 수 있을까 고민하다가,
수료식 날, 비주얼의 보성님께서 알려주신 개념 '개방폐쇄의 원칙'을
내 코드에 적용해보려 한다.
너무 거창한 계획보다는 최근 것부터 톺아보는 게 좋겠죠?


[개방 폐쇄 원칙 (OCP)]

https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99

🎀 "소프트웨어 엔티티(클래스, 모듈, 함수 등)는
확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다"는 원칙

➡️ 새로운 기능을 추가할 때,
기존 코드를 변경하지 않고도 확장 가능해야 한다.

🖥️ 수정 전 BadgeCards.tsx

'use client';

import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import { totalBadges } from '../atoms/TotalBadges';
import useUserStore from '@/stores/user.store';

const BadgeCards: React.FC = () => {
  const { diaryCount, membershipDays } = useUserStore((state) => state);
  const [badgesState, setBadgesState] = useState(
    totalBadges.map((badgeGroup) => ({
      ...badgeGroup[1],
      content: badgeGroup[1].content.replace('true', 'false')
    }))
  );

  useEffect(() => {
    const updateBadges = () => {
      setBadgesState((prevBadges) =>
        prevBadges.map((badge) => {
          let isObtained = false;
          if (badge.id.includes('다이어리수집가') && diaryCount !== null && diaryCount >= 3) {
            isObtained = true;
          } else if (badge.id.includes('문구점사장님') && diaryCount !== null && diaryCount >= 15) {
            isObtained = true;
          } else if (badge.id.includes('안녕하세요') && membershipDays !== null && membershipDays >= 1) {
            isObtained = true;
          } else if (badge.id.includes('빨리친해지길바라') && membershipDays !== null && membershipDays >= 7) {
            isObtained = true;
          } else if (badge.id.includes('찐친') && membershipDays !== null && membershipDays >= 30) {
            isObtained = true;
          }
          return {
            ...badge,
            isObtained,
            content: isObtained ? badge.content.replace('false', 'true') : badge.content.replace('true', 'false')
          };
        })
      );
    };

    updateBadges();
  }, [diaryCount, membershipDays]);

  return (
    <div className="grid grid-cols-4 sm:grid-cols-2">
      {badgesState.map((badge, index) => (
        <div
          key={index}
          className="relative w-[20.8rem] h-[32.4rem] m-[0.8rem] sm:m-[0.4rem] sm:w-[10rem] sm:h-[17.2rem] sm:mx-[0.8rem]"
        >
          <Image
            src={badge.content}
            alt={badge.isObtained ? 'Obtained Badge' : 'Unobtained Badge'}
            fill // 이미지가 부모 요소를 가득 채우도록 함
            style={{ objectFit: 'contain' }} // 왜곡 없이 이미지를 표시
            className="rounded-[1.6rem]"
          />
        </div>
      ))}
    </div>
  );
};

export default BadgeCards;

🖥️ 수정 후 BadgeCards.tsx
: 조건을 평가하는 로직을 외부로 분리하고,
이 로직을 쉽게 확장할 수 있도록 만들어야 한다.
badgeConditions 배열을 활용하여 개방폐쇄원칙 적용.

'use client';

import React, { useEffect, useMemo, useState } from 'react';
import Image from 'next/image';
import { totalBadges } from '../atoms/TotalBadges';
import useUserStore from '@/stores/user.store';

const badgeConditions = [
  {
    idPart: '다이어리수집가',
    condition: (diaryCount: number | null) => diaryCount !== null && diaryCount >= 3,
  },
  {
    idPart: '문구점사장님',
    condition: (diaryCount: number | null) => diaryCount !== null && diaryCount >= 15,
  },
  {
    idPart: '안녕하세요',
    condition: (membershipDays: number | null) => membershipDays !== null && membershipDays >= 1,
  },
  {
    idPart: '빨리친해지길바라',
    condition: (membershipDays: number | null) => membershipDays !== null && membershipDays >= 7,
  },
  {
    idPart: '찐친',
    condition: (membershipDays: number | null) => membershipDays !== null && membershipDays >= 30,
  },
];

const BadgeCards: React.FC = () => {
  const { diaryCount, membershipDays } = useUserStore((state) => state);

  const badgesState = useMemo(() => {
    return totalBadges.map((badgeGroup) => {
      const badge = { ...badgeGroup[1] };
      const condition = badgeConditions.find((cond) => badge.id.includes(cond.idPart));
      
      const isObtained = condition ? condition.condition(diaryCount, membershipDays) : false;
      badge.isObtained = isObtained;
      badge.content = isObtained ? badge.content.replace('false', 'true') : badge.content.replace('true', 'false');
      
      return badge;
    });
  }, [diaryCount, membershipDays]);

  return (
    <div className="grid grid-cols-4 sm:grid-cols-2">
      {badgesState.map((badge, index) => (
        <div
          key={index}
          className="relative w-[20.8rem] h-[32.4rem] m-[0.8rem] sm:m-[0.4rem] sm:w-[10rem] sm:h-[17.2rem] sm:mx-[0.8rem]"
        >
          <Image
            src={badge.content}
            alt={badge.isObtained ? 'Obtained Badge' : 'Unobtained Badge'}
            fill
            style={{ objectFit: 'contain' }}
            className="rounded-[1.6rem]"
          />
        </div>
      ))}
    </div>
  );
};

export default BadgeCards;
  • badgeConditions 배열
    : 각 배지 조건을 객체로 정의하여 쉽게 확장 가능.
    이 배열에 새로운 조건을 추가하면 기존 코드 수정 없이 기능 확장.

  • useMemo
    : 배지 상태를 계산하는 로직을 useMemo로 감싸,
    diaryCount나 membershipDays가 변경될 때만 재계산되도록 함.

  • 조건 평가
    : badgeConditions 배열에서 badge.id에 해당하는 조건을 찾고,
    그 조건에 따라 isObtained 값을 설정.
    새로운 조건이 추가되면
    badgeConditions 배열에 새로운 객체를 추가하면 된다.

profile
아무튼, 개발자

0개의 댓글