Published on

코딩 컨벤션 (React + Typescript)

React Component 정의

optional

다음의 템플릿을 기본으로 작성하며, 클래스 컴포넌트 사용은 지양한다.

// FunctionComponent.tsx

type FunctionComponentProps = {
  //...
}

export function FunctionComponent({ ... }: FunctionComponentProps) {
  //...
}

Custom hook 정의

optional

다음의 템플릿을 기본으로 작성하며, 이름은 "use-"로 정의한다.

// useCustom.tsx

type CustomOptions = {
  //...
}

export function useCustom({ ... }: CustomOptions) {
  //...
}

key prop

optional

key prop으로 index를 사용하지 않고, item의 유니크한 id를 사용하는 것을 권장한다.
index를 사용하는 경우 삽입, 삭제시 불필요한 리렌더링이 발생하고 원치않는 렌더링 결과가 발생할 수 있다.
id가 없는 경우에는 uuid를 생성하여 key로 사용하는 것을 권장한다.

// bad
{
  todos.map((todo, index) => <Todo {...todo} key={index} />);
}

// good
{
  todos.map((todo) => <Todo {...todo} key={todo.id} />);
}

JSX 태그 괄호

mandatory

JSX태그를 한 줄 이상으로 정의하는 경우에는 괄호로 wrapping한다.

// bad
return (
  <MyComponent variant="long body" foo="bar">
    <MyChild />
  </MyComponent>
);

// good
return (
  <MyComponent variant="long body" foo="bar">
    <MyChild />
  </MyComponent>
);

// good, when single line
return <MyComponent>{body}</MyComponent>;

Tag Self Closing

mandatory

children이 없는 경우에는 항상 태그를 self closing 한다.

// bad
<span
  key={idx}
  data-label={`${val}${idx + 1}`}
></span>

// good
<span
  key={idx}
  data-label={`${val}${idx + 1}`}
/>

Event Type

mandatory

Event 타입은 React에서 제공해주는 타입을 사용한다. (React.ChangeEvent, React.MouseEvent)

const handleChange = useCallback(
  (event: React.ChangeEvent<HTMLInputElement>) => {
    const val = event.target?.value;
    onChange(val);
  },
  []
);

const handleClick = useCallback(
  (event: React.MouseEvent<HTMLButtonElement>) => {
    event.stopPropagation();
    ...
  },
  []
);

Style Props

mandatory

style prop에 대한 타입은 React에서 제공해주는 타입을 사용한다. (React.CSSProperties)

type ContainerProps = {
  styles: React.CSSProperties;
};

export function Container({ styles }: ContainerProps) {
  return <div style={styles}>Text here</div>;
}

Generic Props

optional

props가 multiple 타입인 경우 Generic으로 처리가능하다면 Generic을 적극 사용한다.

type ListProps<T> = {
  items: T[];
  onClick: (value: T) => void;
};

export function List<T extends { id: number }>({ items, onClick }: ListProps<T>) {
  return (
    <div>
      {items.map((item, index) => {
        return (
          <div key={item.id} onClick={() => onClick(item)}>
            {item}
          </div>
        );
      })}
    </div>
  );
}
return (
  <List
    items={[
      {
        id: 1,
        name: 'Bruce',
      },
      {
        id: 2,
        name: 'Clark',
      },
    ]}
    onClick={(item) => console.log(item)}
  />
);

useRef non-null assertion

optional

useRef 사용시 reference에 접근할 때는 항상 null이 아닌 것이 확실하다면 초기값에 non-null assertion Operator(!)를 붙여준다.
그러면 다음과 같이 current에 optional을 붙여줄 필요가 없다. inputRef.current?.focus();

export function DomRef() {
  const inputRef = useRef<HTMLInputElement>(null!);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input type='text' ref={inputRef} >
    </div>
  )
}