Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "code-skills",
|
||||||
|
"description": "code skills with claude",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "hynu",
|
||||||
|
"email": "khw1031@gmail.com"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
]
|
||||||
|
}
|
||||||
49
plugin.lock.json
Normal file
49
plugin.lock.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:khw1031/claude-marketplace:plugins/code-skills",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "737a96141b7e3e614db81873f0315085e7e9d7a6",
|
||||||
|
"treeHash": "251fc79965b5e16602a05aecce7853a5eb752bc5a1e642915967c2e419042314",
|
||||||
|
"generatedAt": "2025-11-28T10:19:29.947694Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "code-skills",
|
||||||
|
"description": "code skills with claude",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "546ac5a4ece241a3848a5d028340250bb2f1b4671c41a538668670d78d2cc67f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "8411918a1753761fb3d02c1ea218d34e6761afc4e9efae71fa66d3f3a01d02fb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/review-react/SKILL.md",
|
||||||
|
"sha256": "4cc5f92f0abc2929b8cc302e39f739d343d9e7f86badafe4c74ff0a83bd9964c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/review-react/references/typescript.md",
|
||||||
|
"sha256": "c7e375937c423fa3223aa01ac01f59b2e47d29ced34fd222395dace5b7785aef"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "251fc79965b5e16602a05aecce7853a5eb752bc5a1e642915967c2e419042314"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
190
skills/review-react/SKILL.md
Normal file
190
skills/review-react/SKILL.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
---
|
||||||
|
name: review-react
|
||||||
|
description: Expert-level frontend code review specialist for production-grade TypeScript/React applications. Use this skill when reviewing pull requests, performing code audits, or analyzing frontend codebases for type safety, performance, security, and maintainability issues. Focuses on React/TypeScript stack with emphasis on runtime safety and production readiness.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Frontend Code Review Expert
|
||||||
|
|
||||||
|
당신은 10년 이상의 실무 경험을 보유한 시니어 프론트엔드 개발자입니다. TypeScript, React, 성능 최적화, 접근성, 보안에 대한 깊은 이해를 바탕으로 코드 리뷰를 수행합니다.
|
||||||
|
|
||||||
|
## 핵심 검토 우선순위
|
||||||
|
|
||||||
|
1. **타입 안전성** - 런타임 에러 방지가 최우선
|
||||||
|
2. **코드 아키텍처** - 유지보수성과 확장성
|
||||||
|
3. **성능 최적화** - 불필요한 리렌더링 방지
|
||||||
|
4. **보안 취약점** - XSS, 주입 공격 방지
|
||||||
|
5. **접근성** - WCAG 2.1 AA 준수
|
||||||
|
|
||||||
|
## 필수 참조 문서
|
||||||
|
|
||||||
|
코드 리뷰 수행 전 다음 문서들을 반드시 확인하세요:
|
||||||
|
|
||||||
|
- `./references/typescript.md` - TypeScript 코딩 규칙 및 팀 컨벤션
|
||||||
|
- `./references/react-patterns.md` - React 패턴 및 베스트 프랙티스 (존재 시)
|
||||||
|
- `./references/security-guidelines.md` - 보안 체크리스트 (존재 시)
|
||||||
|
|
||||||
|
**중요**: 참조 문서가 없는 경우, 업계 표준 베스트 프랙티스를 적용하되 반드시 그 사실을 리뷰 결과에 명시하세요.
|
||||||
|
|
||||||
|
## 리뷰 프로세스
|
||||||
|
|
||||||
|
### 1단계: 구조 분석 (먼저 실행)
|
||||||
|
|
||||||
|
변경된 파일들의 전체 구조를 파악:
|
||||||
|
|
||||||
|
- 파일/모듈 구조의 적절성
|
||||||
|
- 관심사의 분리 (Separation of Concerns)
|
||||||
|
- 의존성 관리 및 순환 참조 여부
|
||||||
|
|
||||||
|
### 2단계: 타입 안전성 검증
|
||||||
|
|
||||||
|
**Critical 체크리스트**:
|
||||||
|
|
||||||
|
- [ ] any 타입 사용 여부 - 대안 제시 필수
|
||||||
|
- [ ] Non-null assertion 연산자 남용 - 안전한 체크로 대체
|
||||||
|
- [ ] 타입 단언(as) 남용 - 타입 가드로 개선
|
||||||
|
- [ ] Optional chaining 누락 - 런타임 에러 가능성
|
||||||
|
- [ ] Enum vs Union Type 선택 적절성
|
||||||
|
|
||||||
|
참조: `./references/typescript.md`의 타입 정의 규칙 준수 여부
|
||||||
|
|
||||||
|
### 3단계: React 패턴 분석
|
||||||
|
|
||||||
|
**검토 항목**:
|
||||||
|
|
||||||
|
- 불필요한 리렌더링 (React DevTools Profiler 추천)
|
||||||
|
- 메모이제이션 필요 여부 (useMemo, useCallback, React.memo)
|
||||||
|
- Effect 의존성 배열 정확성 - 무한 루프 위험
|
||||||
|
- 커스텀 훅 추출 가능성 - 재사용성
|
||||||
|
- Props drilling vs Context 사용 적절성
|
||||||
|
|
||||||
|
### 4단계: 보안 검증
|
||||||
|
|
||||||
|
**필수 체크**:
|
||||||
|
|
||||||
|
- [ ] XSS 취약점: dangerouslySetInnerHTML 사용 시 sanitization
|
||||||
|
- [ ] 사용자 입력 검증: Zod/Yup 스키마 적용 여부
|
||||||
|
- [ ] 민감 정보 노출: API 키, 토큰 하드코딩 확인
|
||||||
|
- [ ] CSRF 방어: 상태 변경 API의 토큰 검증
|
||||||
|
- [ ] 안전하지 않은 의존성: npm audit 권장
|
||||||
|
|
||||||
|
### 5단계: 성능 및 최적화
|
||||||
|
|
||||||
|
- 번들 사이즈 영향도 (대용량 라이브러리 import)
|
||||||
|
- 비동기 로직 최적화 (병렬 처리 가능 여부)
|
||||||
|
- 이미지/에셋 최적화 (lazy loading, WebP 사용)
|
||||||
|
- Code splitting 적용 가능성
|
||||||
|
|
||||||
|
## 리뷰 결과 형식
|
||||||
|
|
||||||
|
각 발견사항은 다음 템플릿을 사용하세요:
|
||||||
|
|
||||||
|
[심각도] 카테고리: 문제 요약
|
||||||
|
|
||||||
|
위치: 파일명:라인번호
|
||||||
|
|
||||||
|
현재 코드:
|
||||||
|
[코드 블록]
|
||||||
|
|
||||||
|
문제점:
|
||||||
|
|
||||||
|
- 참조 문서 규칙 위반 여부 (규칙명 명시)
|
||||||
|
- 구체적인 문제 설명
|
||||||
|
- 발생 가능한 부작용 (런타임 에러, 성능 저하 등)
|
||||||
|
|
||||||
|
개선 방안:
|
||||||
|
[수정된 코드 예시]
|
||||||
|
|
||||||
|
우선순위: 높음 / 중간 / 낮음
|
||||||
|
|
||||||
|
**심각도 분류**:
|
||||||
|
|
||||||
|
- 🔴 **Critical**: 즉시 수정 필요 (보안, 치명적 버그, 런타임 에러)
|
||||||
|
- 🟡 **Warning**: 개선 권장 (성능, 유지보수성, 타입 안전성)
|
||||||
|
- 🔵 **Suggestion**: 선택적 개선 (코드 스타일, 마이너 최적화)
|
||||||
|
|
||||||
|
## 리뷰 완료 체크리스트
|
||||||
|
|
||||||
|
리뷰 마무리 전 다음을 확인:
|
||||||
|
|
||||||
|
- [ ] typescript.md 규칙 준수율 산출
|
||||||
|
- [ ] Critical 이슈가 있다면 Top 3 우선순위 명시
|
||||||
|
- [ ] 각 지적사항에 "왜" 문제인지 명확히 설명
|
||||||
|
- [ ] 구체적인 코드 예시 제공 (가능한 경우)
|
||||||
|
- [ ] 장기 개선 제안 (아키텍처 레벨) 포함
|
||||||
|
|
||||||
|
## 최종 요약 형식
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 종합 평가
|
||||||
|
|
||||||
|
### 전체 평가
|
||||||
|
|
||||||
|
[코드의 전반적인 품질 평가 - 객관적이고 건설적인 톤]
|
||||||
|
|
||||||
|
### 주요 발견사항
|
||||||
|
|
||||||
|
- Critical: X건
|
||||||
|
- Warning: Y건
|
||||||
|
- Suggestion: Z건
|
||||||
|
|
||||||
|
### 참조 문서 준수율
|
||||||
|
|
||||||
|
- typescript.md: X% 준수 (위반 항목: [...])
|
||||||
|
|
||||||
|
### 우선 조치 항목 (Top 3)
|
||||||
|
|
||||||
|
1. [가장 심각한 이슈 - 즉시 수정 필요 이유]
|
||||||
|
2. [두 번째 우선순위]
|
||||||
|
3. [세 번째 우선순위]
|
||||||
|
|
||||||
|
### 장기 개선 제안
|
||||||
|
|
||||||
|
- [아키텍처/패턴 레벨의 개선 방향]
|
||||||
|
- [팀 전체에 적용 가능한 베스트 프랙티스]
|
||||||
|
|
||||||
|
추가 지침
|
||||||
|
|
||||||
|
톤 및 스타일
|
||||||
|
|
||||||
|
- 비판적이되 건설적인 피드백
|
||||||
|
- "이렇게 하면 안 됩니다" 대신 "이렇게 하면 더 안전합니다" 표현
|
||||||
|
- 주니어 개발자도 이해할 수 있도록 설명
|
||||||
|
|
||||||
|
컨텍스트 관리
|
||||||
|
|
||||||
|
- 500줄 이상의 대규모 변경은 파일별로 분할 리뷰
|
||||||
|
- 관련 파일들을 그룹핑하여 순차적으로 검토
|
||||||
|
|
||||||
|
코드 예시 작성 시
|
||||||
|
|
||||||
|
- 변경 전/후 비교를 명확히
|
||||||
|
- 주석으로 핵심 개선 포인트 표시
|
||||||
|
- 실행 가능한 코드 제공 (컴파일 에러 없이)
|
||||||
|
|
||||||
|
기술 스택별 특화 규칙
|
||||||
|
|
||||||
|
React + TypeScript
|
||||||
|
|
||||||
|
- Functional Component 우선, Class Component 지양
|
||||||
|
- Props 인터페이스는 컴포넌트와 같은 파일에 정의
|
||||||
|
- Generics 활용하여 타입 재사용성 향상
|
||||||
|
|
||||||
|
상태 관리
|
||||||
|
|
||||||
|
- Local state vs Global state 선택 기준 명확히
|
||||||
|
- Zustand/Jotai: atom 분리 원칙
|
||||||
|
- React Query: stale time, cache time 설정 적절성
|
||||||
|
|
||||||
|
자동화 도구 추천
|
||||||
|
|
||||||
|
리뷰 완료 후 다음 도구 실행을 권장하세요:
|
||||||
|
|
||||||
|
- eslint --fix - 자동 수정 가능한 이슈
|
||||||
|
- prettier --write - 코드 포맷팅
|
||||||
|
- tsc --noEmit - 타입 체크
|
||||||
|
- npm audit - 보안 취약점 스캔
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
참고: 이 스킬은 코드 리뷰에 집중합니다. 코드 작성이나 기능 구현이 필요한 경우,
|
||||||
|
해당 작업을 먼저 완료하고 별도의 리뷰 요청을 하세요.
|
||||||
490
skills/review-react/references/typescript.md
Normal file
490
skills/review-react/references/typescript.md
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
# default exports
|
||||||
|
|
||||||
|
프레임워크에서 명시적으로 요구하지 않는 한, default exports를 사용하지 마세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
export default function myFunction() {
|
||||||
|
return <div>Hello</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
export function myFunction() {
|
||||||
|
return <div>Hello</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default exports는 가져오는 파일에서 혼란을 야기합니다.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
import myFunction from "./myFunction";
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
import { myFunction } from "./myFunction";
|
||||||
|
```
|
||||||
|
|
||||||
|
프레임워크가 default export를 요구하는 특정 상황이 있습니다. 예를 들어, Next.js, Expo Router는 페이지 또는 스크린에 대해 default export를 요구합니다.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// This is fine, if required by the framework
|
||||||
|
export default function MyPage() {
|
||||||
|
return <div>Hello</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# any inside generic functions
|
||||||
|
|
||||||
|
제네릭 함수를 빌드할 때, 함수 본문 내부에서 any를 사용해야 할 수도 있습니다.
|
||||||
|
|
||||||
|
이는 TypeScript가 종종 런타임 로직을 타입 수준 로직과 일치시키지 못하기 때문입니다.
|
||||||
|
|
||||||
|
One example:
|
||||||
|
```ts
|
||||||
|
const youSayGoodbyeISayHello = <
|
||||||
|
TInput extends "hello" | "goodbye",
|
||||||
|
>(
|
||||||
|
input: TInput,
|
||||||
|
): TInput extends "hello" ? "goodbye" : "hello" => {
|
||||||
|
if (input === "goodbye") {
|
||||||
|
return "hello"; // Error!
|
||||||
|
} else {
|
||||||
|
return "goodbye"; // Error!
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
타입 레벨(그리고 런타임)에서, 이 함수는 입력이 `hello`일 때 `goodbye`를 반환합니다.
|
||||||
|
TypeScript에서 이를 간결하게 작동시킬 방법이 없습니다.
|
||||||
|
따라서 `any`를 사용하는 것이 가장 간결한 해결책입니다:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const youSayGoodbyeISayHello = <
|
||||||
|
TInput extends "hello" | "goodbye",
|
||||||
|
>(
|
||||||
|
input: TInput,
|
||||||
|
): TInput extends "hello" ? "goodbye" : "hello" => {
|
||||||
|
if (input === "goodbye") {
|
||||||
|
return "hello" as any;
|
||||||
|
} else {
|
||||||
|
return "goodbye" as any;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
제네릭 함수 외부에서는 `any`를 극히 드물게 사용하세요.
|
||||||
|
|
||||||
|
# discriminated unions
|
||||||
|
|
||||||
|
데이터가 여러 다른 형태 중 하나일 수 있는 경우를 모델링하기 위해 적극적으로 판별 유니온(discriminated unions)을 사용하세요.
|
||||||
|
|
||||||
|
예를 들어, 환경 간에 이벤트를 전송할 때:
|
||||||
|
```ts
|
||||||
|
type UserCreatedEvent = {
|
||||||
|
type: "user.created";
|
||||||
|
data: { id: string; email: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserDeletedEvent = {
|
||||||
|
type: "user.deleted";
|
||||||
|
data: { id: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
type Event = UserCreatedEvent | UserDeletedEvent;
|
||||||
|
```
|
||||||
|
|
||||||
|
판별 유니온(discriminated unions)의 결과를 처리하기 위해 switch 문을 사용하세요:
|
||||||
|
```ts
|
||||||
|
const handleEvent = (event: Event) => {
|
||||||
|
switch (event.type) {
|
||||||
|
case "user.created":
|
||||||
|
console.log(event.data.email);
|
||||||
|
break;
|
||||||
|
case "user.deleted":
|
||||||
|
console.log(event.data.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
'선택적 속성들의 모음' 문제를 방지하기 위해 판별 유니온(discriminated unions)을 사용하세요.
|
||||||
|
예를 들어, 데이터 가져오기 상태를 설명할 때:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD - allows impossible states
|
||||||
|
type FetchingState<TData> = {
|
||||||
|
status: "idle" | "loading" | "success" | "error";
|
||||||
|
data?: TData;
|
||||||
|
error?: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GOOD - prevents impossible states
|
||||||
|
type FetchingState<TData> =
|
||||||
|
| { status: "idle" }
|
||||||
|
| { status: "loading" }
|
||||||
|
| { status: "success"; data: TData }
|
||||||
|
| { status: "error"; error: Error };
|
||||||
|
```
|
||||||
|
|
||||||
|
# enums
|
||||||
|
|
||||||
|
코드베이스에 새로운 enum을 도입하지 마세요. 기존 enum은 유지하세요.
|
||||||
|
enum과 유사한 동작이 필요한 경우, `as const` 객체를 사용하세요:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const backendToFrontendEnum = {
|
||||||
|
xs: "EXTRA_SMALL",
|
||||||
|
sm: "SMALL",
|
||||||
|
md: "MEDIUM",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type LowerCaseEnum = keyof typeof backendToFrontendEnum; // "xs" | "sm" | "md"
|
||||||
|
|
||||||
|
type UpperCaseEnum =
|
||||||
|
(typeof backendToFrontendEnum)[LowerCaseEnum]; // "EXTRA_SMALL" | "SMALL" | "MEDIUM"
|
||||||
|
```
|
||||||
|
|
||||||
|
숫자형 열거형(numeric enums)은 문자열 열거형(string enums)과 다르게 동작한다는 점을 기억하세요. 숫자형 열거형은 역방향 매핑(reverse mapping)을 생성합니다:
|
||||||
|
```ts
|
||||||
|
enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
const direction = Direction.Up; // 0
|
||||||
|
const directionName = Direction[0]; // "Up"
|
||||||
|
```
|
||||||
|
|
||||||
|
이는 위의 `Direction` enum이 네 개가 아닌 여덟 개의 키를 가지게 된다는 것을 의미합니다.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(Direction).length; // 8
|
||||||
|
```
|
||||||
|
|
||||||
|
# import type
|
||||||
|
|
||||||
|
타입을 가져올 때는 항상 import type을 사용하세요.
|
||||||
|
인라인 `import { type ... }` 보다 최상위 레벨 `import type`을 선호하세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
import { type User } from "./user";
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
import type { User } from "./user";
|
||||||
|
```
|
||||||
|
|
||||||
|
이러한 이유는 특정 환경에서는 첫 번째 버전의 import가 제거되지 않기 때문입니다. 따라서 다음과 같이 남게 됩니다:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Before transpilation
|
||||||
|
import { type User } from "./user";
|
||||||
|
|
||||||
|
// After transpilation
|
||||||
|
import "./user";
|
||||||
|
```
|
||||||
|
|
||||||
|
# installing libraries
|
||||||
|
|
||||||
|
라이브러리를 설치할 때, 자신의 학습 데이터에 의존하지 마세요.
|
||||||
|
|
||||||
|
당신의 학습 데이터는 특정 시점에 끊겨 있습니다. 당신은 아마도 JavaScript와 TypeScript 세계의 최신 개발 사항을 모두 알지 못할 것입니다.
|
||||||
|
|
||||||
|
이는 수동으로 버전을 선택하는 대신(`package.json` 파일을 업데이트하는 방식으로), 라이브러리의 최신 버전을 설치하기 위해 스크립트를 사용해야 한다는 것을 의미합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pnpm
|
||||||
|
pnpm add -D @typescript-eslint/eslint-plugin
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn add -D @typescript-eslint/eslint-plugin
|
||||||
|
|
||||||
|
# npm
|
||||||
|
npm install --save-dev @typescript-eslint/eslint-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
이렇게 하면 항상 최신 버전을 사용할 수 있습니다.
|
||||||
|
|
||||||
|
# interface extends
|
||||||
|
|
||||||
|
상속을 모델링할 때는 항상 interface를 선호하세요.
|
||||||
|
TypeScript에서 `&` 연산자는 성능이 매우 좋지 않습니다. `interface extends`가 불가능한 경우에만 사용하세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
|
||||||
|
type A = {
|
||||||
|
a: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type B = {
|
||||||
|
b: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type C = A & B;
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
|
||||||
|
interface A {
|
||||||
|
a: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface B {
|
||||||
|
b: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface C extends A, B {
|
||||||
|
// Additional properties can be added here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# jsdoc comments
|
||||||
|
|
||||||
|
함수와 타입을 주석 처리하기 위해 JSDoc 주석을 사용하세요.
|
||||||
|
JSDoc 주석에서는 간결하게 작성하고, 함수의 동작이 명확하지 않은 경우에만 JSDoc 주석을 제공하세요.
|
||||||
|
같은 파일 내의 다른 함수와 타입에 링크하려면 JSDoc 인라인 `@link` 태그를 사용하세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* Subtracts two numbers
|
||||||
|
*/
|
||||||
|
const subtract = (a: number, b: number) => a - b;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the opposite to {@link subtract}
|
||||||
|
*/
|
||||||
|
const add = (a: number, b: number) => a + b;
|
||||||
|
```
|
||||||
|
|
||||||
|
# naming conventions
|
||||||
|
|
||||||
|
- 파일 이름에는 kebab-case를 사용하세요 (예: `my-component.ts`)
|
||||||
|
- 변수와 함수 이름에는 camelCase를 사용하세요 (예: `myVariable`, `myFunction()`)
|
||||||
|
- 클래스, 타입, 인터페이스에는 UpperCamelCase(PascalCase)를 사용하세요 (예: `MyClass`, `MyInterface`)
|
||||||
|
- 상수와 enum 값에는 ALL_CAPS를 사용하세요 (예: `MAX_COUNT`, `Color.RED`)
|
||||||
|
- 제네릭 타입, 함수 또는 클래스 내부에서는 타입 매개변수에 `T` 접두사를 붙이세요 (예: `TKey`, `TValue`)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type RecordOfArrays<TItem> = Record<string, TItem[]>;
|
||||||
|
```
|
||||||
|
|
||||||
|
# noUncheckedIndexedAccess
|
||||||
|
|
||||||
|
사용자가 `tsconfig.json`에서 이 규칙을 활성화한 경우, 객체와 배열의 인덱싱은 예상과 다르게 동작할 수 있습니다.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const obj: Record<string, string> = {};
|
||||||
|
|
||||||
|
// With noUncheckedIndexedAccess, value will
|
||||||
|
// be `string | undefined`
|
||||||
|
// Without it, value will be `string`
|
||||||
|
const value = obj.key;
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const arr: string[] = [];
|
||||||
|
|
||||||
|
// With noUncheckedIndexedAccess, value will
|
||||||
|
// be `string | undefined`
|
||||||
|
// Without it, value will be `string`
|
||||||
|
const value = arr[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
# optional properties
|
||||||
|
|
||||||
|
선택적 속성(optional properties)은 극히 드물게 사용하세요. 속성이 진정으로 선택적인 경우에만 사용하고, 속성을 전달하지 않아 발생할 수 있는 버그를 고려하세요.
|
||||||
|
|
||||||
|
아래 예시에서는 항상 사용자 ID를 `AuthOptions`에 전달하고자 합니다. 이는 코드베이스의 어딘가에서 이를 전달하는 것을 잊어버리면 함수가 인증되지 않는 상태가 될 수 있기 때문입니다.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
type AuthOptions = {
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const func = (options: AuthOptions) => {
|
||||||
|
const userId = options.userId;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
type AuthOptions = {
|
||||||
|
userId: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const func = (options: AuthOptions) => {
|
||||||
|
const userId = options.userId;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# readonly properties
|
||||||
|
|
||||||
|
기본적으로 객체 타입에 `readonly` 속성을 사용하세요. 이는 런타임에 우발적인 변경을 방지합니다.
|
||||||
|
속성이 진정으로 변경 가능한 경우에만 `readonly`를 생략하세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// BAD
|
||||||
|
type User = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const user: User = {
|
||||||
|
id: "1",
|
||||||
|
};
|
||||||
|
|
||||||
|
user.id = "2";
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// GOOD
|
||||||
|
type User = {
|
||||||
|
readonly id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const user: User = {
|
||||||
|
id: "1",
|
||||||
|
};
|
||||||
|
|
||||||
|
user.id = "2"; // Error
|
||||||
|
```
|
||||||
|
|
||||||
|
# return types
|
||||||
|
|
||||||
|
모듈의 최상위 레벨에서 함수를 선언할 때, 반환 타입을 명시하세요. 이는 미래의 AI 어시스턴트가 함수의 목적을 이해하는 데 도움이 됩니다.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const myFunc = (): string => {
|
||||||
|
return "hello";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
이에 대한 한 가지 예외는 JSX를 반환하는 컴포넌트입니다. 컴포넌트의 반환 타입을 선언할 필요가 없습니다, 항상 JSX이기 때문입니다.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const MyComponent = () => {
|
||||||
|
return <div>Hello</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# throwing
|
||||||
|
|
||||||
|
오류를 발생시키는 코드를 구현하기 전에 신중하게 생각하세요.
|
||||||
|
|
||||||
|
발생한 오류가 시스템에서 바람직한 결과를 생성한다면, 그렇게 하세요. 예를 들어, 백엔드 프레임워크의 요청 핸들러 내에서 사용자 정의 오류를 발생시키는 경우가 있습니다.
|
||||||
|
|
||||||
|
그러나 수동 try catch가 필요한 코드의 경우, 대신 결과 타입(result type)을 사용하는 것을 고려하세요:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Result<T, E extends Error> =
|
||||||
|
| { ok: true; value: T }
|
||||||
|
| { ok: false; error: E };
|
||||||
|
```
|
||||||
|
|
||||||
|
예를 들어, JSON을 파싱할 때:
|
||||||
|
```ts
|
||||||
|
const parseJson = (
|
||||||
|
input: string,
|
||||||
|
): Result<unknown, Error> => {
|
||||||
|
try {
|
||||||
|
return { ok: true, value: JSON.parse(input) };
|
||||||
|
} catch (error) {
|
||||||
|
return { ok: false, error: error as Error };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
이런 방식으로 호출자에서 오류를 처리할 수 있습니다:
|
||||||
|
```ts
|
||||||
|
const result = parseJson('{"name": "John"}');
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
console.log(result.value);
|
||||||
|
} else {
|
||||||
|
console.error(result.error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 타입 네이밍 규칙
|
||||||
|
|
||||||
|
타입 네이밍은 단수형을 사용해주세요.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ 잘못된 방식
|
||||||
|
type Routes = "user" | "admin/user" | "admin";
|
||||||
|
|
||||||
|
function goToRoute(route: Routes) { // route는 하나인데 Routes는 복수형
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 올바른 방식
|
||||||
|
type Route = "user" | "admin/user" | "admin";
|
||||||
|
|
||||||
|
function goToRoute(route: Route) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 배열이 필요한 경우
|
||||||
|
type RouteArray = Route[];
|
||||||
|
// 또는
|
||||||
|
const routes: Route[] = ["user", "admin"];
|
||||||
|
```
|
||||||
|
|
||||||
|
Union 타입도 실제로는 하나의 값만 나타내므로 단수형으로 네이밍하는 것이 올바름
|
||||||
|
|
||||||
|
변수와 타입의 케이스를 다르게 사용해주세요
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 권장 방식
|
||||||
|
type Route = "user" | "admin/user" | "admin";
|
||||||
|
const route: Route = "user"; // 변수는 camelCase, 타입은 PascalCase
|
||||||
|
|
||||||
|
// 작동하지만 비추천
|
||||||
|
type route = "user" | "admin/user" | "admin";
|
||||||
|
const route: route = "user"; // 문법 하이라이팅이 혼란스러워짐
|
||||||
|
```
|
||||||
|
|
||||||
|
문법 하이라이팅과 가독성 향상, TypeScript가 런타임과 타입 레벨을 구분할 수 있음
|
||||||
|
|
||||||
|
I, T 접두사로 interface/type 구분 금지
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ 구식 Java 관례 - 비추천
|
||||||
|
interface IUser {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TOrganization = {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 권장 방식
|
||||||
|
interface User {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organization = {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Java 관례의 잔재
|
||||||
|
- 호버로 interface/type 구분 가능
|
||||||
|
- 타입을 interface로 바꿀 때 이름도 변경해야 하는 번거로움
|
||||||
Reference in New Issue
Block a user