[TypeScript & React] #1 상태관리와 인풋관리

1Hoit·2023년 5월 31일

TypeScript & React

목록 보기
2/5
post-thumbnail

시작하며

앞서 타입스크립트에 대한 기본 내용을 알아보았다.
타입스크립트의 개념 및 기본 타입은 여기에 정리해뒀다.
추가할 부분은 많지만 이론으로만 배우기보단 바로 리액트 프로젝트를 만들어서 적용해 나가는게 좋다고 생각해서 바로 진행해보았다.


리액트에서 타입스크립트 프로젝트 생성

create-react-app 의 스크립트 기능을 사용하면 TypeScript 가 적용된 프로젝트를 쉽게 만들 수 있다.

1. npx 로 만들기

npx create-react-app [프로젝트 이름] --template typescript

2. npm 으로 만들기

npm init react-app [프로젝트 이름] --template typescript

생성후 프로젝트 폴더 구조

  • js 파일 -> ts 파일

  • jsx 파일 -> tsx 파일

  • tsconfig.json
    타입스크립트 설정 파일은 타입스크립트를 자바스크립트로 변환할 때의 설정을 정의해놓는 파일이다.

    수많은 속성이 있지만 일단 자주쓰이는 속성이 뭔지 알아보자

    {
     "compilerOptions": {
       "target": "es5",
       "lib": [
         "dom",
         "dom.iterable",
         "esnext"
       ],
       "allowJs": true,
       "skipLibCheck": true,
       "esModuleInterop": true,
       "allowSyntheticDefaultImports": true,
       "strict": true,
       "forceConsistentCasingInFileNames": true,
       "noFallthroughCasesInSwitch": true,
       "module": "esnext",
       "moduleResolution": "node",
       "resolveJsonModule": true,
       "isolatedModules": true,
       "noEmit": true,
       "jsx": "react-jsx"
     },
     "include": [
       "src"
     ]
    }
  • target : 컴파일된 코드가 어떤 환경에서 실행될 지 정의합니다.
    예를 들어, 화살표 함수를 사용하고 target을 es5로 지정했다면 이를 일반 function키워드를 활용한 함수로 컴파일 해줍니다. 그러나, 이를 es6로 설정했다면 화살표 함수 그대로 유지합니다.

  • module : 컴파일된 코드가 어떤 모듈시스템을 사용할지 정의합니다.
    이 값을 만약 common으로 하면 export default Sample 코드는 exports.default = Sample로 변환되지만 값을 es2015로 하면 export default Sample을 그대로 유지합니다.

  • strict : 모드 타입 체킹 옵션을 활성화합니다.
    esModuleInterop : commonjs 모듈 형태로 이뤄진 파일을 es2015 모듈 형태로 불러올 수 있게 해줍니다.

  • outDir : 컴파일된 파일이 어디에 저장될지 경로를 정합니다.

  • include : 어떤 파일을 컴파일할 것인지 정합니다.

  • exclude : 어떤 파일을 컴파일에서 제외할지 정합니다.

더 다양한 옵션들이 있지만 그건 추후에 따로 정리하도록하자!


리액트에서 타입스크립트 사용법

App.tsx를 보며 이해해보자

import React from 'react';
import Hello from './components/Hello';

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

export default App;
  • const App: React.FC = () => { ... } 와 같이 화살표함수를 사용하여 컴포넌트가 선언되었다.
    여기서 const App: React.FC를 살펴 보자

React.FC

  • FC : FunctionComponent 타입의 줄임말
  • React + Typescript 조합으로 개발할 때 사용하는 타입
  • 함수형 컴포넌트 사용 시 타입 선언에 쓸 수 있도록 React에서 제공하는 타입이다.

사용방법
FC 타입은 주로 다음과 같은 형태로 사용한다.

interface AppProps {
  name: string;
}

// 인자 props의 타입인 AppProps를 props 옆에 붙이지 않고 React.FC 옆에 붙인다 
const App: React.FC<AppProps> = (props) => {
  return <div>hello {props.name}</div>
}

하지만 React.FC 는 단점이 있어서 사용을 지양한다.
이 부분은 추후에 정리해야겠다.

그렇다면 리액트에서 함수형 컴포넌트는 어떤 타입으로 선언해야하는가?
가장 일반적이고 간단한 방법은 props 옆에 타입을 정의해 주는 것이다.

interface AppProps {
  name: string;
}

const App = (props: AppProps) => {};

새로운 컴포넌트를 만들어서 적용해보기

Hello.tsx

import React from 'react';

interface HelloProps {
  name: string;
  onClick(name: string): void;
}

const Hello = ({ name, onClick }: HelloProps) => {
  const handleClick = () => onClick(name);

  return (
    <>
      <h1>Hello, {name}</h1>
      <div>
        <button onClick={handleClick}>Click Me</button>
      </div>
    </>
  );
};

