6 분 소요


1. 함수에서 TS 사용하기

함수에서 type을 지정하는 방법은 두 가지가 있다.

  1. 사용하고자 하는 인자의 타입을 콜론(:)을 사용하여 작성한다.
  2. 객체를 받고 함수의 반환값의 타입은 콜론(:) 다음에 명시한다.
function sum(a: number, b: number): number {
  return a + b;
}

function objSum({ a, b }: { a: number; b: number }): string {
  return `${a + b}`;
}



2. 비동기 함수에서 TS 사용하기

2.1 json 서버 열기

먼저 json 서버를 설치해주자

yarn add json-server


그 후, db.json 파일을 만들어 아래와 같은 내용을 넣어주자

{
  "people": [
    { "id": "1", "age": "25", "height": 179 },
    { "id": "2", "age": "22", "height": 163 },
    { "id": "3", "age": "29", "height": 184 }
  ]
}


아래 명령을 통해 json-sever를 실행하자

yarn json-server --watch db.json --port 3001


2.2 비동기 함수와 TS

아래 비동기 함수를 보자

async function getPost() {
  const res = await fetch(`http://localhost:3001/people`);
  if (!res.ok) {
    throw new Error();
  }
  return res.json();
}
getPost().then((res) => console.log(res));

아래 명령어로 실행하기!

ts-node 파일명.ts

res보면 any 타입인 것을 확인할 수 있다. any 타입은 모든 타입을 허용하기 때문에 (=치트키)TypeScript가 제공하는 강력한 정적 타입 검사 기능을 이용할 수 없게 된다.


any 타입을 피하고 타입을 명시적으로 정의하여 타입 안정성을 보장하도록 해보자.

함수를 선언하는 부분에 Promise<Person[]> 작성하기

type Person = { id: number; age: number; height: number };

// getperson이 반환하는 건 person의 array
// 비동기 함수는 promise를 반환하기 때문에 promise 타입을 사용해야함
async function getperson(): Promise<Person[]> {
  const res = await fetch("http://localhost:3001/people");
  if (!res.ok) {
    throw new Error();
  }
  return res.json();
}

getperson().then((res) => console.log(res[0].age));


이 외에 return 부분에 type을 작성해줘도 함수의 type이 return부의 type으로 자동으로 추론되기 때문에 아래와 같이 작성해도 된다! (근데 Promise가 더 간단한 듯..)

type Person = { id: number; age: number; height: number };

async function getperson() {
  const res = await fetch("http://localhost:3001/people");
  if (!res.ok) {
    throw new Error();
  }
  return res.json() as any as Person[];
}

getperson().then((res) => console.log(res[0].age));



3 CRA를 TS로 만들기

TS 템플릿으로 React가 설치가 된다.

npx create-react-app my-first-ts-app --template typescript



4. Generic  간단히 이해하기

제네릭(generic)이란 데이터의 타입을 일반화한다(generalize)하는 방법을 의미한다.

간단히 데이터의 타입을 변수화 하는 것이다! 아래와 같이 사용한다.

제네릭 타입 선언 / 호출

type Generic<T> = {
  someValue: T;
};

type Test = Generic<string>;


제네릭 함수 선언 / 호출

function someFunc<T>(value: T) {}
someFunc<string>("hello");

즉, 타입을 생성할 때 원하는 타입을 인자로 받아 넣어주면 된다.



5. useState에서 사용하기

명시적으로 제네릭에 원하는 type을 넣어주면 된다.

import React, { useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<string>("hello");

  return <div>{counter}</div>;
};

export default App;


초기 값이 없으면 undefined가 나온다!


버튼 클릭시 counter가 증가하도록 해보자

import React, { useState } from "react";

const App = () => {
  // 명시적으로 제네릭에 원하는 type을 넣어주면 된다.
  const [counter, setCounter] = useState<number>(1);
  const increment = () => {
    setCounter((prev) => 1);
  };
  return <div onClick={increment}>{counter}</div>;
};

export default App;

아래처럼 type이 일치하지 않는 경우 제한을 해주는 것을 볼 수 있다.



6. Passing Props

원래 js에서는 props를 아래와 같이 사용했다.

import React, { useState } from "react";

const Parent = () => {
  const [count, setCount] = useState();
  return <Child count={count}></Child>;
};

const Child = ({ count }) => {
  return <div>{count}</div>;
};

export default Parent;

하지만 위 코드는 count 가 암묵적으로 any타입을 가져 에러가 발생하게 된다.


따라서 아래와 같이 타입을 써줘야한다.

import React, { useState } from "react";

const Parent = () => {
  const [count, setCount] = useState("");
  return <Child count={count}></Child>;
};

const Child = ({ count }: { count: string }) => {
  return <div>{count}</div>;
};

export default Parent;


만약 아래와 같이 여러 개의 props를 전달 받는 상황이면 어떻게 될까? → 코드가 길어지고 가독성이 떨어진다.

const Child = ({
  count,
  double,
  id,
  des,
}: {
  count: string;
  double: string;
  id: string;
  des: string;
}) => {
  return <div>{count}</div>;
};


위 문제를 해결하기 위해 type 부분만 선언하고, 선언한 type을 컴포넌트에서 props로 받도록 사용할 수 있다.

type Props = {
  count: string;
  double: string;
  id: string;
  des: string;
};

