Skip to content

TeamSparta Union Packages 가이드라인

개요

  • 이름: TeamSparta Union Monorepo
  • 설명: React + TypeScript 기반의 공용 유틸리티 패키지 모음으로, 커스텀 훅, 유틸리티 함수, 타입 정의, A/B 테스트 통합을 제공합니다.
  • 철학: 코드 재사용성 극대화, 타입 안전성 확보, 일관된 패턴 제공을 핵심 원칙으로 합니다.

I. 패키지 선택 가이드

@teamsparta/react

React 커스텀 훅과 선언적 유틸리티 컴포넌트를 제공합니다.

  • : useBoolean, useDebounce, useThrottle, useTimer, useFunnel, useMediaQuery 등 19개
  • 컴포넌트: When, SwitchCase, Separated, Spacer, Delay, InView, Suspense, Portal 8개
  • 유틸리티: createContext, mergeRefs

@teamsparta/utils

프레임워크에 종속되지 않는 순수 유틸리티 함수를 제공합니다.

  • string: snakeCase
  • array: isEmptyArray, isLastIndex, getLastItem, unique
  • date: calculateAge, getDateDiff, getTimePastText, getNewDate, fixServerDate, isBeforeOrEqual, isAfterOrEqual
  • math: clamp, random, randomInt, sumBy
  • number: formatPhoneNumber
  • predicate: isNil, isNotNil, isNull, isUndefined, isBoolean, isNumber, isString, isObject, isArray, isFunction, isDate, isClient, isServer
  • function: noop
  • device: isMobileDevice, isMac
  • promise: delay

@teamsparta/types

공용 TypeScript 유틸리티 타입을 제공합니다.

  • PartialRecord: 모든 키가 선택적인 Record 타입

@teamsparta/hackle

Hackle A/B 테스트 및 원격 설정을 Suspense 패턴으로 통합합니다.

  • useSuspenseVariation: A/B 테스트 variation 훅
  • SuspenseVariation: 렌더 프롭 variation 컴포넌트
  • useSuspenseRemoteConfig: 원격 설정 훅
  • SuspenseRemoteConfig: 렌더 프롭 원격 설정 컴포넌트

@teamsparta/assert (Deprecated)

런타임 assertion 유틸리티입니다. 향후 @teamsparta/utils로 통합 예정입니다.


II. Import 규칙

기본 원칙

각 패키지에서 Named Import를 사용합니다.

typescript
// Good
import { useBoolean, useDebounce } from '@teamsparta/react';
import { isNil, clamp, snakeCase } from '@teamsparta/utils';
import type { PartialRecord } from '@teamsparta/types';
import { useSuspenseVariation } from '@teamsparta/hackle';

// Bad - 서브 경로 직접 import
import { useBoolean } from '@teamsparta/react/hooks/useBoolean';

타입 Import

타입은 반드시 type 키워드와 함께 import합니다.

typescript
import type { PartialRecord } from '@teamsparta/types';
import { useSuspenseVariation, type Variation } from '@teamsparta/hackle';

III. Hook 사용 베스트 프랙티스

1. usePreserveCallback으로 콜백 안정화

외부에서 전달받은 콜백이 useEffect 의존성에 포함될 때 사용합니다.

tsx
// Good - 콜백 레퍼런스 안정화
function MyComponent({ onUpdate }: { onUpdate: () => void }) {
  const stableCallback = usePreserveCallback(onUpdate);

  useEffect(() => {
    stableCallback();
  }, [stableCallback]); // 안전한 의존성
}

2. useDebounce/useThrottle 적절한 선택

  • useDebounce: 마지막 입력 후 일정 시간 대기 (검색 입력, 자동 저장)
  • useThrottle: 일정 간격으로 제한하여 실행 (스크롤 이벤트, 리사이즈)
tsx
// 검색: 입력 완료 후 500ms 대기
const debouncedQuery = useDebounce(searchQuery, 500);

// 스크롤: 최대 1초에 1번만 실행
const throttledScroll = useThrottle(scrollPosition, 1000);

3. useTimer vs useStopwatch

  • useTimer: 감소하는 카운트다운 (시험 시간, 인증 코드 만료)
  • useStopwatch: 증가하는 경과 시간 (학습 시간 측정)

4. useFunnel로 단계별 UI

tsx
const [Funnel, setStep] = useFunnel({
  steps: ['info', 'payment', 'complete'] as const,
});

return (
  <Funnel>
    <Funnel.Step name="info">
      <InfoForm onNext={() => setStep('payment')} />
    </Funnel.Step>
    <Funnel.Step name="payment">
      <PaymentForm onNext={() => setStep('complete')} />
    </Funnel.Step>
    <Funnel.Step name="complete">
      <CompletePage />
    </Funnel.Step>
  </Funnel>
);

