React.FC에 대하여

React.FC에 대하여


들어가며

처음 React + typescript 조합을 사용했을 때 컴포넌트를 만들기 위해 아래와 같은 코드를 많이 사용했습니다.

// Parent
type Props = {
  count: number;
};

const Parent: React.FC<Props> = ({ count }) => {
  return <div>Parent {count}</div>;
};

export default Parent;


// App
import Parent from "./Parent";

function App() {
  return (
    <div className="App">
      <Parent count={10} />
    </div>
  );
}

export default App;

강의나 여러 예제에서 React.FC를 많이 사용했기 때문에 “함수형 컴포넌트를 만들 때 React.FC를 사용해야 되는구나”라는 생각을 갖게 되었는데요

최근에 React.FC 사용을 제한하는 추세다.라는 글 을 보게 되었습니다.

무슨일 인고 궁금하니 내용을 읽어보면서 알아보는 시간을 가져보겠습니다.

각 내용 제목은 제가 직역을 해서 어색할 수 있으니 양해 부탁드립니다.

일반 함수와 다른 점

1. React.FunctionComponent is explicit about the return type, while the normal function version is implicit (or else needs additional annotation).

React.FunctionComponent는 명시적이다. 반환하는 type에 대해서. 반면에 일반 함수 버전은 암시적이다 혹은 추가로 *annotation(타입 지정)이 필요하다.)

*annotation

React.FunctionComponent가 명시적이란 말이 무슨 뜻일까요? vscode를 켜고 React.FC에 어떤 타입이 지정되어 있는지 들어가 봤습니다.

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
	(props: P, context?: any): ReactElement<any, any> | null;
	propTypes?: WeakValidationMap<P> | undefined;
	contextTypes?: ValidationMap<any> | undefined;
	defaultProps?: Partial<P> | undefined;
	displayName?: string | undefined;
}

@types/react에 React.FC의 5가지 type이 정의되어 있습니다.

2. It provides typechecking and autocomplete for static properties like ‘displayName’, ‘propTypes’, and ‘defaultProps’.

displayName과 propTypes 그리고 defaultProps와 같은 정적 프로퍼티를 위한 자동완성과 typechecking을 제공합니다.

이 prop들이 어떻게 사용되는지 한번 알아봅시다.

1. displayName

type Props = {
  count: number;
};

const Parent: React.FC<Props> = ({ count }) => {
  return <div>Parent {count}</div>;
};

Parent.displayName = "Hello World";

export default Parent;

display는 문서에서 쉽게 찾을 수 있었는데요 React.Component – React 일반적으로 명시적으로 사용할 필요는 없지만 디버깅용으로 사용된다고 합니다.

2. propTypes, defaultProps

Typechecking With PropTypes – React

propTypes와 defaultProps는 문서에 친절하게 설명되어 있는데 defaultProps는 컴포넌트 기본값을 정의한다고 생각하시면 됩니다. 공식 문서 예제는 클래스 형으로 되어있지만 함수형으로 하면 이렇게 표현할 수 있습니다.

객체 기본값으로 사용

type Props = {
  count: number;
  myName?: string;
};

const Parent = ({ count, myName = "hyunki" }: Props) => {
  return (
    <div>
      Parent {myName} {count}
    </div>
  );
};

이런 defaultProps가 React.FC에서 문제가 된다고 합니다. 왜 그런지 살펴보겠습니다.

예제는 제가 만든 예제로 조금 바꿔보겠습니다.

React.FC로 정의하지 않음

type Props = {
  count: number;
  // 위 예제에서는 optional로 주었지만 defaultProps를 이용하기 때문에 삭제했습니다.
  myName: string;
};

const Parent = ({ count, myName }: Props) => {
  return (
    <div>
      Parent {myName} {count}
    </div>
  );
};

Parent.defaultProps = {
  myName: "hyunki",
};

export default Parent;


// App
import Parent from "./Parent";

function App() {
  return (
    <div className="App">
	  // 에러없이 편안함..
      <Parent count={10} />
    </div>
  );
}

export default App;

React.FC로 정의

// Parent
type Props = {
  count: number;
  myName: string;
};

const Parent: React.FC<Props> = ({ count, myName }) => {
  return (
    <div>
      Parent {myName} {count}
    </div>
  );
};

Parent.defaultProps = {
  myName: "hyunki",
};

// App
import Parent from "./Parent";

function App() {
  return (
    <div className="App">
	  // myName 속성이 없다고 에러...
      <Parent count={10} />
    </div>
  );
}

export default App;

제대로 동작하지 않는걸 확인할 수 있습니다. 블로그나 트위터에서도 hooks의 등장, 이에 따른 함수형 컴포넌트의 사용 등으로 인하여 제가 처음에 작성한 객체 기본값으로 사용을 권장한다고 합니다.

3. Before the React 18 type updates, ‘React.FunctionComponent’ provided an implicit definition of ‘children’ (see below), which was heavily debated and is one of the reasons 

React18 type 업데이트전에는 React.FunctionComponent는 children의 정의를 암시적으로 제공 했었는데 이는 논란이 많았으며 React.FC가 CRA(create-react-app) Typescript에서 템플릿에서 삭제된 이유 중 하나입니다.

children을 암시적으로 제공했다..? 위에서 저희가 살펴본 React.FC type에는 children은 없었는데 이런 children을 암시적으로 제공했다는 뜻일까요? 코드로 확인해봅시다.

React 18 버전에서는 type오류가 납니다.

// Parent
type Props = {
  count: number;
  myName?: string;
};

const Parent: React.FC<Props> = ({ count, myName }) => {
  return (
    <div>
      Parent {myName} {count}
    </div>
  );
};

export default Parent;

// App
import Parent from "./Parent";

function App() {
  return (
    <div className="App">
      // IntrinsicAttributes & Props' 형식에 'children' 속성이 없습니다
      <Parent count={10}>
        <p>나는 children 입니다.</p>
      </Parent>
    </div>
  );
}
export default App;

하지만 React17에서는 에러가 발생하지 않고 그냥 넘어갑니다. 오류를 줄이기 위해 typescript를 도입하는 입장에서 이런 이슈는 조금 크리티컬 한 것 같습니다.

마치며

지금까지 React.FC 사용 제한이라는 내용을 살펴봤습니다. 많은 사람들이 사용하기에 일반적으로 쓰이는줄 알았고 많이 쓰던 React.FC에 대한 배신감(?)도 들면서 한편으로는 깊게 React를 사용 해봤다면 충분히 체감할 수 있었던 이슈였다고 생각합니다.

읽어주셔서 감사합니다.

참고문서


    추천 글