본문 바로가기

언어(JS,TS)/React

React [note: 컴포넌트 코드 순서 컨벤션]

React 컴포넌트 코드 순서 컨벤션 고민

회사 코드를 보다 보니 코드들이 항상 일정한 순서로 배치가 되면 보기가 편하고 리팩토링하기도 좋을것 같다는 생각이 들었다
이런 체계적인 접근 방식은 코드를 읽고 이해하기 쉽게 만들고, 디버깅을 간소화하며, 개발자가 구성 요소의 특정 부분을 빠르게 찾아 업데이트할 수 있으므로 향후 수정도 용이해집니다

그래서 상세한 순서와 왜 적절할지와 그 이유를 정리해 보았다

순서 예시

// 1. 'use client'등의 선언 (필요한 경우)
// 2. import
// 3. 타입 정의 (필요시 외부로 빼기)
// 4. 컴포넌트 내부에서 사용하는 상수 (필요시 외부로 빼기)
// 5. 컴포넌트 내부에서 사용하는 유틸 함수 (필요시 외부로 빼기)
// 6. 메인 컴포넌트 정의
// 7. 라우터 관련 
// 8. useRef
// 9. computed props value
// 10. 상태 정의
// 11. custom hooks
// 12. useMemo/useCallback 같은 훅
// 13. useEffect
// 14. 컴포넌트 반환시 사용하는 변수
// 15. 핸들러 함수 (이벤트 핸들러)
// 16. 컴포넌트 반환 (JSX)
// 17. child comp
// 18. 기본 export

예시 코드

// 1. 'use client'등의 선언 (필요한 경우)
'use client';

// 2. import
import { useState, useEffect, useMemo } from 'react';

import { useRouter } from 'next/navigation';

import { useCustomHooks } from '@/hooks';

// 3. 타입 정의 (필요시 외부로 빼기)
type Props = {
  title: string;
};

// 4. 컴포넌트 내부에서 사용하는 상수 (필요시 외부로 빼기)
const LIMIT = 10;

// 5. 컴포넌트 내부에서 사용하는 유틸 함수 (필요시 외부로 빼기)
const formatDate = (date: Date) => date.toLocaleDateString();

// 6. 메인 컴포넌트 정의
const MyComponent = ({ title }: Props) => {
  // 7. 라우터 관련 코드
  const router = useRouter();
  // 8. useRef
  const divRef = useRef(null)

  // 9. computed props value
  const subTitle = '[sub] ' + title + '이란?';

  // 10. 상태 정의
  const [count, setCount] = useState(0);
  const [customTitle, setCustumTitle] = useState(subTitle);

  // 11. custom hooks
  const { customFunc } = useCustomHooks(count);

  // 12. useMemo/useCallback 같은 훅
  const isStateOne = useMemo(() => count === 1, [count]);

  // 13. useEffect
  useEffect(() => {
    console.log('Component mounted');
  }, []);

  // 14. 컴포넌트 반환시 사용하는 변수
  const pageName = 'about';

  // 15. 핸들러 함수 (이벤트 핸들러)
  const handleClick = () => {
    setCount((prev) => prev + 1);
    formatDate(new Date());
  };

  const handleNavigate = () => {
    router.push(`/${pageName}`);
  };

  // 16. 컴포넌트 반환 (JSX)
  return (
    <div className="p-4 bg-gray-100 rounded-lg" ref={divRef}>
      <h1 className="text-xl font-bold">{title}</h1>
      <h2 className="text-xl font-bold">{customTitle}</h2>
      <p className="text-gray-600">Count: {count}</p>
      <button className="mt-2 p-2 bg-blue-500 text-white rounded" onClick={handleClick}>
        Increase Count
      </button>
      <button className="mt-2 p-2 bg-green-500 text-white rounded" onClick={handleNavigate}>
        Go to &quot;{pageName}&quot;
      </button>
      <ChildComp onClickChildComp={customFunc} />
    </div>
  );
};

// 17. child comp
const ChildComp = ({ onClickChildComp }: { onClickChildComp: () => void }) => {
  return <button onClick={onClickChildComp}>test item add!</button>;
};

// 18. 기본 export
export default MyComponent;

이유

컴포넌트의 하단으로 갈수록 상단에 영향을 받을 수 있는 코드라고 생각한다.
그래서 영향을 가장 적게 받을 가능성이 있는 순서대로 배치하는 것이 좋다고 생각한다.

7. 라우터 관련 코드

라우터 관련 코드는 컴포넌트에서 가장 상단에 위치하는 것이 좋다고 생각했다.
그 이유는 영향을 받을 수 있는 코드가 없다고 판단했기 때문이다. 그래서 가장 상단에 배치했다.

8. computed props value

props를 통해 계산하는 변수의 경우, state의 기본 값에도 사용될 수 있기 때문에 state보다 상단에 위치해야 한다.
또한, props 외에 다른 코드의 영향을 받을 가능성이 거의 없기 때문에 최상단에 위치하는 것이 좋다고 생각했다.

9. 상태 정의

라이프사이클을 관리하는 hook 중 상태를 저장하는 useState는 기본 값을 제외하면 다른 코드의 영향을 받는 경우가 거의 없다.
그러나 props의 데이터가 가공되어 기본 값으로 사용될 수 있기 때문에 computed props value보다 하단에 위치했다.

12. useEffect

return문과 가깝게 또는 hook의 가장 마지막에 useEffect를 배치하는 것이 좋다고 생각한다.
그 이유는 렌더링될 때 useEffect가 실행되지 않고, 렌더링 후에 실행되기 때문이다.

13. 컴포넌트 반환 시 사용하는 변수

컴포넌트 반환 시 사용하는 변수는 반환 코드에서 반복적으로 사용되거나 이벤트 핸들러에서 공통적으로 사용될 수 있다.
따라서 이벤트 핸들러의 상단에 위치하는 것이 좋다고 생각한다.

14. 핸들러 함수 (이벤트 핸들러)

이벤트 핸들러는 컴포넌트 반환과 연관되어 있기 때문에 return문 바로 상단에 위치해야 한다고 생각한다.
선언할 때는 컴포넌트 내부에서 handle을 접두사(prefix)로 사용하고, props로 내려줄 때는 다른 이벤트와 비슷한 on~~ 형식으로 명명하는 것이 좋다고 생각한다.

ref:

'언어(JS,TS) > React' 카테고리의 다른 글

React [정보 : Link vs <a>태그]  (0) 2022.03.25