티스토리 뷰

 

imprun.dev Console 개발 가이드

새로운 세션에서도 일관된 개발을 위한 프론트엔드 아키텍처 및 코딩 규칙

DESIGN_GUIDELINES.md
0.01MB

목차

  1. 기술 스택
  2. 아키텍처 패턴
  3. 프로젝트 구조
  4. 페이지 작성 규칙
  5. 컴포넌트 작성 규칙
  6. Hooks 패턴
  7. 타입 시스템
  8. 스타일링 & 디자인 시스템
  9. API 서비스 패턴
  10. 새 기능 추가 가이드
  11. 코드 리뷰 체크리스트

기술 스택

Core

  • React 19: UI 라이브러리
  • react-router-dom v6: 클라이언트 사이드 라우팅
  • TypeScript: strictNullChecks: false, noImplicitAny: false
  • Vite: 빌드 도구

상태 관리

  • Zustand: 전역 상태 (auth, ui)
  • TanStack Query v5: 서버 상태 관리

UI

  • Tailwind CSS v4: 유틸리티 기반 스타일링
  • shadcn/ui: UI 컴포넌트 라이브러리
  • lucide-react: 아이콘

기타

  • Monaco Editor: 코드 에디터
  • React Hook Form + Zod: 폼 관리 및 검증
  • Recharts: 차트
  • Sonner: 토스트 알림
  • axios: HTTP 클라이언트

배포

  • nginx: 정적 파일 서빙
  • Docker: 컨테이너화
  • Kubernetes: 오케스트레이션

아키텍처 패턴

핵심 철학

"프레임워크는 UI 레이어에만 영향을 줘야 한다. 비즈니스 로직과 데이터 레이어는 독립적이어야 한다."

이 프로젝트는 프레임워크에 독립적인 아키텍처를 채택하여:

  • ✅ 프레임워크 마이그레이션 용이성 확보
  • ✅ 테스트 가능성 향상
  • ✅ 재사용성 극대화
  • ✅ 유지보수성 개선

1. Container/Presentational Pattern

데이터 로직과 UI 렌더링을 완전히 분리합니다.

// ✅ Container Component (데이터 + 로직)
export function ApplicationListContainer() {
  // 데이터 페칭 (Hook Layer)
  const { data: applications, isLoading } = useApplications()

  // 비즈니스 로직
  const activeApps = applications?.filter(app => app.phase !== 'Deleted')

  // Presentational Component에 데이터 전달
  return <ApplicationList applications={activeApps} isLoading={isLoading} />
}

// ✅ Presentational Component (렌더링만)
interface ApplicationListProps {
  applications: TApplicationDetail[]
  isLoading: boolean
}

export function ApplicationList({ applications, isLoading }: ApplicationListProps) {
  if (isLoading) return <Spinner />

  return (
    <div className="grid grid-cols-3 gap-4">
      {applications.map(app => (
        <ApplicationCard key={app.appid} app={app} />
      ))}
    </div>
  )
}

장점:

  • 테스트 용이: Presentational 컴포넌트는 props만 검증
  • 재사용성: 동일한 UI를 다른 데이터 소스에 연결 가능
  • 프레임워크 독립성: 렌더링 로직은 라우터 변경에 영향 없음

2. Layered Architecture (4계층)

┌──────────────────────────────────────┐
│  Component Layer                      │  ← UI 렌더링
│  - React 컴포넌트                      │
│  - 사용자 인터랙션                     │
│  - UI 상태 (useState)                 │
├──────────────────────────────────────┤
│  Hook Layer                           │  ← 비즈니스 로직
│  - TanStack Query (use-*.ts)          │
│  - 로직 Hook (use-*-control.ts)       │
│  - 서버 상태 관리                      │
├──────────────────────────────────────┤
│  Service Layer                        │  ← API 인터페이스
│  - *.service.ts                       │
│  - 도메인별 API 그룹화                 │
│  - 요청/응답 변환                      │
├──────────────────────────────────────┤
│  HTTP Client Layer                    │  ← 네트워크 통신
│  - axios (httpClient.ts)              │
│  - 인증 토큰 주입                      │
│  - 공통 에러 처리                      │
└──────────────────────────────────────┘

