λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

FrontEnd/React

[React] SOLID 원칙 적용 μ»΄ν¬λ„ŒνŠΈ λ¦¬νŒ©ν† λ§

πŸ“Œ SOLID 원칙에 맞게 μ»΄ν¬λ„ŒνŠΈ λ¦¬νŒ©ν† λ§

ν‰μ†Œμ— λ‚΄ μ½”λ“œλŠ” μ™œ 가독성이 쒋지 λͺ»ν• κΉŒ? 

μ™œ λ‚΄ μ½”λ“œλŠ” μ΄λ ‡κ²Œ 길지? 

λΌλŠ” 생각을 ν–ˆμ—ˆλŠ”λ° μ»΄ν¬λ„ŒνŠΈ 섀계 방식을 λ°”κΎΈλ‹ˆ μ’€ 더 읽기 쉽고 μ±…μž„ λΆ„ν• λœ 쒋은 μ½”λ“œλ₯Ό 섀계할 수 있게 λ˜μ–΄μ„œ μ“°λŠ” ν›„κΈ°μž…λ‹ˆλ‹€.

λ§Œμ•½ 저와 같이 μƒκ°ν•˜μ‹œλŠ” 뢄듀이 μžˆλ‹€λ©΄ ν•œλ²ˆ λ΄μ£Όμ‹œλ©΄ 쒋을 κ±° κ°™μŠ΅λ‹ˆλ‹€. 

 

- SRP (Single Responsibility Principle) 단일 μ±…μž„ 원칙

단일 μ±…μž„ μ›μΉ™μ΄λž€ μ‰½κ²Œ λ§ν•΄μ„œ μ½”λ“œλŠ” ν•˜λ‚˜μ˜ μž‘μ—…μ„ ν•΄μ•Ό ν•œλ‹€λΌκ³  λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 그전에 μ œκ°€ 짜던 μ½”λ“œλ₯Ό 보면 μ»΄ν¬λ„ŒνŠΈ μžμ²΄μ—μ„œ μ—¬λŸ¬ 가지 λ‘œμ§μ„ λŒλ©΄μ„œ UIλ‘œμ§κΉŒμ§€ ν–‰ν•˜κ³  μžˆλ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 

밑에 μ˜ˆμ‹œκ°€ μ±…μž„ λΆ„ν• λ˜κΈ° μ „ μ½”λ“œμž…λ‹ˆλ‹€.

 

μ½”λ“œλ₯Ό 보면 CouponList μ»΄ν¬λ„ŒνŠΈλŠ” μ„œλ²„μ—μ„œ 데이터도 패칭 ν•˜κ³ , κ·Έ 패칭 된 λ°μ΄ν„°λ‘œ 화면은 λ Œλ”λ§ ν•˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ‰½κ²Œ λ§ν•΄μ„œ 이 μ»΄ν¬λ„ŒνŠΈλŠ” 2가지 역할을 ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

1. μ„œλ²„μ—μ„œ 쿠폰 데이터λ₯Ό κ°€μ Έμ˜¨λ‹€.

2. κ°€μ Έμ˜¨ λ°μ΄ν„°λ‘œ 화면을 λ Œλ”λ§ ν•œλ‹€.

function CouponList({ handleSelectCoupon }: Props) {
  const [coupons, setCoupons] = useState<Coupon[]>([]);

  useEffect(() => {
    getCoupons().then((res) => setCoupons(res));
  }, []);

  return (
    <Block>
      {coupons.map((coupon, i) => (
        <CouponItem
          key={i}
          title={coupon.title}
          onClick={() => handleSelectCoupon(coupon)}
        />
      ))}
      <CouponItem title="μ·¨μ†Œν•˜κΈ°" onClick={() => handleSelectCoupon(null)} />
    </Block>
  );
}

ν•˜μ§€λ§Œ useEffect, useStateκ°€ μžˆλŠ” 뢀뢄은 hook으둜 λΊ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ hook으둜 뢄리λ₯Ό ν•˜λ©΄ μ•„λž˜μ™€ 같은 μ½”λ“œκ°€ λ©λ‹ˆλ‹€.

 

