React 코딩 규칙

곽태욱·2020년 12월 13일
42

페스타에서 인턴하면서 개인적으로 느낀 React, Next, Apollo, JavaScript, CSS 코딩 규칙

아래에서 언급된 규칙 외에도 좋은 저명한 레퍼런스 사이트가 있으면 댓글로 알려주세요 😎

공통

제외 예정이거나(deprecated) 제외된(obsolute) 기능, 비표준 구문은 사용하지 않는다.

항상 적용할 순 없지만 가능하면 SOLID 규칙을 따르는 것을 권장한다.

쓰나 안 쓰나 결과에 영향을 미치지 않는 코드는 최대한 제거한다. 작성된 코드엔 항상 작성된 이유가 있는 것이 좋다.

JavaScript

주석

짧은 1줄 주석(// ...)은 가능하면 변수, 함수 등의 이름에 반영하는 것을 권장한다.

다른 사람의 이해를 돕기 위해 주석을 다는 것을 권장한다.

주석엔 복잡한 로직에 대한 설명 등을 담을 수 있다.

네이밍

  • PascalCase : type, interface, React component(function, class)

  • camelCase : variable(const, let), JavaScript function, custom hook, props

  • UPPER_SNAKE_CASE : tuple(as const 등)

  • 어떤 값으로부터 다른 값을 계산하는 함수 : get___From( ... )

    e.g. getTodayFrom(date: Date, index: number)

중괄호

if ( ) {
  ...
} else if ( ) {
  ...
} else {
  ...
}

function functionName() {
  ...
}

원활한 수정과 불필요한 diff 생성을 방지하기 위해 for, if, function 등에서 중괄호는 생략하지 않는다.

import

import ... from ... 문의 순서는 신경쓰지 않는다. (vscode '빠른 수정' 기본값은 패키지 이름 기준 ABC순)

함수 정의

익명 함수(anonymous function)는 화살표 함수로(arrow function), 이름 있는 함수(named function)는 function 키워드로 정의한다.

함수는 언제 만드는 것이 좋을까? 중복되는 코드가 있을 때? 너무 많이 만들어도 가독성이 떨어진다.

undefined와 falsy 값

변수에 직접 undefined를 할당하는 것은 그냥 없는 변수로 만든다는 뜻이어서 주의해서 사용해야 한다. 어떤 값이 존재하지 않음을 나타낼 땐 아래와 같이 각 자료형 별 falsy 값을 이용한다.

자료형falsy
정수0
불린false
문자열''
객체null
배열[].length

물론 0이나 false가 의미있는 값이면 정수나 불린 자료형에도 null을 사용할 수 있다. 그리고 빈 배열 자체는 truthy 값이다.

// 매개변수 초기값 설정에서 null과 undefined의 차이
function hello(name = 'World') {
  console.log(`Hello, ${name}!`)
}

hello(null) // Hello, null!
hello(undefined) // Hello, World!

기타

// 문자열 → 숫자
const str = '1000000'
const str2num = +str // 또는 Number(str)

// 숫자 → 문자열
const num = 1_000_000
const num2str = `${num}`

// 문자열 합치기
const str2 = '1 문자열'
const stringConcatination = `${str}${num}, ${str2}` // '1000000과 1000000, 1 문자열'

// null, undefined 체크는 || 대신 ?? 사용
const nullableString: string | null = getNullableString()
const result = `${nullableString ?? ''}`

React

컴포넌트 정의

// FunctionComponent.tsx

type Props = {
  ...
}

function FunctionComponent({ ... }: Props) {
  ...
}

export default FunctionComponent

위와 같이 CRA 템플릿에 있는 방식으로 컴포넌트를 정의한다.

클래스 컴포넌트의 사용은 지양한다. 클래스 컴포넌트는 함수 컴포넌트에서 지원하지 않는 일부 컴포넌트 생명주기 함수를 사용하기 위해서 사용한다.

React 17에선 번들 사이즈 최적화를 위해 컴포넌트를 정의할 때 import React from 'react'문을 넣지 않는 것을 권장한다.

컴포넌트 props의 자료형은 PropTypes로 정의하거나 TypeScript로 정의할 수 있다. TypeScript는 자료형을 정적으로 검사하지만, PropTypes는 동적으로 검사한다는 차이가 있다. 근데 TypeScript가 자동으로 PropTypes 코드를 생성한다고 하니 TypeScript만 사용해도 문제없다. 그리고 동적 자료형 검사는 실행 시 약간의 오버헤드가 생긴다.

컴포넌트 props의 기본값을 정의할 땐 defaultProps 대신 ES6의 매개변수 기본값을 사용한다. 그리고 defautProps는 나중에 deprecated될 예정이라고 한다.

type Props = {
  prop1: number | undefined
  prop2?: number
}

prop1은 컴포넌트에 명시적으로 전달해야 하지만 number 또는 undefined 일 수 있는 값이고, prop2는 전달하지 않아도 되는 number형 데이터다. 이 props가 컴포넌트 내부에서 매개변수로 활용될 때의 자료형은 둘 다 number | undefined로 동일하다.

(type 이름은 논의 필요)

메모이제이션

컴포넌트 props로 넘겨주는 값은 useMemo()를 통해, 함수는 useCallback()를 통해 메모이제이션하는 것을 권장한다. 만약 props를 받는 컴포넌트가 memo()로 감싸진 컴포넌트(순수 컴포넌트)면 무조건 메모이제이션을 해야 한다.

이벤트 핸들러 네이밍

import { MouseEvent as ReactMouseEvent, useCallback, useState } from 'react'

function FunctionComponent() {
  const [searchTerm, setSearchTerm] = useState('')

  // 이벤트 핸들러 함수
  const handleClickSearchButton = useCallback(
    (e: ReactMouseEvent<HTMLElement, MouseEvent>) => {
      ...
    },
    [...]
  })

  const handleChangeSearchTerm = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      setSearchTerm(e.target.value)
    },
    [],
  ) 

  // 이벤트 핸들러 prop
  return (
    <>
      <SearchInput searchTerm={searchTerm} onChange={handleChangeSearchTerm} />
      <SearchButton onClick={handleClickSearchButton} />
    </>
  )
}