3. 실제 데이터 흐름

// 1️⃣ Component Layer
export function ApplicationCard({ app }: { app: TApplicationDetail }) {
  const { actions, state } = useApplicationControl(app)  // Hook 호출

  return (
    <Card>
      <Button onClick={actions.start} disabled={!state.canStart}>
        시작
      </Button>
    </Card>
  )
}

// 2️⃣ Hook Layer (use-application-control.ts)
export function useApplicationControl(app: TApplicationDetail) {
  const queryClient = useQueryClient()

  const start = async () => {
    await applicationService.start(app.appid)  // Service 호출
    queryClient.invalidateQueries({ queryKey: ['applications'] })
    toast.success("시작했습니다")
  }

  return {
    actions: { start },
    state: { canStart: app.phase === 'Stopped' }
  }
}

// 3️⃣ Service Layer (application.service.ts)
export const applicationService = {
  async start(appid: string): Promise<void> {
    await httpClient.post(`/v1/applications/${appid}/start`)  // HTTP 호출
  }
}

// 4️⃣ HTTP Client Layer (httpclient.ts)
const httpClient = axios.create({
  baseURL: env.API_URL,
  timeout: 10000,
})

httpClient.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

4. 프레임워크 독립성

프레임워크 변경 시 영향 범위:

  • 변경 필요 (5%): 라우팅, 페이지 구조
  • 변경 불필요 (95%): Hooks, Services, Components, Store

프로젝트 구조

frontend/
└── src/
    ├── pages/                   # 페이지 컴포넌트
    │   ├── Home.tsx
    │   ├── Applications.tsx
    │   ├── ApplicationDetail.tsx
    │   └── FunctionEditor.tsx
    │
    ├── routes/                  # 라우트 정의
    │   └── index.tsx           # react-router-dom 설정
    │
    ├── components/
    │   ├── layout/             # 레이아웃 컴포넌트
    │   │   ├── AppBar.tsx
    │   │   ├── MainNavBar.tsx
    │   │   └── AppNavBar.tsx
    │   ├── applications/       # 도메인별 컴포넌트
    │   ├── functions/
    │   ├── database/
    │   ├── triggers/
    │   ├── modals/            # 모달 컴포넌트
    │   └── ui/                # shadcn/ui 컴포넌트
    │
    ├── hooks/                  # TanStack Query + 로직 훅
    │   ├── use-applications.ts
    │   ├── use-functions.ts
    │   └── use-application-control.ts
    │
    ├── services/               # API 서비스
    │   ├── application.service.ts
    │   ├── function.service.ts
    │   └── database.service.ts
    │
    ├── store/                  # Zustand 스토어
    │   ├── auth.ts
    │   └── ui.ts
    │
    ├── types/                  # 도메인별 타입
    │   ├── application.ts
    │   ├── function.ts
    │   ├── common.ts
    │   └── index.ts           # ⚠️ 필수 re-export
    │
    └── lib/                    # 유틸리티
        ├── httpclient.ts
        └── env.ts

레이아웃 계층 구조

