1. prettier
{ "tabWidth": 2, "singleAttributePerLine": true, "bracketSameLine": true, "singleQuote": true, "endOfLine": "lf", "plugins": ["@trivago/prettier-plugin-sort-imports"], "importOrder": [ "@storybook*", "@emotion*", "^react*", "^@*", "^[../]", "^[./]" ], "importOrderSortSpecifiers": true }
2. eslint
// .eslintrc.cjs module.exports = { root: true, env: { browser: true, es2021: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended', 'plugin:import/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'emotion.d.ts'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: true, tsconfigRootDir: __dirname, }, plugins: ['react-refresh', 'react', 'import'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], '@typescript-eslint/naming-convention': [ 'error', { selector: 'interface', format: ['PascalCase'], custom: { regex: '^I.*$|^.*Props$', match: true, }, }, { selector: 'variable', format: ['camelCase', 'PascalCase'], types: ['function'], }, { selector: 'variable', types: ['boolean'], format: ['PascalCase'], prefix: ['is', 'should', 'has', 'can', 'did', 'will'], }, { selector: 'variable', modifiers: ['destructured'], format: null, }, { selector: 'typeAlias', format: ['PascalCase'], suffix: ['Type'], }, ], 'react/jsx-key': 'error', 'no-unreachable': 'warn', }, settings: { 'import/resolver': { typescript: {}, }, }, };
3. 네이밍
분류 | 규칙 | 예시 | eslint 강제 유무 |
상수 | UPPER_SNAKE_CASE | const MAX_USERS = 10; | ㅤ |
변수 | camelCase
boolean은 접두사 사용 | let userName = 'JohnDoe';
let isActive = true;
let hasLoggedIn = false; | ✅ |
함수 | camelCase, 동사+명사
boolean 반환은 'is' 접두사,
api 요청 함수는 동사에 메서드,
api 요청 함수에 명사 없을시 생략 | const getUserProfile = () ⇒ { ... }
const isUserActive = () ⇒ { ... }
const async getPost = () ⇒ { … }
const async login = () ⇒ { … } | ⚠ 이름 규칙만 |
이벤트 핸들러 | camelCase
handle+명사+동사
props는 on 사용 | const handleButtonClick = () ⇒ { ... }
<button onClick={handleButtonClick}>Click me</button> | ㅤ |
컴포넌트 | PascalCase | const UserList = () ⇒ { ... } | ㅤ |
type 별칭 | PascalCase
Type 접미사 | type UserResponseType = ...
type ButtonClickType = ... | ✅ |
interface | PascalCase
컴포넌트에만 Props 접미사
그외 (스타일드컴포넌트포함) 모두 I 접두사 | interface UserProps { ... }
interface IUserOptions { ... } | ✅ |
styled-component | PascalCase
St 접두사 | const StButton = styled.button
<StButton>Save</StButton> | ㅤ |
ㅤ | ㅤ | ㅤ | ㅤ |
함수 인자 구조분해 | 규칙 없음 (모든 컨벤션 무시) | const createUser = ({ userName, userEmail }) ⇒ { ... } | ㅤ |
4. 코드 스타일
// export를 붙이고 표현식을 사용해요 export const Component = () => { }
// return만 하는 조건문은 중괄호 없이 한줄로 작성해요 if(true) return; // return, break, throw 등에 대해서만 { } 없이 if(true) { state = newState }
// 명시적으로 import 하자 ! import { ReactElement, MouseEvent as ReactMouseEvent, ReactNode, RefObject, } from 'react';
5. 폴더구조
src/ |-- _redux/ | |-- slices/ | |-- store.ts # 여기에 Redux 스토어 설정을 초기화합니다. | `-- hooks.ts # Redux 관련 커스텀 훅을 정의합니다. |-- api | |-- _types/apiModels.ts # api Model 타입들을 정의합니다. | |-- apis.ts # method별 요청 api | `-- customAxios.ts # 커스텀한 AxiosInstance |-- assets/ # 로고, 파비콘등 이미지 파일 |-- components/ # 재사용 가능한 컴포넌트들을 포함합니다. | `-- _common/ # 재사용 공통 컴포넌트 |-- hooks/ # 공통 커스텀 훅 |-- pages/ # 라우트에 맞게 페이지 컴포넌트를 구성합니다. | |-- MainPage/ | |-- DetailPage/ | |-- ProfilePage/ | |-- LoginPage/ | |-- SignupPage/ | `-- ErrorPage/ |-- styles/ # 라우트에 맞게 페이지 컴포넌트를 구성합니다. | |-- emotion.d.ts # emotion 타입 설정파일 | |-- GlobalReset.tsx | `-- theme.ts/ # 디자인시스템 정의 |-- utils/ # 유틸리티 함수 등 공통적으로 사용될 수 있는 모듈들 |-- App.ts # 애플리케이션 레이아웃을 정의하는 최상위 컴포넌트 |-- index.ts # 애플리케이션 진입점 -- index.ts # 페이지 라우팅 설정 파일
6. import 순서 규칙
아래에 위치할수록 본문과 가까워져서 중요도 높은 순으로 오름차순 정렬
- 나머지
- @storybook*
- emotion*
- react*
- @*
- ../
- ./