기여 가이드
GMD Soft Platform Frontend 프로젝트에 기여해주셔서 감사합니다! 이 문서는 프로젝트에 기여하는 방법을 안내합니다.
📋 목차
시작하기 전에
필수 확인 사항
기여하기 전에 다음을 확인하세요:
- [ ] onboarding.md를 읽고 개발 환경 설정 완료
- [ ] CLAUDE.md를 읽고 개발 컨벤션 숙지
- [ ] 작업할 이슈가 GitHub Issues에 등록되어 있는지 확인
- [ ] 중복 작업이 없는지 확인 (다른 PR 검색)
작업 전 체크리스트
- [ ] 최신
main브랜치를 pull - [ ] 새로운 브랜치 생성
- [ ] 의존성 최신 상태 확인 (
yarn install)
개발 워크플로우
1. 이슈 확인 또는 생성
작업을 시작하기 전에 GitHub Issues에서 이슈를 확인하거나 생성하세요.
좋은 이슈 예시:
markdown
## 문제
Button 컴포넌트에 loading state가 없어서 비동기 작업 중 사용자 피드백을 제공할 수 없습니다.
## 제안
Button 컴포넌트에 `isLoading` prop을 추가하여 로딩 중 Spinner를 표시하도록 개선
## 작업 범위
- [ ] `isLoading` prop 추가
- [ ] Spinner 컴포넌트 통합
- [ ] Storybook 스토리 추가
- [ ] 테스트 작성2. 브랜치 생성
bash
# 최신 main 브랜치 가져오기
git checkout main
git pull origin main
# 새 브랜치 생성
git checkout -b feature/add-button-loading-state3. 개발
코드를 작성하고 자주 커밋하세요.
bash
# 작업 진행...
# 변경사항 확인
git status
git diff
# 스테이징 및 커밋
git add .
git commit -m "feat: Button에 isLoading prop 추가"4. 테스트 및 검증
bash
# 린트 검사
npm run lint
# 타입 체크
npm run build
# 테스트 실행
npm run test
# Storybook 확인
npm run storybook5. Push 및 PR 생성
bash
# 원격 브랜치에 푸시
git push origin feature/add-button-loading-state
# GitHub에서 Pull Request 생성브랜치 전략
브랜치 네이밍 규칙
<type>/<description>Type:
feature/- 새로운 기능 추가fix/- 버그 수정refactor/- 코드 리팩토링docs/- 문서 수정test/- 테스트 코드 추가/수정chore/- 빌드 설정, 패키지 업데이트 등style/- 코드 포맷팅 (기능 변경 없음)
Description:
- 소문자, kebab-case 사용
- 명확하고 간결하게 작성
- 영어 사용 권장
예시:
bash
feature/add-button-loading-state
fix/user-profile-rendering-error
refactor/extract-form-validation-logic
docs/update-onboarding-guide
test/add-button-component-tests
chore/upgrade-react-query
style/format-user-profile-component브랜치 수명 주기
- 생성: main 브랜치에서 분기
- 개발: 작업 진행 및 커밋
- 리뷰: PR 생성 및 코드 리뷰
- 머지: main 브랜치에 머지
- 삭제: 로컬 및 원격 브랜치 삭제
bash
# 머지 후 로컬 브랜치 삭제
git checkout main
git pull origin main
git branch -d feature/add-button-loading-state
# 원격 브랜치 삭제 (GitHub UI에서 자동 삭제 설정 권장)
git push origin --delete feature/add-button-loading-state커밋 컨벤션
Conventional Commits 사용
<type>: <subject>
<body> (선택)
<footer> (선택)Type
feat: 새로운 기능 추가fix: 버그 수정refactor: 코드 리팩토링 (기능 변경 없음)chore: 빌드 설정, 패키지 업데이트 등docs: 문서 수정test: 테스트 코드 추가/수정style: 코드 포맷팅 (기능 변경 없음)perf: 성능 개선
Subject
- 50자 이내로 작성
- 한글 또는 영어 사용
- 마침표(.) 사용하지 않음
- 현재형 동사 사용 ("추가", "수정", "제거")
예시
좋은 커밋 메시지:
bash
feat: Button 컴포넌트에 isLoading prop 추가
fix: 로그인 시 토큰 갱신 오류 수정
refactor: forEach를 for...of로 변경
docs: onboarding.md에 트러블슈팅 섹션 추가
test: useForm 훅 테스트 추가
chore: TanStack Query 버전 업그레이드피해야 할 커밋 메시지:
bash
# ❌ 너무 모호함
fix: 버그 수정
# ❌ 너무 김
feat: Button 컴포넌트에 isLoading prop을 추가하고 로딩 중에는 Spinner를 표시하도록 개선했으며...
# ❌ 과거형 사용
feat: Button에 isLoading을 추가했음
# ❌ Type 누락
Button에 isLoading 추가커밋 단위
- 하나의 커밋은 하나의 의도만 담기
- 독립적으로 동작하는 단위로 커밋
- 너무 작은 커밋보다는 의미 있는 단위로 묶기
예시:
bash
# ✅ 좋은 예 (의미 있는 단위)
feat: Button에 isLoading prop 추가
feat: Input에 icon prop 추가
# ❌ 나쁜 예 (너무 세분화)
feat: Button Props에 isLoading 타입 추가
feat: Button에 Spinner import 추가
feat: Button에 isLoading 조건부 렌더링 추가Pull Request 작성
PR 제목
커밋 컨벤션과 동일한 형식 사용:
<type>: <subject>예시:
feat: Button 컴포넌트에 loading state 추가
fix: 로그인 시 토큰 갱신 오류 수정
refactor: useForm 훅 구조 개선PR 설명 템플릿
markdown
## 변경 사항
<!-- 무엇을 변경했는지 간략히 설명 -->
Button 컴포넌트에 `isLoading` prop을 추가하여 비동기 작업 중 로딩 상태를 표시할 수 있도록 개선했습니다.
## 주요 변경 내용
<!-- 구체적인 변경 사항을 나열 -->
- `Button.tsx`에 `isLoading` prop 추가
- 로딩 중 Spinner 컴포넌트 렌더링
- Storybook 스토리 추가 (`Loading` 스토리)
- 타입 정의 업데이트
## 관련 이슈
<!-- 관련 이슈 번호 링크 -->
Closes #123
## 체크리스트
<!-- 완료한 항목에 체크 -->
- [x] 코드 작성 완료
- [x] Storybook 스토리 추가
- [x] 린트 검사 통과
- [x] 타입 체크 통과
- [x] 테스트 작성 (필요시)
- [x] 문서 업데이트 (필요시)
- [x] 코드 리뷰 요청
## 스크린샷
<!-- UI 변경이 있는 경우 스크린샷 첨부 -->

