본문 바로가기
C.W.K.
Stream
Lesson 05 of 06 · published

Action을 감싸고 이어 붙이는 방법

~11 min · actions, composition, patterns

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
액션도 평범한 함수라서 감싸고 이어 붙일 수 있어. 로깅, 확인, 후속 작업을 읽기 쉬운 조합으로 만드는 패턴을 익혀.

공통 동작으로 Action을 감싸

모든 저장 작업의 시작과 성공, 실패를 기록해야 한다면 각 Action에 같은 코드를 복사하지 마. Action 함수를 인자로 받아 로깅을 더한 새 함수를 돌려주는 wrapper를 만들 수 있어. 원래 반환 값과 오류 계약을 유지하면 사용하는 컴포넌트는 차이를 알 필요가 없어.

여러 작업을 하나의 제출 흐름으로 이어

초안을 저장한 뒤 알림을 보내야 한다면 하나의 Action 안에서 순서대로 await해. React에는 여전히 하나의 제출로 보이므로 pending 상태가 전체 작업이 끝날 때까지 유지돼. 두 번째 작업이 실패했을 때 무엇을 되돌릴지도 이 함수의 계약에서 정해.

반복되는 연결은 커스텀 훅으로 묶어

유효성 검사, 로깅, 낙관적 업데이트, 오류 표시가 여러 컴포넌트에서 반복된다면 커스텀 훅이 Action과 관련 상태를 함께 제공하게 해. 화면은 입력과 결과를 렌더링하는 데 집중할 수 있어.

Action은 특별한 마법이 아니라 비동기 함수야. 로깅, 재시도, rate limit을 평범한 함수에 조합하듯 같은 원칙으로 구성해.

고차 Action으로 계약을 보존해

withLogging(name, action)은 원래 Action과 같은 (previousState, formData) => Promise<State> 시그니처를 받아 같은 시그니처를 돌려줘. 실행 전에는 start를 기록하고, 성공하면 success와 원래 결과를 반환하며, 실패하면 failure를 기록한 뒤 같은 오류를 다시 던져.

saveAndNotify로 순서를 드러내

제출 하나가 초안 저장과 알림 전송을 모두 해야 한다면 먼저 const draft = await saveDraft(formData)를 실행하고, 그 ID로 await sendNotification(draft.id)를 호출해. 마지막에 draft를 반환하면 React의 pending 상태는 두 작업 전체를 덮어.

확인과 재시도도 같은 방식으로 조합해

사용자 확인이 필요한 삭제 Action은 확인을 통과한 뒤 원래 Action을 호출하는 wrapper로 만들 수 있어. 네트워크 재시도와 rate limit도 입력과 반환 계약을 보존하는 함수로 둘 수 있어. 다만 한 번만 쓰는 Action까지 미리 추상화하지 말고 두 번째 반복이 생길 때 공통 모양을 꺼내.

커스텀 훅은 UI 연결을 재사용해

Wrapper가 함수의 동작을 조합한다면 useSubscribeForm 같은 훅은 useActionState, validation, optimistic state를 컴포넌트에 연결해. Action 함수와 hook의 책임을 나누면 테스트할 순수 함수와 렌더링 계약이 모두 선명해져.

Code

재사용 가능한 useFormAction 훅·tsx
import { useActionState, useOptimistic } from "react";

type ActionFn<S> = (state: S, formData: FormData) => Promise<S>;

export function useFormAction<S>(
  action: ActionFn<S>,
  initial: S,
  optimisticUpdater?: (current: S, fd: FormData) => S
) {
  const [state, formAction, isPending] = useActionState(action, initial);
  const [optimistic, addOptimistic] = useOptimistic(
    state,
    optimisticUpdater ?? ((s) => s)
  );

  const wrappedAction = async (fd: FormData) => {
    if (optimisticUpdater) addOptimistic(fd as unknown as Parameters<typeof addOptimistic>[0]);
    await formAction(fd);
  };

  return { state: optimistic, action: wrappedAction, isPending };
}

// 호출자 — action + initial + 선택적 낙관적 업데이트r 선언, 깔끔한 번들 받음.
// const { state, action, isPending } = useFormAction(saveTitle, { title: '' }, ...);

External links

Exercise

Subscribe Action을 원래 시그니처 그대로 받는 withLogging으로 감싸고 각 제출에서 start와 success 또는 failure가 순서대로 기록되는지 확인해. 이어서 saveDraft 뒤 sendNotification을 실행하는 saveAndNotify를 만들고, 저장 성공 뒤 알림만 실패하는 부분 성공을 상태에 어떻게 표시하고 재시도할지 정해. 마지막으로 useActionState 연결을 useSubscribeForm 훅으로 옮겨 컴포넌트에는 렌더링 계약만 남겨.
Hint
래퍼는 (previousState, formData) => Promise<State> 입력과 반환, 오류 의미를 보존해야 해. 성공, 첫 단계 실패, 두 번째 단계 실패 세 경로의 log와 화면 상태를 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.