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);