React.FC, 안 써도 괜찮은 것 맞아?

유키미아우·2023년 12월 13일
0

게임에 새롭게 입문한 뉴비시절을 떠올려본다. 내가 선택할 스킬이 아직도 강한지, 최근 패치로 인해 너프 당했는지 먼저 최신트렌드를 살펴야 나중에 후회가 없는 법이다.

타입스크립트 첫 적용시 상황도 이와 흡사했다. 내 JS + React 프로젝트에 마이그레이션해보기 전에 먼저 기존 숙련자들의 사용 트렌드가 알아보고 싶었다. 따라서 여러 학습매체의 예시를 관찰했다.

그런데 가장 기초적인 부분에서부터 선뜻 이해가 안되는 부분이 튀어나왔다. 각종 도서와 강의들 사이에서 함수 컴포넌트의 타입지정용인 듯 보이는 React.FC가 일관되게 쓰이지 않는 것이었다.
일단은 사용하지 않는 패턴에 따라서 마이그레이션 작업을 진행하였으나, 찝찝함이 가시지 않는다. React.FC는 당췌 무엇이고, 왜 개발자의 선호에 따라 사용되기도 사용되지 않기도 하는걸까?

크게 사전지식 파트와 사용예시 파트로 나누어 글을 작성해보았다.

上 - 사전지식편

FC 옆에 제네릭은 왜 있는거지?

리액트가 제공하는 함수 컴포넌트의 전용 타입인 react.functioncomponent, 줄여서 FC는 제네릭문법과 깊은 연관이 있다.
제네릭은 코드를 작성할 때 컴파일때 타입을 동적으로 결정할 수 있도록 하는 TypeScript의 강력한 기능 중 하나이다.

제네릭을 쉽게 설명하면 타입을 사용하는 쪽에서 지정해줄 수 있도록해서 자유도를 높여주는 문법이다. 물렁물렁한 점토같은 타입을 미리 만들어 놓고 <제네릭> 내부에 강아지타입을 넣으면 강아지타입체커, 고양이타입을 넣으면 고양이타입체커가 되는 느낌과 흡사하다.
그런데 심지어 이런 타이핑이 런타임때가 아닌 컴파일 단계에서 곧바로 적용이 된다. 인터프리터 언어인 JS 환경에서는 상상도 못할 일이다. 😲

리액트로 개발하다보면 아래와 같은 상황이 매우 흔하다.

  • 컴포넌트A의 프랍은 name, id
  • 컴포넌트B의 프랍은 id, userArray
  • 컴포넌트C의 프랍은 children

각 컴포넌트마다 prop이 이렇게 가지각색 다양한데, FC의 내부에서 prop이 뭐가 올지 모르는데 타입지정을 미리하는 것은 불가능할 것이다. 따라서 React.FC의 내부에는 제네릭을 통해 프랍타입의 동적 지정이 가능하도록 세팅되어있다. 제네릭은 그야말로 function component에 딱 들어맞는 기능이 아닐 수 없다.

문제는 children이(었)다

children을 프랍으로 받는 컴포넌트C에 주목해보자.

나는 최근의 사용트렌드를 통해 TS에 입문했기에 곧바로 children의 타입지정을 해주어야겠다 💪고 생각이 들었으나, 예전엔 그럴 필요가 없었다고 한다 (!)

2022년 3월 React 18이 발표되기 전까지는 children이 React.FC 내부에서 이미 암묵적 타입지정이 되어있어 에러가 나지 않았다고 한다.


🔺 지금은 볼 수 있는 이 빨간 줄과 에러가 없었다고 함.

일순간,

편하고 좋은데?

라고 생각할 수 있겠으나 TS사용 의의가 유야무야되는 아래와 같은 문제가 있을 수 있다.

// This component doesn't accept children
const Component: React.FC = () => {
  return <div />;
};
// No error!
<Component>123</Component>;

🔺 children을 안 받는 컴포넌트에 123을 넘겨주었지만 무사통과하는 현상. React.FC 내부에 children의 암묵적 타입지정이 내장되어 있어 벌어지는 문제이다.

