React 구조 설계 및 코딩 컨벤션

2021. 09. 02

협업을 생각한다면 코드는 무엇보다 직관적이고 명시적으로 짜는 것이 제일 중요합니다. 저의 경우, 2주 뒤에 다시 봐도 어떤 코드였는지 바로 알 수 있게 코드를 짜는 것이 제 규칙입니다.

협업뿐만 아니라 생산성가독성을 위해서라도 스스로의 코딩 컨벤션을 익혀나가는 것 또한 중요합니다.

변수 네이밍

1. boolean

모든 boolean 타입 변수는 반드시 접두사로 is를 붙입니다. 해당 변수 타입이 boolean임을 암시하는 용도입니다.

const [isLoading, setIsLoading] = useState(false)
const [isFetched, setIsFetched] = useState(false)

이 경우 camelCase를 사용합니다.

2. function

CRUD

데이터를 가져오는 함수는 접두사 get을 붙입니다.

interface Props {}

const HomePage: FunctionComponent<Props> = () => {
    ...

    const getPosts = async () => {}

    useEffect(() => {
        getPosts()
    }, [])
    return <>...</>
}

export default HomePage

생성과 관련된 함수는 접두사 create를 붙입니다.

interface Props {}

const HomePage: FunctionComponent<Props> = () => {
    ...

    const createPost = async () => {}

    return <>...</>
}

export default HomePage

수정과 관련된 함수는 접두사 update를 붙입니다.

interface Props {}

const HomePage: FunctionComponent<Props> = () => {
    ...

    const updatePost = async () => {}

    return <>...</>
}

export default HomePage

삭제와 관련된 함수는 접두사 delete를 붙입니다.

interface Props {}

const HomePage: FunctionComponent<Props> = () => {
    ...

    const deletePost = async () => {}

    return <>...</>
}

export default HomePage

상수 네이밍

모든 고정된 값은 전부 대문자로 짓습니다. 또한 data 폴더를 따로 만들어 관리합니다.

const THEME: 'dark' | 'light' = 'dark'
const BASE_URL = 'http://localhost:3000'

이 경우 snake_case를 사용합니다.

컴포넌트 내 배치

  • Hooks를 가장 상단에 몰아둡니다.

    • 그 중에서도 state를 선언하는 useObject가 가장 상단에 선언됩니다.
  • 함수들이 중간에 들어갑니다.
  • useEffect를 가장 하단에 몰아둡니다.
interface Props {}
interface State {}

const HomePage: FunctionComponent<Props> = () => {
    const [{}, setState] = useObject<State>({})
    const {} = useHistory()
    const user = useUser()

    ...

    const getPosts = async () => {}
    const createPost = async () => {}
    const updatePost = async () => {}

    ...

    useEffect(() => {
        getPosts()
    }, [])
    return <>...</>
}

export default HomePage

컴포넌트 네이밍

components 폴더 안의 모든 컴포넌트는 React의 Re를 따서 접두사로 Re를 붙입니다. 이 것은 해당 컴포넌트가 components 폴더 안에 있으며, 직접 커스터마이징한 컴포넌트라는 의미를 부여하기 위함입니다.

Material-UI같은 외부 UI 라이브러르를 사용하게 될 경우를 예상한다면, 해당 라이브러리의 컴포넌트명과 직접 만든 컴포넌트명을 접두사만 보고도 쉽게 구분할 수 있게 됩니다.

import { Button, Checkbox, Switch } from '@material-ui/core'
import { ReButton, ReCheckbox, ReSwitch } from 'components'

이 경우 PascalCase를 사용합니다.

components 폴더가 복잡해지는 경우

규모가 작은 프로젝트의 경우는 단순히 components 폴더에 컴포넌트를 몰아 넣습니다.

components1

하지만 규모가 커지는 경우, 이미 유명한 Atomic Design의 규약을 참고하여 4개의 폴더로 세분화합니다.

components2

절대 경로 모듈 시스템

모듈을 불러올 때, 어떤 폴더에서 불러왔는지 최상위 폴더명만 기입해서 가독성을 높입니다. 절대 경로만 잘 적용해놓으면 클릭 시 바로 해당 모듈로 이동해서 확인할 수도 있습니다.

export { default as ReButton } from './Button'
export { default as ReInput } from './Input'
export { default as ReSEO } from './SEO'
export { default as ReHeader } from './Header'

// 또는

export { default as ReButton } from './atoms/Button'
export { default as ReInput } from './atoms/Input'

export { default as ReSEO } from './molecules/SEO'

export { default as ReHeader } from './organisms/Header'

가장 중요한 것은 모든 컴포넌트는 어떤 폴더에 있어도 불러올 때는 ‘components’에서 불러와야 한다는 것입니다.

import { ReButton, ReSEO, ReHeader } from 'components'

index

모듈의 이름은 항상 폴더가 기준이고, 파일명은 반드시 index.[확장자] 식으로 짓습니다. 모듈 시스템에서 index로 이름을 지은 파일들은 기입하지 않아도 자동으로 인식하기 때문에 가독성면에서 더 좋은 코드가 될 수 있습니다.

컴포넌트의 경우 한 컴포넌트에 tsx, storybook, jest 파일을 한 번에 넣어서 관리할 수도 있습니다.

component

state는 무조건 객체로 선언

제가 만든 useObject라는 커스텀 훅이 있습니다. 해당 훅으로 한 컴포넌트 혹은 페이지의 state는 전부 useObject 하나만 써서 관리합니다.

services

페이지를 담당하는 pages와 컴포넌트를 담당하는 components를 제외하고 (store도 제외) 기능 단위로 전부 모아서 보관하는 폴더입니다.

사용자 코드 조각

해당 글 참조.

Prettier & ESLint

prettier는 다음의 규칙을 가집니다.

{
  "singleQuote": true,
  "semi": false,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "none",
  "printWidth": 80
}

짚고 넘어갈 게 있다면

  • 큰따옴표(“)가 아닌 작은따옴표(‘)를 사용합니다. - “singleQuote”
  • 세미콜론(;)을 찍지 않습니다. - “semi”
  • 배열의 맨 마지막 요소 뒤에 반점(,)을 찍지 않습니다. - “trailingComma”

ESLint는 적용하지 않습니다. (문법 에러가 의미없는 경우가 다반사)

개발 블로그

김동욱

wcgo2ling@gmail.com

© 2021 kidow. All right reserved.