┌──────────────────────────────────────────┐
│ AppBar (전역)                             │  ← 모든 페이지
│ Logo + Breadcrumb    [User Menu]        │
├──────────────────────────────────────────┤
│ MainNavBar (메인 영역)                    │  ← /projects, /billing
│ 프로젝트 | 빌링 | 설정                     │
├──────────────────────────────────────────┤
│ AppNavBar (Application 영역)              │  ← /applications/:appid/*
│ 개요 | Functions | Database | ...       │
├──────────────────────────────────────────┤
│             Page Content                  │
└──────────────────────────────────────────┘

페이지 작성 규칙

React Router DOM 기반 페이지

// src/routes/index.tsx
import { createBrowserRouter } from 'react-router-dom'

export const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: 'applications',
        children: [
          { index: true, element: <ApplicationList /> },
          {
            path: ':appid',
            element: <ApplicationLayout />,
            children: [
              { index: true, element: <ApplicationOverview /> },
              { path: 'functions', element: <FunctionList /> },
              { path: 'functions/:name/edit', element: <FunctionEditor /> },
            ],
          },
        ],
      },
    ],
  },
])

페이지 컴포넌트 패턴

// src/pages/ApplicationList.tsx
import { useApplications } from '@/hooks/use-applications'
import { ApplicationCard } from '@/components/applications/ApplicationCard'
import { CreateApplicationModal } from '@/components/modals/CreateApplicationModal'

export function ApplicationList() {
  // 1. 데이터 페칭 (Hook Layer)
  const { data: applications, isLoading } = useApplications()

  // 2. 로딩 처리
  if (isLoading) return <Spinner />

  // 3. 페이지 구조 정의
  return (
    <div className="p-6 h-full overflow-auto">
      <div className="max-w-7xl mx-auto flex flex-col gap-6">
        {/* 헤더 */}
        <div className="flex items-center justify-between">
          <h1 className="text-2xl font-bold">Applications</h1>
          <CreateApplicationModal />
        </div>

        {/* 메인 콘텐츠 */}
        {applications.length === 0 ? (
          <EmptyState />
        ) : (
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            {applications.map(app => (
              <ApplicationCard key={app.appid} app={app} />
            ))}
          </div>
        )}
      </div>
    </div>
  )
}

페이지 작성 원칙

✅ 해야 할 것:

  • Hook으로 데이터 페칭
  • 컴포넌트 조합으로 UI 구성
  • 페이지 레벨 레이아웃 정의

❌ 하지 말아야 할 것:

  • 페이지에 비즈니스 로직 직접 작성 (Hook으로 분리)
  • 인라인 API 호출 (Service Layer 사용)
  • 복잡한 상태 관리 (Hook으로 추상화)

컴포넌트 작성 규칙

컴포넌트 분리 기준

다음 조건을 만족하면 별도 컴포넌트로 분리:

  1. 재사용 가능: 2곳 이상에서 사용
  2. 복잡도: 100줄 이상
  3. 독립 로직: 자체 상태 관리 필요
  4. Client 인터랙션: 이벤트 핸들러 필요

컴포넌트 배치 규칙

src/components/
├── layout/              # 레이아웃 (AppBar, NavBar)
├── applications/        # Application 도메인
├── functions/           # Function 도메인
├── database/            # Database 도메인
├── triggers/            # Trigger 도메인
├── modals/             # 모든 모달
└── ui/                 # shadcn/ui 컴포넌트

컴포넌트 작성 패턴

// src/components/applications/ApplicationCard.tsx
import { Card, CardHeader, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { useApplicationControl } from "@/hooks/use-application-control"
import type { TApplicationDetail } from "@/types"

interface ApplicationCardProps {
  app: TApplicationDetail
}

export function ApplicationCard({ app }: ApplicationCardProps) {
  // Hook으로 로직 추상화
  const { actions, state } = useApplicationControl(app)

  return (
    <Card>
      <CardHeader>
        <h3 className="text-lg font-semibold">{app.name}</h3>
        <StatusBadge phase={app.phase} />
      </CardHeader>
      <CardContent>
        <div className="flex gap-2">
          <Button
            onClick={actions.start}
            disabled={!state.canStart || state.isActioning}
            size="sm"
          >
            시작
          </Button>
          <Button
            onClick={actions.stop}
            disabled={!state.canStop || state.isActioning}
            size="sm"
            variant="secondary"
          >
            정지
          </Button>
        </div>
      </CardContent>
    </Card>
  )
}

Hooks 패턴

1. Data Hooks (TanStack Query)

// src/hooks/use-applications.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { applicationService } from "@/services/application.service"
import type { TApplicationDetail, TCreateApplicationDto } from "@/types"

// 조회
export function useApplications() {
  return useQuery({
    queryKey: ["applications"],
    queryFn: () => applicationService.getApplications(),
  })
}

export function useApplication(appid: string) {
  return useQuery({
    queryKey: ["applications", appid],
    queryFn: () => applicationService.getApplication(appid),
    enabled: !!appid,
  })
}

// 생성
export function useCreateApplication() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (data: TCreateApplicationDto) =>
      applicationService.create(data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["applications"] })
      toast.success("생성되었습니다")
    },
    onError: (error: any) => {
      toast.error(error.response?.data?.message || "생성 실패")
    },
  })
}

