Best Practice 17

17 All React Best Practice

1. Constants

1. 숫자 값은 변수로 선언하고 export 해서 쓰자.

  • MAX_FREE_TODOS라는 변수를 선언해서 숫자로 쓰이는 값들은 언제 어디서 재사용될지 모르니 상수화 시키자.
lib/constants.js
export const MAX_FREE_TODOS = 3;
Component.jsx
import { useState } from "react";
 
export default function Best_Practice_17() {
  const [todoContents, setTodoContents] = useState("");
 
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
 
        if (todos.lenth === MAX_FREE_TODOS && !isAuthenticated) {
          alert(`you need to sign in to add more than ${MAX_FREE_TODOS} todos`);
          return;
        }
        // 원래라면 MAX_FREE_TODOS가 아니라 3이라는 숫자가 들어가 있었다.
      }}
    ></form>
  );
  return <div>Best_Practice_17</div>;
}

2. 스트링또한 상수화

  • 문자열또한 변순를 선언해서 재사용성을 높이자.
lib/constants
export const SENSITVE_WORDS = ["password", "credit"];
Component
import { useState } from "react";
 
export default function Best_Practice_17() {
  const [todoContents, setTodoContents] = useState("");
 
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
 
        if (
          todoContents.includes("password") ||
          todoContents.includes("credit card")
        ) {
          alert("Please do not use sensitive information");
          return;
        }
      }}
    ></form>
  );
  return <div>Best_Practice_17</div>;
}

3. useState의 initialState도 상수화

  • 유의해야하는것은 컴포넌트 바깥에서 선언해야 한다. 컴포넌트가 리렌더링 될때마다 다시 새로 만들어질것인데 성능측면에서 좋지 않다.
Component
import { useState } from "react";
 
const initialState = [
  { id: 1, content: "Buy Groceries", completed: false },
  { id: 1, content: "Buy Groceries", completed: false },
  { id: 1, content: "Buy Groceries", completed: false },
];
 
export default function Best_Practice_17() {
  const [todoContents, setTodoContents] = useState(initialState);
 
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
 
        if (
          todoContents.includes("password") ||
          todoContents.includes("credit card")
        ) {
          alert("Please do not use sensitive information");
          return;
        }
      }}
    ></form>
  );
  return <div>Best_Practice_17</div>;
}

2. Folder Structure

서비스가 커지기 전에, 이제 막 세팅하는 프로젝트에 적용하는 모범적인 폴더구조이다. 또한 폴더구조에는 정답이 없다는 것을 기억하자. 하지만 초기에 폴더구조를 고민하면서 무의미한 시간을 보내고 개발 생산성을 떨어뜨리지 않도록 주의하자. 나중에 서비스가 커지거나 팀 컨벤션이 생기더라도 유연하게 적용할 수 있는 폴더구조에 대해서 알아보자.

  • assets : 프로젝트에서 사용되는 리소스들
  • components : form, header, button과 같은 컴포넌트들, 또한 하위 폴더를 생성할 수 있으며 폴더는 UI, 도메인, 기능 별로 그룹화 할 수 있다.
  • lib : utils, hooks, types, constants
  • context : ContextAPI, Redux와 같은 상태 관리와 관련한 폴더

3. Components

디자이너로부터 디자인 시안을 받고 사용자의 액션을 담을 수 있는 리액트 웹 페이지를 만든다고 했을 때, 우리는 과연 컴포넌트를 어떻게 구성할 것인가?

1. 재사용성을 기준으로

  • 재사용이 가능하다면 독립적인 컴포넌트로 설계할 수 있을 것이다. 예를 들어, 버튼과 같은 컴포넌트.
    • 버튼 컴포넌트에는 버튼의 타입, 아이콘 유무, 이벤트 핸들러, children prop등을 유연하게 받을 수 있도록 설계하자.

반대로 앱에서 단 한 번만 사용된다고 했을 때, 컴포넌트로 분리하지 않는 것이 맞는 원칙인가?

2. 재사용하지 않더라도.

앱에서 단 한 번만 사용된다고 했을 때, 가독성을 위해 혹은 반대로 중요하지 않은 컴포넌트 일 경우 독립적인 컴포넌트로 만들 수 있다.

