React.js & TypeScript (FC)

강정우·2023년 2월 6일
0

TypeScript

목록 보기
2/23
post-thumbnail

리액트 프로젝트 타입스트립트로 만들기

  • cra(create-react-app)의 공식문서 중 타입스크립트 추가하기에 들어가서
npx create-react-app 앱이름 --template typescript
  • 이렇게 해주면 된다. 또한 ts 템플릿 추가, 업뎃, 삭제를 위해 npx 버전은 항상 최신을 유지하라고 적혀있다.

  • 이제 TS템플릿을 기존 프로젝트에 추가하든 cra with ts로 하든 완성이 되었다면 위와 비슷한 모습을 볼 수 있다. (필자는 next로 함)

  • 보면 .ts.tsx가 있는 것을 볼 수 있는데 .tsx는 그 안에서 JSX문법을 사용하기 때문이다. 그럼 이제 기존 ts와 tsx의 차이점을 알아보자

참고 1. 컴파일링

  • 사실 우리가 안해도 되지만 짚고 넘어가자

  • 앞서 포스팅한 것처럼 TS는 JS로 한 번 컴파일을 해줘야하는데 개발서버을 확인할 때 이 작업이 필요하다. 하지만 우리가 하는건 아니고 리액트가 알아서 해준다.

  • 단순히 이 개발 서버는 자바스크립트 코드를 받아 파일들을 하나로 묶는 작업을 수행하고 최적화 단계는 별도로 제공하였는데 지금은 이런 과정에 더해 추가적으로 타입스크립트를 자바스크립트로 컴파일하는 단계가 있다.

  • 역시 보이지 않는 곳에서 진행되기때문에 그래서 우리가 타입스크립트를 자바스크립트로 직접 변환할 필요가 없다

참고 2. 의존성

  • TS는 TS용 컴파일러 언어로 이 프로젝트 안에 설치되어 있다. 그래서 이 프로젝트에서 TS를 사용할 수 있다.

  • 보면 react, react-dom은 JS의 프레임워크이다. 그렇다면 우리가 TS 및 개발 툴이 제공하는 기능과 자동 완성 같은 기능을 사용하기 위해서는 TS를 JS로 변환을 해줘야하는데
    @types 패키지가 바닐라 JS 라이브러리와 TS 프로젝트 사이에서 번역기 역할을 한다.

참고 3. 프로젝트 init

  • 항상 프로젝트를 만들면 기본적으로 들어있는 것들이 있는 다 걷어내면 된다.

FC(Functional Component)

문제점 1. TS의 에러