이 때문에 다른 프랍들과 마찬가지로 children 역시 엄연히 프랍에 속하는데 특별취급을 받을 이유가 없다는 논란이 일었다고 한다. 심지어 대표적인 React 프로젝트용 부트스트랩 중 하나인 create-react-app이 React.FC를 제거해버릴 정도였다고.

따라서 React.FC를 쓰지말자는 트렌드가 있었으나 React18에서부터 children의 암묵적 지정이 사라졌다. 따라서 다른 프랍들과 다름 없이 아래와 같이 명시적으로 타입선언을 해주도록 변경되었다.


🔺 TS의 목적을 따져봤을 때 오히려 클린한 코드.

위의 children의 암묵적 지정 문제, 그리고 이 글에서는 다루지 않지만 undefined, string, number를 리턴할 수 없었던 문제가 모두 업데이트를 통해 해결되면서 React.FC는 다시 지위를 회복하였다.

그래서 FC는 필수인가?

많은 조사를 해본 결과, FC를 사용한 prop 타입지정은 MUST가 아니며, 평소 기명과 익명 중 어떤 선언방식을 선호하느냐에 따라서 갈린다고 결론내릴 수 있겠다.
기명함수와 익명함수가 가진 각각의 특징에 대해서는 간단히만 톺아보고 넘어가겠다.

기명함수익명함수
function Test() {...}const Test = () => {...}
런타임 이전에 선언 및 할당런타임에 할당
호이스팅 O호이스팅 X
React.FC로 타이핑 불가React.FC로 타이핑하고 싶을시 가능

기명함수와 익명함수 중 어느것이 올바르거나 올바르지 않다는 개념은 없다. React.FC 또한 익명함수 선언식일 경우 "원한다면" 적용가능한 문법인 것이다.

TS를 배우며 참고한 강의자료 중 하나인 Total Typescript의 저자 Matt Pocock은 기명함수 선언식의 장점에 대해 다음과 같이 말한다.

This approach is nicer because it's friendlier to beginners - you don't need to know what React.FC is, or even what type argument syntax is. It's also slightly easier to refactor to a generic component if needed.

결론

나는 평소 기명 함수 선언방식을 즐겨 썼었고, 입문시 크게 도움을 주었던 매체 또한 나와 선호하는 문법이 흡사했던 관계로, 계속해서 React.FC 없이 프랍타입지정을 이어나가기로 했다. 그렇게 해도 문제 되지 않는다는 강력한 확신을 얻을 수 있었기에 보람찬 조사였다.

참고자료:

참고1: https://www.totaltypescript.com/you-can-stop-hating-react-fc
참고2: https://blog.shiren.dev/2022-04-28/
참고3: https://stackoverflow.com/questions/71788254/react-18-typescript-children-fc


下 - 사용예시편

익명함수 + React.FC + 인터페이스/타입을 통한 타입지정

import React from "react";

type ComponentProps = {
  children?: React.ReactNode;
}

const Component: React.FC<ComponentProps> = ({ children }) => {
  return (
    <div>
      {children}
    </div>
  );
};

export default SquaresContainer;

<Component /> // Valid
<Component>test</Component> // Valid

기명함수 + 인터페이스/타입을 통한 타입지정

Component with children optional

type Props = {
  children?: React.ReactNode
};

export function Component({children}: Props) {
  ...
}

<Component /> // Valid
<Component>test</Component> // Valid

etc.

  1. FC만을 디스트럭쳐링으로 가져올 수도 있다.
import { FC } from "react";

const Component: FC<ComponentProps> = ({ children }) => {
  ...

하지만 이름이 짧기도하고, 딱 한곳에서 쓰일 예정이라 여기까지 디스트럭쳐링할 필요는 굳이 없을 것 같기도.

  1. inline 타입지정

또한 간단한 컴포넌트의 경우, 혹은 취향에 따라 디스트럭쳐링 없이 props객체를 이용하는 경우가 있다. 이 때는 type이나 interface 선언 없이 아래처럼 inline으로 타입지정이 가능하다.

  • React.FC
import React from "react";

const Component: React.FC<{ name: string }> = props => {
  return <div>{props.name}</div>;
};
  • non-React.FC
const Component = (props: { name: string }) => {
  return <div>{props.name}</div>;
};
profile
능동적인 마음

0개의 댓글