분리한 컴포넌트
export const App = () => {
  return (
    <div>
      <BackGroundHeader />
      <div>
        <main>main</main>
        <button>todo button</button>
      </div>
    </div>
  );
};
분리하지 않은 컴포넌트
export const App = () => {
  return (
    <div>
      <header>back ground header 입니다.</header>
      <div>
        <main>main</main>
        <button>todo button</button>
      </div>
    </div>
  );
};
  • 적절한 예시처럼 안 보일 수도 있지만, 실제로 tailwindCSS와 같이 인라인 스타일을 작성한다거나 이미지, 텍스트가 포함되어 있으면 불필요한 공간만 차지하고 컴포넌트의 가독성을 헤칠 수 있다.

또는 각자의 선호도에 따라 컴포넌트를 분리할 수도 있다

분리하지 컴포넌트
export const App = () => {
  return (
    <div>
      <BackGroundHeader />
      <main>
        <Header />
        <TodoList />
        <Sidebar />
      </main>
      <Footer />
    </div>
  );
};
분리한 컴포넌트
export const App = () => {
  return (
    <div>
      <BackGroundHeader />
      <Main />
      <Footer />
    </div>
  );
};
  • 둘의 차이점은 Main이라는 컴포넌트를 만들고 헤더, 투두리스트, 사이드바를 안으로 숨긴 것인데 각각 장단점이 있는 것 같다.

    Main에서 일어나는 일을 보려면 Main 파일을 따로 열어야 하는 단점이 있지만 대신, 분리되어 있다는 것을 한눈에 볼 수 있다. 기능적으로 별 차이 없지만 상황에 맞게 고려해서 잘 적용하면 좋을 것 같다. 이 컴포넌트가 루트 App 컴포넌트가 아니라 어떤 비즈니스 로직을 담당하고 있는 컴포넌트라면 난 Main이라는 컴포넌트를 만들고 분리할 것 같다. 하지만 사이즈가 작은, 또는 별 다른 로직이 없다면 굳이 안나눌수도 있을 것 같다.

4. 쓸데없는 마크업 하지 말자!

  • 필요없는 div 태그
Button
export const Button = () => {
  return (
    <div>
      <button>button입니다~</button>
    </div>
  );
};
  • 조금 더 상세한 예시를 봐보자

두 개의 버튼이 UI에 표시되는 경우와 한 개의 버튼만 표시되는 경우가 있을 때 상위 div에 간격을 설정해야하는데, 버튼을 div로 감싸고 margin을 주는 방식으로 처리하면 불필요한 div태그를 생성 할 수 있다.

isAuthenticatedUI
export const App = () => {
  const isAuthenticated = true;
  return (
    <div className="space-y-2">
      {isAuthenticated ? (
        <button>button입니다~</button>
      ) : (
        <>
          <button>button 입니다.</button>
          <button>button 입니다.</button>
        </>
      )}
    </div>
  );
};

5. 재사용되는 컴포넌트에 레이아웃 스타일을 추가하지 말자.

재사용되는 H1 컴포넌트를 만들었다고 가정해보자. 똑같은 스타일을 가졌지만 margin이 없기 때문에 사용하는 위치에 따라 margin을 유연하게 적용해주고 싶다. 어떻게 해야할까?? div 태그를 만들어서 margin이 필요한 곳에 감싸줘야 할까??

margin 추가
export const Component = () => {
  return (
    <>
      <div className="mb-5">
        <H1>Title</H1>
      </div>
      <main>main</main>
    </>
  );
};

틀렸다고 말할 순 없지만 더 깔끔한 방법이 있다.

  • H1 컴포넌트에 className prop을 받아서 레이아웃이 필요한 경우에 동적으로 결합 하도록 설계해주면 됨.

아래는 tailwindCSS를 사용하는 예시이다. 다른 스타일링 도구를 사용하더라도 비슷하다. prop만 잘 받도록 구현해주면 된다.

H1
 
type H1props = {
  children:React.ReactNode
  className?:string
}
 
export default function H1({className,children}:H1props){
  return <h1 className={cn("프로젝트에서 정의한 H1 스타일들",className)}>{children}</h1>
}

6. 타입스크립트를 사용하자

  • 개발자의 실수를 방지 (오타)
  • 허용되지 않는 Props를 입력한 경우 알려준다.
  • 인텔리전스 제공

어쨌든 타입스크립트를 사용하는 것이 최선의 방법인 것 같음.

7. Keep Component Simple