즉 μ„œλ²„μ—μ„œ 데이터λ₯Ό 패칭 ν•˜λŠ” 역할은 useGetCoupons ν›…μ—κ²Œ μœ„μž„ν•˜κ³  ListλŠ” 데이터λ₯Ό 가지고 화면을 κ·Έλ¦¬λŠ”λ° 집쀑할 수 μžˆλ„λ‘ μ—­ν•  뢄리λ₯Ό ν•΄μ€€ κ²ƒμž…λ‹ˆλ‹€.

μ΄λ ‡κ²Œ ν–ˆμ„ μ±…μž„ λΆ„λ¦¬λ‘œ μΈν•œ 가독성이 μ˜¬λΌκ°€κ³  역할별 ν…ŒμŠ€νŒ… ν•˜κΈ°λ„ μš©μ΄ν•΄μ§‘λ‹ˆλ‹€.

function CouponList({ handleSelectCoupon }: Props) {
  const { coupons } = useGetCoupons();
  return (
    <Block>
      {coupons.map((coupon, i) => (
        <CouponItem
          key={i}
          title={coupon.title}
          onClick={() => handleSelectCoupon(coupon)}
        />
      ))}
      <CouponItem title="μ·¨μ†Œν•˜κΈ°" onClick={() => handleSelectCoupon(null)} />
    </Block>
  );
}
function useGetCoupons() {
  const [coupons, setCoupons] = useState<Coupon[]>([]);

  useEffect(() => {
    getCoupons().then((res) => setCoupons(res));
  }, []);

  return {
    coupons,
  };
}

 

- OCP (Open Closed Principle) 개방 폐쇄 원칙

개방 폐쇄 μ›μΉ™μ΄λž€ ν™•μž₯μ—λŠ” μ—΄λ €μžˆμ–΄μ•Ό ν•˜μ§€λ§Œ λ³€κ²½μ—λŠ” λ‹«ν˜€μžˆμ–΄μ•Ό ν•œλ‹€ λΌλŠ” μ›μΉ™μΈλ°μš”

μ½”λ“œλ‘œ λ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

function QuantityButton({ mode, onClick }: Props) {
  return (
    <CountButton onClick={onClick}>
      {mode === 'plus' ? <AiOutlinePlus /> : <AiOutlineMinus />}
    </CountButton>
  );
}

μœ„ μ½”λ“œλŠ” μ•„μ΄μ½˜μ΄ Plus, Minusμ•„μ΄μ½˜μœΌλ‘œ ν•œμ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

즉 ν™•μž₯에 λ‹«ν˜€μžˆλ‹€λŠ” 뜻이죠

μœ„ μ½”λ“œμ— ν™•μž₯성을 올리렀면 μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”??

 

μ•„μ΄μ½˜μ„ λ²„νŠΌ μ•ˆμ—μ„œ μ •μ˜ν•˜λŠ” 게 μ•„λ‹ˆλΌ λ°”κΉ₯μ—μ„œ μ£Όμž…λ°›μœΌλ©΄ λ©λ‹ˆλ‹€.

즉 μ•„λž˜μ™€ 같은 μ½”λ“œκ°€ λ˜λŠ” 것이죠.

function QuantityButton({ icon, onClick }: Props) {
  return <CountButton onClick={onClick}>{icon}</CountButton>;
}

즉 이제 μ•„μ΄μ½˜μ΄ 두 개둜 ν•œμ •λ˜μ–΄ μžˆλŠ”κ²Œ μ•„λ‹ˆλΌ, λ°”κΉ₯μ—μ„œ ν”„λ‘­μŠ€λ‘œ λ„˜κ²¨λ°›κΈ° λ•Œλ¬Έμ— λ‘κ°œλ‘œ ν•œμ •λœ 게 μ•„λ‹ˆλΌ μ—¬λŸ¬ 가지 μ•„μ΄μ½˜μ„ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 