## 테스트 방법
<!-- 리뷰어가 테스트할 수 있는 방법 설명 -->
1. Storybook 실행 (`npm run storybook`)
2. `Atoms/Button` → `Loading` 스토리 확인
3. 버튼 클릭 시 Spinner가 표시되는지 확인
## 추가 정보
<!-- 리뷰어가 알아야 할 추가 정보 -->
- Spinner 컴포넌트는 기존 `shared/ui/atoms/Spinner`를 재사용했습니다.
- `isLoading={true}`일 때 버튼은 disabled 상태가 됩니다.PR 크기 가이드
작은 PR이 좋은 PR입니다!
- 한 PR에는 하나의 기능만 포함
- 변경된 파일 수: 10개 이하 권장
- 변경된 라인 수: 300줄 이하 권장
- 큰 작업은 여러 PR로 분할
Draft PR 활용
작업 진행 중에 피드백을 받고 싶다면 Draft PR을 활용하세요:
markdown
## [WIP] Button 컴포넌트 loading state 추가
**현재 상태**: 기본 구조만 구현, Spinner 통합 예정
**질문**:
- Spinner 크기를 Button size에 맞게 조정해야 할까요?
- isLoading일 때 children을 숨겨야 할까요?코드 리뷰
리뷰 요청
- PR 생성 후 리뷰어 지정
- 슬랙 또는 팀 채널에 리뷰 요청 알림
- CI/CD 체크가 모두 통과되었는지 확인
리뷰어 체크리스트
리뷰어는 다음 사항을 확인합니다:
코드 품질
- [ ] 코드가 CLAUDE.md의 컨벤션을 따르는가?
- [ ] TypeScript 타입이 올바르게 정의되었는가?
- [ ] Props는
Readonly<{}>패턴을 사용하는가? - [ ] 반복문에서
for...of를 사용하는가? - [ ] 불필요한 코드나 주석이 없는가?
기능
- [ ] 요구사항을 올바르게 구현했는가?
- [ ] 엣지 케이스를 고려했는가?
- [ ] 에러 처리가 적절한가?
테스트
- [ ] 중요한 로직에 대한 테스트가 있는가?
- [ ] 테스트가 모두 통과하는가?
문서
- [ ] Storybook 스토리가 추가되었는가? (공통 컴포넌트의 경우)
- [ ] 필요시 문서가 업데이트되었는가?
성능
- [ ] 불필요한 리렌더링이 발생하지 않는가?
- [ ] 큰 리스트의 경우 virtualization을 고려했는가?
보안
- [ ] XSS, SQL Injection 등의 취약점이 없는가?
- [ ] 사용자 입력 검증이 적절한가?
리뷰 피드백 작성
좋은 피드백:
markdown
**Question**: `isLoading`이 `true`일 때 버튼이 disabled되는 이유가 있나요? 사용자가 로딩 중에도 취소 버튼을 눌러야 하는 경우도 있을 것 같은데요.
**Suggestion**: Spinner의 크기를 Button의 `size` prop에 따라 조정하면 더 일관성 있을 것 같습니다.
```typescript
const spinnerSize = size === 'small' ? 16 : size === 'large' ? 24 : 20;
```Nit: 변수명 isLoading보다 loading이 더 간결할 것 같습니다. (선택사항)
Praise: Storybook 스토리를 잘 작성해주셨네요! 다양한 케이스를 잘 커버하고 있습니다. 👍
**피해야 할 피드백:**
```markdown
# ❌ 너무 모호함
코드를 개선해주세요.
# ❌ 비판적
이렇게 하면 안 됩니다.
# ❌ 설명 없음
여기를 수정하세요.피드백 반영
- 피드백을 읽고 이해
- 불명확한 부분은 질문
- 수정 후 커밋 및 푸시
- 변경 사항을 PR 코멘트로 설명
markdown
@reviewer 피드백 감사합니다! 다음과 같이 수정했습니다:
- Spinner 크기를 Button size에 맞게 조정했습니다.
- `isLoading`일 때도 버튼을 클릭할 수 있도록 `disabled` 로직을 제거했습니다.
다시 한번 확인 부탁드립니다!Approve 및 머지
- 모든 피드백이 반영되고 리뷰어의 Approve를 받으면 머지
- Squash Merge 사용 권장 (커밋 히스토리 정리)
- 머지 후 브랜치 삭제
테스트
테스트 작성 기준
다음의 경우 테스트 작성을 권장합니다:
- Pure Functions: 유틸리티 함수는 반드시 테스트 작성
- Custom Hooks: 복잡한 로직이 있는 훅
- 비즈니스 로직: 중요한 기능
- 버그 수정: 재발 방지를 위한 regression test
테스트 생략 가능:
- 단순 UI 컴포넌트 (Storybook으로 대체)
- Getter/Setter 함수
- 단순 타입 정의
테스트 작성 예시
typescript
// useForm.test.ts
import { renderHook, act } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { useForm } from './useForm';
describe('useForm', () => {
it('초기값으로 formState가 설정된다', () => {
const { result } = renderHook(() =>
useForm({
initialValues: { email: '', password: '' },
}),
);
expect(result.current.formState.email).toBe('');
expect(result.current.formState.password).toBe('');
});
it('handleInputChange로 값을 변경할 수 있다', () => {
const { result } = renderHook(() =>
useForm({
initialValues: { email: '' },
}),
);
act(() => {
result.current.handleInputChange('email', 'test@example.com');
});
expect(result.current.formState.email).toBe('test@example.com');
});
it('validation 함수가 에러를 반환한다', () => {
const { result } = renderHook(() =>
useForm({
initialValues: { email: '' },
validation: {
email: (value) => (value.includes('@') ? null : '올바른 이메일을 입력하세요'),
},
}),
);
act(() => {
result.current.handleInputChange('email', 'invalid');
});
expect(result.current.formErrors.email).toBe('올바른 이메일을 입력하세요');
});
});Coverage 목표
- Lines: 70% 이상
- Functions: 70% 이상
- Branches: 70% 이상
- Statements: 70% 이상
bash
# Coverage 확인
npm run test:coverage문서화
코드 문서화
JSDoc 주석 (선택):
복잡한 함수나 유틸리티의 경우 JSDoc 주석 추가:
typescript
/**
* 파일 크기를 사람이 읽기 쉬운 형식으로 변환
* @param bytes - 바이트 단위의 파일 크기
* @returns 포맷된 문자열 (예: "1.5 MB")
*/
export function formatFileSize(bytes: number): string {
// ...
}Storybook 문서화
공통 컴포넌트(atoms, molecules, organisms)는 반드시 Storybook 스토리 작성:
typescript
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import Button from './Button';
const meta = {
title: 'Atoms/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
color: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: '버튼 색상',
},
size: {
control: 'select',
options: ['small', 'medium', 'large'],
description: '버튼 크기',
},
isLoading: {
control: 'boolean',
description: '로딩 상태',
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
children: 'Primary Button',
color: 'primary',
},
};
export const Loading: Story = {
args: {
children: 'Loading',
isLoading: true,
},
};
export const AllSizes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
<Button size="large">Large</Button>
</div>
),
};프로젝트 문서 업데이트
다음의 경우 프로젝트 문서 업데이트:
- 새로운 컨벤션 추가: CLAUDE.md 업데이트
- 새로운 패턴 발견: onboarding.md 또는 docs/ 업데이트
- API 변경: 관련 문서 업데이트
추가 리소스
- onboarding.md - 신입 개발자 온보딩
- CLAUDE.md - 개발 컨벤션 상세 가이드
- README.md - 프로젝트 개요
질문이 있나요?
- 팀 슬랙 채널에 질문하기
- PR 코멘트로 질문하기
- 1:1 미팅 요청하기
함께 만들어가는 프로젝트에 감사드립니다! 🚀