const Child = ({ count, double, id, des }: Props) => {
  return <div>{count}</div>;
};


todolist에서 setTodos를 Todo 컴포넌트에다 넘기는 예제를 살펴보자.

import React, { useState } from "react";

type Todo = {
  id: string;
  idDone: boolean;
};
const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  return (
    <>
      {todos.map(({ id }) => (
        <Todo key={id} />
      ))}
    </>
  );
};
function Todo() {
  return <div></div>;
}

export default App;


콜백 함수를 props로 넘겨줄 때는 아래와 같이 작성해주면 된다.

import React, { useState } from "react";

type Todo = {
  id: string;
  idDone: boolean;
};
const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  return (
    <>
      {todos.map(({ id }) => (
        <Todo key={id} id={id} setTodos={setTodos} />
      ))}
    </>
  );
};
function Todo({
  id,
  setTodos,
}: {
  id: string;
  setTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
}) {
  const deleteTodo = () => {
    setTodos((prev) => prev.filter((todo) => todo.id !== id));
  };

  return <div onClick={deleteTodo}></div>;
}

export default App;


React.Dispatch<React.SetStateAction<Todo[]>>는 아래와 같이 축약해서 쓸 수 있다.

setTodos: (cb: (todo: Todo[]) => Todo[]) => void;

function Todo({
  id,
  setTodos,
}: {
  id: string;
  setTodos: (cb: (todo: Todo[]) => Todo[]) => void;
}) {
  const deleteTodo = () => {
    setTodos((prev) => prev.filter((todo) => todo.id !== id));
  };

Todo를 받아 Todo를 반환하는 콜백을 받으면 void 함수(아무 리턴 값이 없는 함수)를 setTodos에 넣어준다는 의미이다.


아래처럼 부모 컴포넌트에서 자식 컴포넌트에 props로 함수를 넘겨줄 때 매개변수, 리턴부로 타입을 나눠 작성하여 type을 물려줄 수 있다.

import React, { useState } from "react";

type Todo = {
  id: string;
  idDone: boolean;
};
const App = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  const deleteTodo = (id: string) => {
    const newTodos = todos.filter((todo) => todo.id === id);
    setTodos(newTodos);
  };
  return (
    <>
      {todos.map(({ id }) => (
        <Todo key={id} id={id} deleteTodo={deleteTodo} />
      ))}
    </>
  );
};

function Todo({
  id,
  deleteTodo,
}: {
  id: string;
  deleteTodo: (id: string) => void;
}) {
  const handeOnClick = () => {
    deleteTodo(id);
  };
  return <div onClick={handeOnClick}></div>;
}

export default App;



7. Children Props

방법 1) PropsWithChildren 사용하기

BaseType을 제네릭에 넣어주기만 하면 바로 children props를 사용할 수 있다.

import React, { PropsWithChildren } from "react";

type BaseType = {
  id: string;
};

function Child({ children }: PropsWithChildren<BaseType>) {
  return <div>{children}</div>;
}

export function Parent() {
  return (
    <Child id="">
      <div>children</div>
    </Child>
  );
}


하지만, 위 방법은 children을 넘기지 않더라도 아무런 오류를 뱉지 않는다.(옵셔널 체이닝을 사용하기 때문) → PropsWithChildren type은 엄격하지 않다고 볼 수 있다.(하지만 많이 사용)


방법 2) PropsWithChildrenrn과 비슷한 StrictChildren같은 type을 만들어줌으로써 해결이 가능하다.

옵셔널이 아닌 무조건 type+children을 사용하게 하여 더 엄격하게 type을 사용할 수 있게 한다.

import React, { ReactNode } from "react";

type BaseType = {
  id: string;
};

type StrictChildren<T> = T & { children: ReactNode };

function Child({ children }: StrictChildren<BaseType>) {
  return <div>{children}</div>;
}

export function Parent() {
  return (
    <Child id="">
      <div>children</div>
    </Child>
  );
}

엄격하게 type을 지정하여 children을 지웠을 때 바로 오류가 나는 것을 볼 수 있다.



8. Event Handler 사용하기

이벤트 핸들러에서 TS를 사용할 때는 어떻게 해야할까?

import React, { useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e) => {
    e.target.value;
  };

  return <div onClick={increment}>{counter}</div>;
};

export default App;

event에 대한 type을 지정해야한다.


onClick 메서드와 같은 자주 사용하는 메서드들의 type을 지정하는 방법을 알아보자.

방법 1) 함수를 작성하고 event 넣은 후 마우스 올려보기

return <div onClick={(e) => {}}>{counter}</div>;

어떤 type을 썼는지 타입 추론이 가능하다.


type 복사 붙여넣기!

import React, { useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    e.target;
  };

  return <div onClick={increment}>{counter}</div>;
};

export default App;



방법 2) 더 간단한 방법이다. 메서드에다 호버링할 때 나오는 ...EventHandler...를 통해 type을 추론할 수 있다.

onClick일 경우

import React, { MouseEvent, useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e: MouseEvent<HTMLDivElement>) => {
    e.target;
  };

  return <div onClick={increment}>{counter}</div>;
};

export default App;


onChange일 경우

import React, { FormEvent, useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e: FormEvent<HTMLDivElement>) => {
    e.target;
  };

  return <div onChange={increment}>{counter}</div>;
};

export default App;


댓글남기기