완성은 아니지만 완료는 되어버린 최종프로젝트.
처음 캠프에 합류하고, 이 과정을 통해 소중한 동기들을 얻어가게 될 거라는 말을 들었을 때는, 아 훈훈한 분위기 만들고 싶으신가 보다 했는데
그게 찐이었다 찐찐찐!
실명을 언급하려니까 갑자기 조심스러운 마음이 든다.
모두모두모두 (튜터님, 디자이너님 포함) 감사드립니다.
덕분에 완주할 수 있었습니다.
생애 가장 긴 시간 집중적으로 코딩에 몰입하면서,
개발자의 삶이 어떤 건지 조금이나마 맛 볼 수 있었고
이 습관을 잃고 싶지 않다는 의지 또한 정말 값진 트로피다.
늘어지는 몸과 마음과는 달리 할일이 줄줄이 기다리고 있다 보니,
오늘 내가 어떤 새로운 걸 배우고 적을 수 있을까 고민하다가,
수료식 날, 비주얼의 보성님께서 알려주신 개념 '개방폐쇄의 원칙'을
내 코드에 적용해보려 한다.
너무 거창한 계획보다는 최근 것부터 톺아보는 게 좋겠죠?
🎀 "소프트웨어 엔티티(클래스, 모듈, 함수 등)는
확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다"는 원칙
➡️ 새로운 기능을 추가할 때,
기존 코드를 변경하지 않고도 확장 가능해야 한다.
🖥️ 수정 전 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 배열에 새로운 객체를 추가하면 된다.