2. 리액트 컴포넌트 타입스크립트로 작성하기

이번 섹션에서는 리액트 컴포넌트를 타입스크립트로 작성하는 방법을 알아보도록 하겠습니다.

프로젝트 생성

우선, 타입스크립트를 사용하는 리액트 프로젝트를 만들어볼까요?

타입스크립트를 사용하는 리액트 프로젝트를 만들때는 다음과 같이 명령어를 사용하세요.

$ npx create-react-app ts-react-tutorial --typescript

위와 같이 뒤에 --typescript 가 있으면 타입스크립트 설정이 적용된 프로젝트가 생성된답니다.

만약, 이미 만든 프로젝트에 타입스크립트를 적용하고 싶으시다면 이 링크를 확인해주세요.

이제 프로젝트를 열어보시면 src 디렉터리 안에 App.tsx 라는 파일이 있을것입니다. 타입스크립트를 사용하는 리액트 컴포넌트는 이와 같이 *.tsx 확장자를 사용한답니다. 해당 파일을 한번 열어볼까요?

App.tsx

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

이 컴포넌트를 보시면, const App: React.FC = () => { ... } 와 같이 화살표함수를 사용하여 컴포넌트가 선언되었습니다. 우리가 지금까지 강의에서 다뤄왔던 컴포넌트들은 function 키워드를 사용해서 선언했었는데요, 컴포넌트를 선언 할 때 이렇게 화살표함수를 사용하여 선언해도 무방합니다. 리액트 공식 문서나 해외의 유명 개발자들 (링크1, 링크2)은 보통 function 키워드를 사용하여 함수형 컴포넌트를 선언하는 것이 추세이기에, 이 튜토리얼에서는 지금까지 function 키워드를 사용해왔습니다. 반면 위 코드에서는 화살표함수로 선언이 되었고 React.FC 라는 것을 사용하여 컴포넌트의 타입을 지정했는데요, 이렇게 타입을 지정하는것이 좋을수도 있고 나쁠수도 있습니다.

한번 새로운 컴포넌트를 선언하면서 React.FC 를 사용하고 사용하지 않는것이 어떤 차이가 있는지 알아보도록 하겠습니다.

새로운 컴포넌트 만들기

Greetings 라는 새로운 컴포넌트를 작성해봅시다.

src/Greetings.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
};

const Greetings: React.FC<GreetingsProps> = ({ name }) => (
  <div>Hello, {name}</div>
);

export default Greetings;

React.FC 를 사용 할 때는 props 의 타입을 Generics 로 넣어서 사용합니다. 이렇게 React.FC를 사용해서 얻을 수 있는 이점은 두가지가 있습니다.

첫번째는, props 에 기본적으로 children 이 들어가있다는 것 입니다.

중괄호 안에서 Ctrl + Space 를 눌러보면 확인 할 수 있습니다.

두번째는 컴포넌트의 defaultProps, propTypes, contextTypes 를 설정 할 때 자동완성이 될 수 있다는 것 입니다.

한편으로는 단점도 존재하긴 합니다. children 이 옵셔널 형태로 들어가있다보니까 어찌 보면 컴포넌트의 props 의 타입이 명백하지 않습니다. 예를 들어 어떤 컴포넌트는 children이 무조건 있어야 하는 경우도 있을 것이고, 어떤 컴포넌트는 children 이 들어가면 안되는 경우도 있을 것입니다. React.FC 를 사용하면 기본적으로는 이에 대한 처리를 제대로 못하게 됩니다. 만약에 하고 싶다면 결국 Props 타입 안에 children 을 설정해야하죠.

예를 들자면 다음과 같습니다.

type GreetingsProps = {
  name: string;
  children: React.ReactNode;
};

그리고, React.FC는 (아직까지는) defaultProps 가 제대로 작동하지 않습니다. 예를 들어서 코드를 다음과 같이 작성했다고 가정해봅시다.

src/Greetings.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
};

const Greetings: React.FC<GreetingsProps> = ({ name, mark }) => (
  <div>
    Hello, {name} {mark}
  </div>
);

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

그리고 App 에서 해당 컴포넌트를 렌더링한다면

src/App.tsx

import React from 'react';
import Greetings from './Greetings';

const App: React.FC = () => {
  return <Greetings name="Hello" />;
};

export default App;

markdefaultProps 로 넣었음에도 불구하고 mark값이 없다면서 제대로 작동하지 않습니다. 반면, React.FC 를 생략하면 어떨까요?

src/Greetings.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
};

const Greetings = ({ name, mark }: GreetingsProps) => (
  <div>
    Hello, {name} {mark}
  </div>
);

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

생략을 하면 오히려 아주 잘됩니다!

이러한 이슈때문에 React.FC 를 쓰지 말라는 도 존재합니다. 이를 쓰고 안쓰고는 여러분의 자유이지만, 저는 사용하지 않는 것을 권장합니다.

취향에 따라, 화살표 함수도 만약에 사용하지 않는다면 다음과 같은 형태겠지요?

src/Greetings.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
};

function Greetings({ name, mark }: GreetingsProps) {
  return (
    <div>
      Hello, {name} {mark}
    </div>
  );
}

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

컴포넌트에 만약 있어도 되고 없어도 되는 props 가 있다면 ? 문자를 사용하면 됩니다.

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
  optional?: string;
};

function Greetings({ name, mark, optional }: GreetingsProps) {
  return (
    <div>
      Hello, {name} {mark}
      {optional && <p>{optional}</p>}
    </div>
  );
}

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

만약 이 컴포넌트에서 특정 함수를 props 로 받아와야 한다면 다음과 같이 타입을 지정 할 수 있답니다.

src/Greetings.tsx

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
  optional?: string;
  onClick: (name: string) => void; // 아무것도 리턴하지 않는다는 함수를 의미합니다.
};

function Greetings({ name, mark, optional, onClick }: GreetingsProps) {
  const handleClick = () => onClick(name);
  return (
    <div>
      Hello, {name} {mark}
      {optional && <p>{optional}</p>}
      <div>
        <button onClick={handleClick}>Click Me</button>
      </div>
    </div>
  );
}

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

그러면, App 에서 해당 컴포넌트를 사용해야 할 때 다음과 같이 작성해야겠지요.

App.tsx

import React from 'react';
import Greetings from './Greetings';

const App: React.FC = () => {
  const onClick = (name: string) => {
    console.log(`${name} says hello`);
  };
  return <Greetings name="Hello" onClick={onClick} />;
};

export default App;

이제 yarn start를 해서 잘 작동하는지 확인해보세요. 버튼도 눌러보시고 콘솔에 메시지가 잘 출력되는지도 확인해보세요.

TypeScript 를 사용하신다면 만약 여러분이 컴포넌트를 렌더링 할 때 필요한 props 를 빠뜨리게 된다면 다음과 같이 에디터에 오류가 나타나게 됩니다.

그리고 만약 컴포넌트를 사용하는 과정에서 이 컴포넌트에서 무엇이 필요했더라? 하고 기억이 안날때는 단순히 커서를 컴포넌트 위에 올려보시거나, 컴포넌트의 props 를 설정하는 부분에서 Ctrl + Space 를 눌러보시면 됩니다.

이제, 여러분들은 함수형 컴포넌트를 타입스크립트로 작성하는 기본적인 방법을 배우셨습니다.

다음 섹션에서는 상태관리를 하는 방법도 알아보도록 하겠습니다.

results matching ""

    No results matching ""