λ˜ν•œ 이 μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ—μ„œ μ•„μ΄μ½˜μ„ λ°”κΏ€ ν•„μš”κ°€ μ—†μœΌλ―€λ‘œ ν™•μž₯μ—λŠ” μš©μ΄ν•˜κ³  λ³€κ²½μ—λŠ” λ‹«ν˜€μžˆλŠ” 원칙이 적용된 κ²ƒμž…λ‹ˆλ‹€!

 

- ISP (Interface Segregation Principle) μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙

μΈν„°νŽ˜μ΄μŠ€ 뢄리 μ›μΉ™μ΄λž€ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μΈν„°νŽ˜μ΄μŠ€μ— μ˜μ‘΄ν•˜λ©΄ μ•ˆ λœλ‹€λŠ” λ‚΄μš©μΈλ°μš”.

밑에 μ½”λ“œλ₯Ό μ˜ˆμ‹œλ‘œ λ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

밑에 μ²΄ν¬λ°•μŠ€ μ»΄ν¬λ„ŒνŠΈλŠ” 8개의 product νƒ€μž…μ„ λ°›μ•„μ„œ 그쀑 product.item_no, product.checked 속성 두 개만 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

interface CartProduct {
  item_no: number;
  item_name: string;
  detail_image_url: string;
  price: number;
  score: number;
  availableCoupon?: boolean;
  quantity: number;
  checked: boolean;
}

interface Props {
  product: CartProduct;
  onClick: () => void;
}

function CheckBox({ product, onClick }: Props) {
  const uniqueID = `checkbox-${product.item_no}`;
  return (
    <Block>
      <input
        id={uniqueID}
        type={'checkbox'}
        defaultChecked={product.checked}
        onClick={onClick}
      ></input>
      <label htmlFor={uniqueID}></label>
    </Block>
  );
}

즉 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μΈν„°νŽ˜μ΄μŠ€κ°€ λ„ˆλ¬΄ λ§Žμ€ 것이죠.

그러면 μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”? 

κΌ­ ν•„μš”ν•œ μΈν„°νŽ˜μ΄μŠ€λ§Œ λ„˜κ²¨λ°›μœΌλ©΄ λ©λ‹ˆλ‹€.

interface Props {
  item_no: number;
  checked: boolean;
  onClick: () => void;
}

function CheckBox({ item_no, onClick, checked }: Props) {
  const uniqueID = `checkbox-${item_no}`;
  return (
    <Block>
      <input
        id={uniqueID}
        type={'checkbox'}
        defaultChecked={checked}
        onClick={onClick}
      ></input>
      <label htmlFor={uniqueID}></label>
    </Block>
  );
}

이제 μœ„μ— μ²΄ν¬λ°•μŠ€λŠ” μ•ˆ μ“°λŠ” μΈν„°νŽ˜μ΄μŠ€ 없이 ν•„μš”ν•œ μΈν„°νŽ˜μ΄μŠ€λ§Œ μ˜μ‘΄ν•΄μ„œ μ‚¬μš©λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” μ œκ°€ SOLID 원칙을 곡뢀해 κ°€λ©΄μ„œ μ μš©ν•œ λ‚΄μš©μ„ λΈ”λ‘œκΉ… ν–ˆλŠ”λ°μš”.

μ΄λ²ˆμ— κ³΅λΆ€ν•˜λ©΄μ„œ μ–΄λ–»κ²Œ μ»΄ν¬λ„ŒνŠΈλ₯Ό 섀계해야 ν•˜λŠ”μ§€ 견문을 λ„“νž 수 μžˆλŠ” 쒋은 곡뢀 κΈ°νšŒμ˜€μŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ μ €μ²˜λŸΌ μ»΄ν¬λ„ŒνŠΈ 섀계에 λŒ€ν•΄μ„œ κ³ λ―Ό 쀑이신 뢄듀이라면 ν•œλ²ˆ SOLID원칙을 κ³΅λΆ€ν•΄κ°€λ©΄μ„œ μ μš©ν•΄ 봐도 쒋을 κ±° κ°™μŠ΅λ‹ˆλ‹€.