2. Logic Hooks (비즈니스 로직)

// src/hooks/use-application-control.ts
import { useState } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { applicationService } from "@/services/application.service"
import { ApplicationPhase } from "@/types"
import { toast } from "sonner"

export function useApplicationControl(app: TApplicationDetail | null) {
  const [isActioning, setIsActioning] = useState(false)
  const queryClient = useQueryClient()

  // 상태 계산
  const canStart = app?.phase === ApplicationPhase.Stopped
  const canStop = app?.phase === ApplicationPhase.Started

  // 액션 정의
  const start = async () => {
    if (!app) return
    setIsActioning(true)
    try {
      await applicationService.start(app.appid)
      queryClient.invalidateQueries({ queryKey: ["applications"] })
      toast.success("시작했습니다")
    } catch (error: any) {
      toast.error(error.response?.data?.message || "시작 실패")
    } finally {
      setIsActioning(false)
    }
  }

  const stop = async () => {
    // 동일한 패턴
  }

  return {
    actions: { start, stop },
    state: { canStart, canStop, isActioning },
  }
}

Hooks 작성 원칙

  • Data Hooks: TanStack Query 사용, 캐시 관리
  • Logic Hooks: 재사용 가능한 비즈니스 로직
  • 명명 규칙: use-도메인명.ts (Data), use-도메인명-동사.ts (Logic)

타입 시스템

1. 도메인별 타입 분리

src/types/
├── application.ts    # Application 도메인
├── function.ts       # Function 도메인
├── database.ts       # Database 도메인
├── trigger.ts        # Trigger 도메인
├── common.ts         # 공통 타입
└── index.ts          # ⚠️ 필수 re-export

2. 타입 정의 예시

// src/types/application.ts
export enum ApplicationPhase {
  Starting = "Starting",
  Started = "Started",
  Stopping = "Stopping",
  Stopped = "Stopped",
}

export type TApplicationDetail = {
  _id: string
  appid: string
  name: string
  phase: string  // ApplicationPhase
  state: string
  region: string
  createdAt: string
  updatedAt: string
}

export type TCreateApplicationDto = {
  name: string
  region: string
}

3. 중앙 re-export (필수!)

// src/types/index.ts
export * from "./application"
export * from "./function"
export * from "./database"
export * from "./trigger"
export * from "./common"

4. 타입 Import 규칙

// ✅ 중앙 re-export 사용
import { TApplicationDetail, ApplicationPhase } from "@/types"

// ❌ 개별 파일 직접 import 금지
import { TApplicationDetail } from "@/types/application"

5. State vs Phase

  • State: 사용자가 설정한 목표 상태 (Running, Stopped)
  • Phase: 실제 시스템 상태 (Starting, Started, Stopping, Stopped)
// ✅ Phase 기반 UI 제어
const canStart = app.phase === ApplicationPhase.Stopped
const canStop = app.phase === ApplicationPhase.Started
const isTransitioning = ["Starting", "Stopping"].includes(app.phase)

6. 주의사항

  • .ts 사용 (.d.ts 아님): enum은 런타임 코드
  • TypeScript 설정: strictNullChecks: false → null/undefined 명시적 체크 필수

스타일링 & 디자인 시스템

중요: 상세한 UI/UX 가이드라인은 DESIGN_GUIDELINES.md 참조

디자인 시스템에는 다음이 포함되어 있습니다:

  • ✨ 일관된 타이포그래피 (그라디언트 헤더, semantic colors)
  • 🎨 세련된 카드 디자인 (border-2, hover 효과, 그라디언트 아이콘)
  • 🎯 인터랙티브 애니메이션 (duration-300, -translate-y-1)
  • 📱 반응형 레이아웃 (grid, gap-8, md:, lg:)
  • 🌙 다크모드 자동 지원 (semantic colors 사용)

아래는 기본 스타일링 규칙입니다. 새 페이지/컴포넌트 작성 시 반드시 DESIGN_GUIDELINES.md를 함께 참조하세요.

