Initial commit
This commit is contained in:
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