export default Hello;
  • 위에서 받은 props를 하위 컴포넌트에서는 이렇게 사용할 수 있다.

상태 관리 해보기

타입스크립트를 사용하는 리액트 컴포넌트에서 useState 를 사용하여 컴포넌트의 상태를 관리하는 방법을 보자.

예제 1)

import React, { useState } from 'react';

function Counter() {
  //제네릭타입으로 state 타입 결정
  const [count, setCount] = useState<number>(0);
  const onIncrease = () => setCount(count + 1);
  const onDecrease = () => setCount(count - 1);
  return (
    <div>
      <h1>{count}</h1>
      <div>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
    </div>
  );
}

export default Counter;
  • 제네릭타입<>으로 state 타입 결정하기!

  • useState를 사용 할 때 Generics 를 사용하지 않아도 알아서 타입을 유추하기 때문에 생략해도 상관없다

    그럼 언제 써야 좋을까?
    상태가 null일 수도 있고 아닐수도 있을때 Generics 를 활용하시면 좋다

    type Information = { name: string; description: string };
    const [info, setInformation] = useState<Information | null>(null);

그렇다면 useState로 관리하던 상태와 상태관리 함수는 어떻게 Props로 넘길까?

Main.tsx

const [modalOpen, setModal] = useState(false);
...
return(
      <DetailModal modalOpen={modalOpen} setModal={setModal} />
)

위와 같이 상태와 상태관리 함수를 하위 컴포넌트에서 어떻게 받을까?

DetailModal.tsx

interface IModalDataProps {
  modalOpen: boolean;
  setModal: React.Dispatch<React.SetStateAction<boolean>>;
}

const DetailModal = ({ id, modalOpen, setModal, modalData }: IModalDataProps) => {...}

위와 같이 받으면 된다.
setState함수는 위와 같이 받을 수 있지만 아래(1)처럼 void로 넘길 수도 있다.
하지만 SetStateAction 과 Dispatch를 사용하여(2) 타입을 명확히 해주어 타입스크립트를 사용하는 이점을 챙기자!

 setModal: () => void // 1
 
 setModal: React.Dispatch<React.SetStateAction<boolean>>; //2

예제 2) 인풋 상태 관리하기
App.tsx

import React from 'react';
import Hello from './components/Hello';
import Counter from './components/Counter';
import MyForm from './components/MyForm';

const App: React.FC = () => {
  const onClick = (name: string) => {
    console.log(`${name} says hello`);
  };

  const onSubmit = (form: { name: string; description: string }) => {
    console.log(form);
  };
  return (
    <>
      <Hello name="First" onClick={onClick}></Hello>
      <hr />
      <h1>카운터 예제</h1>
      <Counter />
      <hr />

      <h1>Form 예제</h1>
      <MyForm onSubmit={onSubmit}></MyForm>
    </>
  );
};

export default App;

MyForm.tsx

import React, { useState } from 'react';

type MyFormProps = {
  onSubmit(form: { name: string; description: string }): void;
};

const MyForm = ({ onSubmit }: MyFormProps) => {
  const [form, setForm] = useState({
    name: '',
    description: '',
  });

  const { name, description } = form;

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setForm({
      ...form,
      [name]: value,
    });
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(form);
    setForm({
      name: '',
      description: '',
    }); // 초기화
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={name} onChange={onChange} />
      <input name="description" value={description} onChange={onChange} />
      <button type="submit">등록</button>
    </form>
  );
};

export default MyForm;
  • 여기서 살짝 문제가 있었다.
    우리는 e의 타입을 모른다.

    VSCode 에서 확인하기
    우리는 구글에 onChange 의 event type 이 무엇인지 검색하지 않아도 된다.
    input 태그의 해당 eventHandler 에 마우스를 올려보면 친절히 알려주기 때문이다.

.onChange?: 뒤의 React.ChangeEventHandler<HTMLInputElement> 가 우리가 찾던 타입이다.

하지만...?
event 는 target을 가지고 있지않다?
에러 발생 : ChangeEventHandler

해결 : ChangeEventHandler => ChangeEvent

지금까지 JavaScript 를 통해 진행했을 때 항상 event 는 target 을 프로퍼티로 가지고 있었다.

그러나 Reaact.ChangeEventHandler 를 타입으로 지정할 경우 event.target 부분에서 에러를 띄어주었다.

이유는 타입의 뒤에 Handler가 붙었기 때문이었다..

React.ChangeEvent<HTMLInputElement> 로 타입을 지정해주니 성공적으로 컴파일 된다.

profile
프론트엔드 개발자를 꿈꾸는 원호잇!

0개의 댓글