1. Tailwind 기본 패턴

// ✅ flex + gap 사용
<div className="flex flex-col gap-6">
  <div className="flex items-center gap-2">
    ...
  </div>
</div>

// ❌ margin 사용 금지
<div className="space-y-4">  // X
<div className="mb-4">       // X

2. 페이지 레이아웃 패턴

<div className="p-6 h-full overflow-auto">
  <div className="max-w-7xl mx-auto flex flex-col gap-6">
    {/* 헤더 */}
    <div className="flex items-center justify-between">
      <h1 className="text-2xl font-bold">Title</h1>
      <Button>Action</Button>
    </div>

    {/* 콘텐츠 */}
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      {items.map(item => <Card key={item.id} />)}
    </div>
  </div>
</div>

3. 색상 시스템 (Semantic Colors)

// ✅ Tailwind semantic colors 사용
text-foreground
bg-background
border-border
bg-primary text-primary-foreground
bg-muted text-muted-foreground

// ❌ 하드코딩된 색상 금지
text-gray-900
bg-white
border-gray-200

이유: 다크모드 자동 지원

4. 아이콘 사용

import { Settings, Info, AlertCircle } from "lucide-react"

// ✅ size prop 사용
<Settings size={16} />  // 기본 (대부분)
<Info size={14} />      // 버튼 내부
<Settings size={20} />  // 헤더

// ❌ className 크기 금지
<Settings className="h-4 w-4" />

5. 반응형 디자인

// ✅ Tailwind breakpoints 사용
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {/* 모바일: 1열, 태블릿: 2열, 데스크톱: 3열 */}
</div>

API 서비스 패턴

Service Layer 구조

// src/services/application.service.ts
import { httpClient } from "@/lib/httpclient"
import type {
  TApplicationDetail,
  TCreateApplicationDto,
  TUpdateApplicationDto,
} from "@/types"

export const applicationService = {
  // 조회
  async getApplications(): Promise<TApplicationDetail[]> {
    return await httpClient.get("/v1/applications")
  },

  async getApplication(appid: string): Promise<TApplicationDetail> {
    return await httpClient.get(`/v1/applications/${appid}`)
  },

  // 생성
  async create(dto: TCreateApplicationDto): Promise<TApplicationDetail> {
    return await httpClient.post("/v1/applications", dto)
  },

  // 수정
  async update(
    appid: string,
    dto: TUpdateApplicationDto
  ): Promise<TApplicationDetail> {
    return await httpClient.patch(`/v1/applications/${appid}`, dto)
  },

  // 삭제
  async delete(appid: string): Promise<void> {
    await httpClient.delete(`/v1/applications/${appid}`)
  },

  // 액션
  async start(appid: string): Promise<void> {
    await httpClient.post(`/v1/applications/${appid}/start`)
  },

  async stop(appid: string): Promise<void> {
    await httpClient.post(`/v1/applications/${appid}/stop`)
  },
}

HTTP Client 설정

// src/lib/httpclient.ts
import axios from "axios"
import { useAuthStore } from "@/store/auth"
import { env } from "./env"

const httpClient = axios.create({
  baseURL: env.API_URL,
  timeout: 10000,
})

// 요청 인터셉터: 토큰 주입
httpClient.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// 응답 인터셉터: 공통 에러 처리
httpClient.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout()
      window.location.href = "/login"
    }
    return Promise.reject(error)
  }
)

export default httpClient

Service 작성 원칙

  • 도메인별 분리: application.service.ts, function.service.ts
  • 명명 규칙: 도메인명.service.ts
  • 타입 명시: 요청/응답 타입 명확히
  • 에러 처리: HTTP Client에서 일괄 처리

새 기능 추가 가이드

1. 라우트 추가

// src/routes/index.tsx
{
  path: ':appid',
  element: <ApplicationLayout />,
  children: [
    // ... 기존 라우트
    { path: '새기능', element: <새기능Page /> },
  ],
}

2. NavBar 업데이트

// src/components/layout/AppNavBar.tsx
const navItems = [
  // ... 기존 항목
  { label: "새기능", href: "새기능", icon: YourIcon },
]