export default FunctionComponent

이벤트 핸들러를 받는 prop 이름은 on___으로 짓고, 이벤트 함수 이름은 handle___로 짓는다.

Custom Hook 정의

// use___.ts

type Options = {
  ...
}

function use___({ ... }: Options) {
  ...
}

export default use___

Custom Hook의 이름use___로 지정한다. Custom Hook 안에선 JSX 사용을 지양하고, 다른 React Hook이나 JavaScript 로직 위주로 작성한다.

(type 이름은 논의 필요)

상태 관리

웬만하면 전역 상태 대신 지역 상태로 처리하는 것을 권장한다. 이유는 다른 언어에서 전역 변수 대신 지역 변수 사용을 권장하는 것과 비슷하다. 전역 상태를 사용하려면 React Context API를 사용한다. (Redux, Mobx 대신 React Context만 쓸지 논의 필요)

한 상태를 여러 컴포넌트가 공유해야 할 땐 상태 관리 로직을 공통된 상위 컴포넌트로 올리는 것을 권장한다.

Next

페이지 컴포넌트

// pages/index.tsx

function HomePage() {
  return <div>Welcome to Next.js!</div>
}

export default HomePage

위와 같이 pages 폴더에 포함된 페이지 컴포넌트 이름___Page로 지정한다.

페이지 컴포넌트 경로는 원활한 수정과 불필요한 diff 생성을 방지하기 위해 [id].tsx 대신 항상 [id]/index.tsx 파일을 통해 동적 라우팅을 정의한다.

Styled Components

모든 styled 변수는 해당 컴포넌트 파일에 정의하는 것을 권장한다.

다른 컴포넌트랑 같은 css를 공유할 땐 export를 활용한다? (아님 src/components/layouts에 모아 놓을까?)

Wrapper

import styled from 'styled-components'
import { ThirdPartyStyledComponent } from 'third-party-css-library'

const StyledComponent = styled(ThirdPartyStyledComponent)`
  margin: 1rem;
  ...
`

DOM 트리가 깊어질수록 렌더링 속도가 느려지기 때문에, 외부 CSS 라이브러리에 있는 컴포넌트는 Wrapper 컴포넌트를 사용하지 않고 가능하면 해당 컴포넌트를 상속해 스타일을 적용하는 것을 권장한다.

Apollo GraphQL

Apollo Codegen을 위해 Query와 Mutation의 이름(operation 이름)은 익명이지 않고 유일해야 한다.

useQueryfetchPolicycache-first사용하고, 캐시 값을 최신 상태로 업데이트 해야 하면 refetch사용한다. 캐시 값을 최신 상태로 업데이트하기 위해서 fetchPolicy: network-only를 사용하진 않는다.

gql 변수

// src/graphql/queries/User.ts

const GET_USER = gql`
  query User($id: Int!) {
    user(id: $id) {
      id
      name
    }
  }
`

export default GET_USER

모든 gql 변수는 src/graphql 폴더 안의 fragments, mutations, queries 중 적절한 곳에 정의한다.

파일 이름은 operation 이름이랑 일치시킨다.

client query는 /operations/queries/client/ 에 정의한다.

client mutation은 없다. Reactive variable을 업데이트 하는 방법은 useMutation이 아니기 때문이다. (공식 문서와 중복되는 내용인데 뺄까)

  • gql 변수 이름 : UPPER_SNAKE_CASE

  • operation 이름 : PascalCase 명사형 (Apollo Codegen이 operation 이름 단위로 type을 생성하기 때문에)

  • GraphQL 필드 이름 : camelCase

Fragment

GraphQL 필드 중 반복되는 필드 집합은 Fragment로 정의하는 것을 권장한다. 중복되는 로직을 함수, React 컴포넌트로 정의하는 맥락과 비슷하다.

CSS

CSS Wrapper, Container 네이밍 규칙

Normalize.css, Sanitize.css 로 브라우저별 CSS 스타일을 통일한다.

Prettier (취향)

  • single quote : shift 누르기 귀찮아서?
  • no semicolon : 불필요한 코드 제거
  • print width : 120. 잦은 줄바꿈 방지

React Hook Form

Controller

as prop는 HTML5 기본 태그에 대해서, render prop는 사용자 지정 컴포넌트에 대해서 사용한다.

입력 기본값은 useFormdefaultValues에서 설정한다.

profile
이유와 방법을 알려주는 메모장 겸 블로그. 블로그 내용에 대한 토의나 질문은 언제나 환영합니다.

0개의 댓글