1. 우리의 리액트 기본 코드

//src/Payment.tsx…
export const Payment = ({ amount }: { amount: number }) => {
  const [paymentMethods, setPaymentMethods] = useState<LocalPaymentMethod[]>(
    []
  );

  useEffect(() => {
    const fetchPaymentMethods = async () => {
      const url = "<https://online-ordering.com/api/payment-methods>";

      const response = await fetch(url);
      const methods: RemotePaymentMethod[] = await response.json();

      if (methods.length > 0) {
        const extended: LocalPaymentMethod[] = methods.map((method) => ({
          provider: method.name,
          label: `Pay with ${method.name}`,
        }));
        extended.push({ provider: "cash", label: "Pay in cash" });
        setPaymentMethods(extended);
      } else {
        setPaymentMethods([]);
      }
    };

    fetchPaymentMethods();
  }, []);

  return (
    <div>
      <h3>Payment</h3>
      <div>
        {paymentMethods.map((method) => (
          <label key={method.provider}>
            <input
              type="radio"
              name="payment"
              value={method.provider}
              defaultChecked={method.provider === "cash"}
            />
            <span>{method.label}</span>
          </label>
        ))}
      </div>
      <button>${amount}</button>
    </div>
  );
};

문제점

  1. Payment에서 너무 많은 일을 하고 있다.
    1. 백엔드 데이터 fetch
    2. ViewModel로 만들기
    3. 클라이언트 state에 매핑하기
    4. 클라이언트 상태 가지기
    5. 반복문으로 개별 항목 렌더링하기

2. 뷰와 관련된 코드와 뷰와 관련되지 않은 코드의 분리

커스텀훅을 통해서, 컴포넌트 내부에서 상태를 갖지 않게 할 수 있다. usePaymentMethods 라는 커스텀훅을 만든다. 이렇게만 하더라도 View에 필요한 정보들만 컴포넌트에 가지게 되었다. (Payment의 로직 분리)

// src/Payment.tsx…

export const Payment = ({ amount }: { amount: number }) => {
	// 이제부터 커스텀 훅 내부에서 상태를 가지게 되었다.
  const { paymentMethods } = usePaymentMethods();

  return (
    <div>
      <h3>Payment</h3>
      <div>
        {paymentMethods.map((method) => (
          <label key={method.provider}>
            <input
              type="radio"
              name="payment"
              value={method.provider}
              defaultChecked={method.provider === "cash"}
            />
            <span>{method.label}</span>
          </label>
        ))}
      </div>
      <button>${amount}</button>
    </div>
  );
};

문제점

  1. 아직 한발 남았다..
    1. 반복문을 순회하면서 컴포넌트를 렌더링하는 로직이 남아있다.

하위 컴포넌트 추출을 통한 뷰 분할

하위 컴포넌트를 분리했다. 여기서는 PresentionalComponent로 분할. 이렇게 하면 다음과 같은 이점을 얻는다.

<aside> 👉🏼 만약 컴포넌트를 순수 함수, 즉 입력이 주어지면 출력이 확실한 함수로 만들 수 있다면 테스트를 작성하고 코드를 이해하며 다른 곳에서 컴포넌트를 재사용하는 데 많은 도움이 될 것입니다. 결국 컴포넌트가 작을수록 재사용될 가능성이 높아집니다.

</aside>

/// src/Payment.tsx…

export const Payment = ({ amount }: { amount: number }) => {
  const { paymentMethods } = usePaymentMethods();

  return (
    <div>
      <h3>Payment</h3>
      {/* 상태가 없는 순수한 함수로 분리했다. */}
      <PaymentMethods paymentMethods={paymentMethods} />
      <button>${amount}</button>
    </div>
  );
};

PaymentMethods는 상태가 없는 순수 함수(순수 컴포넌트)라는 점에 유의하세요. 단순히 문자열을 포맷팅하는 함수입니다.