3. 타입 정의

// src/types/새기능.ts
export type T새기능 = {
  id: string
  name: string
  // ...
}

export type TCreate새기능Dto = {
  name: string
  // ...
}

// src/types/index.ts에 추가
export * from "./새기능"

4. Service 작성

// src/services/새기능.service.ts
import { httpClient } from "@/lib/httpclient"
import type { T새기능, TCreate새기능Dto } from "@/types"

export const 새기능Service = {
  async getList(appid: string): Promise<T새기능[]> {
    return await httpClient.get(`/v1/apps/${appid}/새기능`)
  },

  async create(appid: string, dto: TCreate새기능Dto): Promise<T새기능> {
    return await httpClient.post(`/v1/apps/${appid}/새기능`, dto)
  },
}

5. Hook 작성

// src/hooks/use-새기능.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { 새기능Service } from "@/services/새기능.service"

export function use새기능s(appid: string) {
  return useQuery({
    queryKey: ["새기능", appid],
    queryFn: () => 새기능Service.getList(appid),
    enabled: !!appid,
  })
}

export function useCreate새기능(appid: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (data: TCreate새기능Dto) =>
      새기능Service.create(appid, data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["새기능", appid] })
    },
  })
}

6. 컴포넌트 작성

// src/components/새기능/새기능List.tsx
import type { T새기능 } from "@/types"

interface 새기능ListProps {
  items: T새기능[]
  appid: string
}

export function 새기능List({ items, appid }: 새기능ListProps) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {items.map(item => (
        <새기능Card key={item.id} item={item} appid={appid} />
      ))}
    </div>
  )
}

7. 페이지 작성

// src/pages/새기능Page.tsx
import { useParams } from "react-router-dom"
import { use새기능s } from "@/hooks/use-새기능"
import { 새기능List } from "@/components/새기능/새기능List"
import { Create새기능Modal } from "@/components/modals/Create새기능Modal"

export function 새기능Page() {
  const { appid } = useParams<{ appid: string }>()
  const { data: items, isLoading } = use새기능s(appid!)

  if (isLoading) return <Spinner />

  return (
    <div className="p-6 h-full overflow-auto">
      <div className="max-w-7xl mx-auto flex flex-col gap-6">
        <div className="flex items-center justify-between">
          <h1 className="text-2xl font-bold">새기능</h1>
          <Create새기능Modal appid={appid!} />
        </div>
        <새기능List items={items} appid={appid!} />
      </div>
    </div>
  )
}

코드 리뷰 체크리스트

아키텍처

  • Container/Presentational 패턴 준수?
  • 4계층 구조 유지? (Component → Hook → Service → HTTP Client)
  • 비즈니스 로직이 Hook Layer에 있는가?
  • 페이지 컴포넌트가 단순 래퍼가 아닌가?

컴포넌트

  • 컴포넌트가 도메인별 폴더에 위치?
  • Presentational 컴포넌트가 props만 받는가?
  • 100줄 이상이면 분리 검토했는가?

Hooks

  • Data Hook은 TanStack Query 사용?
  • Logic Hook은 재사용 가능한가?
  • Mutation 성공 시 Query Invalidation?

타입

  • 타입이 도메인별 파일에 정의?
  • types/index.ts에서 re-export?
  • 중앙 re-export로 import?

스타일링

  • flex + gap 사용, margin 회피?
  • Semantic colors 사용?
  • 아이콘에 size prop 사용?
  • 반응형 디자인 적용?

API

  • Service Layer 사용?
  • 에러 처리가 적절한가?
  • Toast 알림 제공?

일반

  • TypeScript 타입 명시?
  • null/undefined 명시적 체크? (strictNullChecks: false)
  • Phase 기반 UI 제어?

참고 자료

코드 예시


마지막 원칙

"일관성이 완벽함보다 중요하다. 기존 코드 패턴을 따르라."

새로운 세션에서 작업 시:

  1. 기존 유사 기능 찾기
  2. 패턴 파악 후 동일하게 적용
  3. 의문점은 이 가이드 참조
  4. 가이드에 없으면 기존 코드 우선
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함