IV. 유틸 함수 패턴

1. Type Guard 활용

@teamsparta/utils의 predicate 함수들은 TypeScript type narrowing을 지원합니다.

typescript
// Good - filter에서 타입 가드 활용
const validItems = items.filter(isNotNil);
// 타입: NonNullable<T>[]

// Good - 조건부 로직에서
if (isNil(user)) {
  // user: null | undefined
  return redirect('/login');
}
// user: User (narrowed)

2. 날짜 처리 패턴

typescript
// 서버 날짜 보정
const fixedDate = fixServerDate(serverResponse.createdAt);

// 경과 시간 표시
const timeText = getTimePastText(fixedDate); // "3시간 전"

// 날짜 비교
if (isBeforeOrEqual(startDate, endDate)) {
  // 유효한 날짜 범위
}

// 불변 날짜 변환
const nextWeek = getNewDate(today, (d) => d.setDate(d.getDate() + 7));

3. 수학 유틸리티 패턴

typescript
// 값 범위 제한
const progress = clamp(rawProgress, 0, 100);

// 배열 합산
const total = sumBy(cartItems, (item) => item.price * item.quantity);

V. Hackle 통합 패턴

1. Suspense + ErrorBoundary 조합

Hackle 훅들은 Suspense를 throw하므로 반드시 Suspense와 ErrorBoundary로 감싸야 합니다.

tsx
<ErrorBoundary fallback={<ErrorFallback />}>
  <Suspense fallback={<Loading />}>
    <ABTestContent />
  </Suspense>
</ErrorBoundary>

2. A/B 테스트 패턴

tsx
function PricingSection() {
  const { variation } = useSuspenseVariation({ experimentKey: 101 });

  switch (variation) {
    case 'A': return <OriginalPricing />;
    case 'B': return <NewPricing />;
    default: return <OriginalPricing />;
  }
}

3. 원격 설정 패턴

tsx
function Banner() {
  const { data } = useSuspenseRemoteConfig<{ text: string; color: string }>({
    key: 'main-banner',
    defaultValue: { text: '기본 배너', color: '#000' },
  });

  return <div style={{ color: data.color }}>{data.text}</div>;
}

VI. 선언적 컴포넌트 패턴

조건부 렌더링

tsx
// 삼항 연산자 대신 When 사용
<When condition={isLoggedIn} fallback={<LoginButton />}>
  <UserProfile />
</When>

// switch 대신 SwitchCase 사용
<SwitchCase
  value={status}
  cases={{
    loading: <Spinner />,
    success: <Content />,
    error: <ErrorMessage />,
  }}
/>

리스트 구분자

tsx
<Separated with={<Divider />}>
  {sections.map(section => <Section key={section.id} {...section} />)}
</Separated>

VII. 흔한 실수

1. assert 패키지 혼동

typescript
// ⚠️ @teamsparta/assert는 deprecated
// 현재 @teamsparta/utils에는 assert 함수가 아직 없으므로,
// 기존 코드에서는 @teamsparta/assert를 한시적으로 사용 가능
// 신규 코드에서는 직접 조건 검증 또는 utils 통합 시까지 대기 권장
import { assert } from '@teamsparta/assert';

2. SSR 환경에서 브라우저 API 직접 접근

typescript
// Bad
const width = window.innerWidth;

// Good - 클라이언트 확인 후 접근
import { isClient } from '@teamsparta/utils';
const width = isClient() ? window.innerWidth : 0;

// Good - useIsClient 훅 사용
const isClientSide = useIsClient();

3. useMediaQuery SSR 기본값 누락

tsx
// Bad - SSR에서 hydration mismatch 발생 가능
const isMobile = useMediaQuery('(max-width: 768px)');

// Good - SSR 기본값 제공
const isMobile = useMediaQuery('(max-width: 768px)', false);

4. Hackle 훅을 Suspense 없이 사용

tsx
// Bad - Suspense 없이 사용하면 unhandled promise rejection
function Component() {
  const { variation } = useSuspenseVariation({ experimentKey: 1 });
  return <div>{variation}</div>;
}

// Good - 반드시 Suspense로 감싸기
<Suspense fallback={<Loading />}>
  <Component />
</Suspense>

5. fixServerDate를 일반 UTC 날짜에 사용

typescript
// Bad - 이미 정상적인 UTC 날짜를 fixServerDate로 변환
const date = fixServerDate('2024-01-01T00:00:00Z'); // 잘못된 시간이 됨

// Good - fixServerDate는 KST로 저장되었지만 UTC+0으로 표현된 서버 날짜에만 사용
const fixedDate = fixServerDate(serverData.createdAt);