function Todos(props) {
    	...

  • TS는 특이하게 사용하지 않은 불필요한 코드에 대하여 에러를 내뿜는다.
  • 물론 이러한 에러를 어디까지 경고하도록 할건지 tsconfig.json에서 설정할 수 있다.
const Todos = (props:{items:string[], children:any}) => {

문제점 2. 중복되는 코드

  • 그리고 우리는 중요한 특수 props인 children 프롭이 있다. 그럼 모든 컴포넌트에 저걸 넣어줘야할까? 아니다. @types/react에 정의되어있는 React를 사용하면 된다.

FC의 작동원리

  • FC에 대하여 조금 자세히 설명하자면 함수형 컴포넌트를 바로 제너릭으로 변환하여 사용하는 것이다.
    좀 더 자세히 설명하면 우리가 만든 함수형 컴포넌트에서 몇 가지 설정을 추가하여 리액트 함수형 컴포넌트로 동작하도록 만들어 children 같은 기본 props를 사용할 수 있도록 하는 것이다. [참고 참조...] 그런 다음 새로운 props를 추가로 정의하면 된다.

    즉, FC === generics이다.

FC사용법

const Todos:React.FC = (props) => {
  • 이렇게 React.FC라고 타입을 정의함으로써 이 함수가 함수형 컴포넌트로 동작한다는 걸 명확히 하는 것이다.
  • 이렇게 하면 비로소 우리가 흔히 쓰던 props가 되는 것이다.
  • 그리고 이 FC 는 우리가 만든 프로퍼티와 합칠 수 있는데 generics에 쓰던 <> 를 사용하여 가능하다.
    하지만 사용법은 조금 다른다.
const Todos: React.FC<{ items: string[] }> = (props) => {
  • FC가 이미 generic 타입이기 때문에 <>에 들어갈 값은 데이터 타입이 아닌,
    내부적으로 사용되는 generic 타입에 구체적인 값을 집어넣어줘야 한다.

그렇다면 그냥 TS가 inference하게 두면 좀 더 편리하지 않을까?

=> 우리가 추가한 props를 받아서 기본적인 props의 성질들을 사용할 수 있도록 함수를 정의하고 타입스크립트에게 이 함수를 내부적으로 어떻게 처리해야 하는지 알려줘야하기 때문에 구체적으로 정의를 해주어야한다.

  • 그래서 이렇게 <>를 사용하여 내가 원하는 props의 필드값도 지정해줄 수 있다.
    무튼 그래서 함수형 컴포넌트마다 props에 대한 정의가 다르기 때문에 "제네릭"이라고 부를 수 있는 것이다.

  • 단, 이렇게 되면 TS의 특징으로 해당 <Todos>를 사용하는 컴포넌트 함수에는 빨간색 밑줄이 뜬다.
    그 이유는 반드시 있어야할 items의 props이 누락되었기 때문이다.

  • 이때 당연하게 <Todos>에 items props을 추가해도 되지만

  • 이렇게 ?를 사용하여 optional로 처리하여도 된다. 이때, 또 TS의 특징이 나오는데 이렇게 optional로 처리하려면 반드시 try-catch처럼 없는 경우를 또 처리해주어야한다.

  • 그래서 컴포넌트를 잘못된 방식으로 사용하는 일, 예를 들어 컴포넌트에 필요한 props를 일부만 넘기거나 하는 일은 발생할 수 없다

정리하면 새롭게 만들 모든 컴포넌트에 React.FC 표기를 추가하면 된다.
props도 새롭게 만들어 사용한다면 이 제네릭 타입을 추가하고 <> 안에 새로운 props의 타입을 정의하면 된다.
그래서 React.FC의 기본적인 props성질에 <"객체">로 구조분해한 후 내부에있는 "객체"를 더하여 하나의 완성된 props을 만드는 것이라 생각하면 편하다.

key prop

  • 앞에 FC와 generics를 이용하여 하나의 코드를 만들어보자
    대충 부모 컴포넌트에서 data를 만들고 자식 컴포넌트에서 props로 받아와서 최종 html 코드로만 구성한 코드이다.
const todos = [
  new Todo("Learn React"),
  new Todo("Learn TypeScript")
];

return (
  <>
    <Todos items={todos}/>
    </>
const Todos: React.FC<{ items: Todo[] }> = (props) => {
    return (
        <ul>
            {props.items.map(item => <TodoItem key={item.id} text={item.text}/>)}
        </ul>
    )
}
const TodoItem:React.FC<{text:string}> = (props) => {
    return(
        <li>{props.text}</li>
    )
}
  • 위 코드 뭉치들은 에러가 없는 코드들인데 한가지 이상한 점이 있다. 원래라면 사용하지 않는 prop이라고 2번째 코드에 key 부분에 빨간 밑줄이 있어야하는데 없다. 그 이유는 지금은 삭제되었지만 children prop처럼 key prop은 prop이 갖고있는 기본 속성중 하나이다.

참고

  • React18 버전부터 TS의 children 프롭이 삭제되었다고 한다. 또 그냥 FC만 사용하는 것도...
import {FC} from "react";

const Todos: FC<{ items: string[] }> = (props) => {
    return (
        <ul>
            {props.items.map(item => <li key={item}>{item}</li>)}
            {props.children}
        </ul>
    )
}
  • 즉, 예전에는 위의 예제코드처럼 사용이 가능했지만 지금은 안 된다는 것이다.

  • 그래서 정 사용하고 싶다면 ReactNode 객체를 가져와서 사용하면 된다고 한다.

  • 본문

참고 1

Before

import * as React from 'react';

type Props = {};
const Component: React.FC<Props> = ({children}) => {...}

After

import * as React from 'react';

type Props = {
  children?: React.ReactNode
};
const Component: React.FC<Props> = ({children}) => {...}

After 2 (stop using React.FC altogether)

import * as React from 'react';

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

function Component({children}: Props): React.ReactNode {
  ...
}

참고 2

  • FC없이 사용하고 싶을 때
  • 만약 FC없이 TS, react를 사용하고 싶다면 react가 <>를 컴포넌트로 인식하여 빨간 줄이 뜨는데 이것을 해결하는 방법은 그냥 "," 면 된다.
const TodoItem = <{T,}>(props:T) => {
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글