[웹개발] TypeScript 기본문법 및 React와 사용하는 방법

방법이있지·2025년 8월 23일

웹개발

목록 보기
5/19
post-thumbnail

타입스크립트를 사용하는 이유

  • 자바스크립트는 어떤 타입이든 받아줌 -> 이로 인해 오류 발생 가능
let age = 25;
age = "스물다섯";
console.log(age + 5); // "스물다섯5"
  • age에는 숫자만 들어갈 수 있게 의도했는데, 문자열이 들어가게 되면 의도치 않은 결과 발생할 수 있음

  • 타입스크립트에선 변수, 함수, 객체에 타입을 명시적으로 선언

    • 컴파일 단계에서 타입 오류를 잡아주므로, 이런 버그를 줄일 수 있음

타입스크립트 기본 문법

설치

npm install -g typescript

타입스크립트의 type

  • 총 6개의 타입이 존재한다.
    • number
    • string
    • boolean
    • null
    • undefined
    • any -> 그 외의 타입
// let (변수명):(타입명)
let a: number = 15;
a = "hello world";
let b: string = "무적LG";

any 타입

// any: 뭐가 올지 모름
let c: any = 4;
c = "날려버려 날려버려 안타 신민재"; // 다른 타입의 값 넣어도 에러 없음
  • 단, 지나친 any를 사용하는 것은 지양해야 함
  • 그럼 TypeScript를 쓰는 의미가 없으니까

2개 이상 타입 지정

// 여러 타입 지정 가능 - 얘네 중 하나
let c: number | string = "홍창기 안타 날려 홍창기";
c = 51;
  • 여러 타입 중 하나가 올 수 있는 경우

배열

// 배열
let d: string[] = ["박해민", "신민재", "홍창기", "문성주"];
d.push("김현수");
  • (타입)[] 과 같은 식으로 가능
let d: (string | number)[] = ["박해민", "신민재", "홍창기", "문성주"];
d.push("김현수");
d.push(13);
  • |를 통한 여러 타입 선언도 가능

함수

// 함수
// e.g., number 인수 2개를 받고, number를 반환한다.
function addNumber(a: number, b: number): number {
  return a + b;
}
console.log(addNumber(7, 22)); // 29
  • 인수 및 반환값에도 타입을 지정할 수 있음
const addNumber = (a: number, b: number): number => {
  return a + b;
};
  • 화살표 함수인 경우에도 선언 가능

객체

  • 객체는 C 구조체마냥, 타입을 직접 만들 수 있음
  • 방법은 두 가지. interfacetype이 있음
  • 이런 객체를 선언한다고 가정하면
let data = {
  name: "임찬규",
  number: 1,
  size: { height: 186, weight: 88 },
  friends: [
    { name: "손주영", number: 29 },
    { name: "김진성", number: 42 },
    { name: "유영찬", number: 54 },
    { name: "송승기", number: 13 },
  ],
};

type

  • 객체에 들어갈 필드의 타입 지정 후, 나만의 타입을 지정 가능
type Player = {
  name: string;
  number: number;
  size: { height: number; weight: number }; // 객체 안 객체
  friends: { name: string; number: number }[]; // 객체의 배열
};

let data: Player = {
  name: "임찬규",
  number: 1,
  size: { height: 186, weight: 88 },
  friends: [
    { name: "손주영", number: 29 },
    { name: "김진성", number: 42 },
    { name: "유영찬", number: 54 },
    { name: "송승기", number: 13 },
  ],
};
  • 위처럼 해도 되고, 객체 안 객체를 별도의 객체로 만들어 줘도 됨
type Size = {
  height: number;
  weight: number;
};

type Friend = {
  name: string;
  number: number;
};

type Player = {
  name: string;
  number: number;
  size: Size;
  friends: Friend[];
};

interface

  • type 타입명 = {...} 이 아니라 interface 타입명 {...} 식으로 선언된다는 점이 큰 차이.
interface Size {
  height: number;
  weight: number;
}

interface Friend {
  name: string;
  number: number;
}

interface Player {
  name: string;
  number?: number;
  size: Size;
  friends: Friend[];
}

let data: Player = {
  name: "임찬규",
  number: 1,
  size: { height: 186, weight: 88 },
  friends: [
    { name: "손주영", number: 29 },
    { name: "김진성", number: 42 },
    { name: "유영찬", number: 54 },
    { name: "송승기", number: 13 },
  ],
};
  • number? 처럼 물음표를 붙이면, 해당 필드는 와도 되고, 안 와도 됨.

extends

  • interface에서는 extends라고, 기존 interface에 뭘 더하는 문법을 사용할 수 있음
interface Food {
  name: string;
  price: number;
}

interface Pizza extends Food {
  pieces: number;
  comment: string;
}

let pizza: Pizza = {
  name: "불고기 피자",
  price: 13000,
  pieces: 8,
  comment: "너무 맛있어요",
};

타입스크립트 코드 컴파일

  • 웹 브라우저는 JavaScript만 이해하지, TypeScript를 이해하지 못함
    • TypeScript는 인간의 편리를 위한 언어
  • 즉, TypeScript는 JavaScript로 컴파일해야 함
// test.ts
function addNumber(a: number, b: number): number {
  return a + b;
}
console.log(addNumber(7, 22));
# test.ts -> test.js 파일 생성
tsc test.ts
// 컴파일된 test.js 파일
function addNumber(a, b) {
  return a + b;
}
console.log(addNumber(7, 22));
  • 타입 오류 발생 시, 컴파일은 되지만 아래와 같이 알려줌

test.ts:2:1 - error TS2322: Type 'string' is not assignable to type 'number'.

2 a = "fsdfsdsfd";
  ~
Found 1 error in test.ts:2

tsconfig.json

  • ts -> js config 위한 json 파일
  • 자바스크립트 버전도 다양한 만큼, 이를 설정하기 위함
{
  "compilerOptions": {
    "outDir": "dist", // 컴파일된 JS 파일을 저장할 디렉토리
    "target": "es6", // 어떤버전의 JS로 변환할지 지정. 제일 최신은 ES6
    "module": "commonjs", // Express는 commonjs, React는 es6로 설정
    "lib": ["es6"], // 컴파일 시 사용할 라이브러리 지정
    "sourceMap": true // 디버깅용. JS코드에서 에러 발생시, 원래 작성한 TS에서 위치를 정확히 확인하기 위함.
  }
}
  • 이후 tsc (파일명)로 빌드하면, 해당폴더의 tsconfig.json에서 지정한 컴파일러 옵션을 적용함
  • config 옵션은 사용하는 라이브러리 등에 따라 달라질 수 있음. 보통 이를 그대로 따르면 됨

React + TypeScript

  • 기존처럼 npm create vite@latest을 실행하시되, TypeScript 선택.
> create-vite

│
◇  Project name:
│  react-typescript
│
◇  Select a framework:
│  React
│
◇  Select a variant:
│  TypeScript
│
◇  Scaffolding project in C:\Users\drink\Documents\jungle\week15\typescript\react\react-typescript...
│
└  Done. Now run:

  cd react-typescript
  npm install
  npm run dev
  • 기본 파일도 .tsx고, tsconfig.json 파일도 기본적으로 생성된 것을 확인할 수 있음.
  • 대신 코드 자체는 아직 타입스크립트 형태로 바뀌지 않은 상황

state 타입스크립트에서 사용하기

  • useState 함수에 무슨 타입이 들어가지?
  • 리액트는 기본적으로는 JS 기반. 뭔 타입이 들어와도 괜찮음.
  • JS를 위한 함수를 TS에 맞게 수정할 수는 없는 노릇. 대신 제네릭이라는 친구를 사용.
  • 제네릭 <...>: 내가 useState를 호출하는 순간, 타입을 정해 준다.
// 내가 `myPlayer` state에 앞서 만든 Player type를 저장하는 경우
// `useState<Player>`와 같이 사용.
const [myPlayer, setMyPlayer] = useState<Player>(data);
  • 여기서 myPlayerPlayer 타입
  • setMyPlayerPlayer를 인수로 받고 반환값이 없는 함수 타입이 됨.

props 타입스크립트에서 사용하기

  • 컴포넌트에 보내는 props 역시 typeinterface로 타입을 만들어 사용
const App = () => {
  const [myPlayer, setMyPlayer] = useState<Player>(data);
  const changeName = (name: string) => {
    setMyPlayer((myPlayer) => ({ ...myPlayer, name }));
  };

  return (
    <>
      <CheckPlayer data={myPlayer} changeName={changeName} />
    </>
  );
};
  • e.g., 내가 Player 형의 data와, string을 받고 반환값이 없는 함수 changeNameCheckPlayer 컴포넌트에 전달하고 싶다면?
interface CheckPlayerProps {
  data: Player;
  changeName(name: string): void;
}
  • Propsinterfacetype로 만들어 주고
    • 이때 interface에 함수를 넣는 경우, (변수명: 변수타입, ...): 반환값타입 식으로 작성
    • 반환값 없는 경우 void
const CheckPlayer = (props: CheckPlayerProps) => {
  return (
    <>
      <h1>
        #{props.data.number}: {props.data.name}
      </h1>
    </>
  );
};
  • 이후 CheckPlayer의 매개변수 타입을 CheckPlayerProps로 설정한다

예제코드

// App.tsx
import "./App.css";
import { useState } from "react";

interface Size {
  height: number;
  weight: number;
}

interface Friend {
  name: string;
  number: number;
}

interface Player {
  name: string;
  number: number;
  size: Size;
  friends: Friend[];
}

const data: Player = {
  name: "임찬규",
  number: 1,
  size: { height: 186, weight: 88 },
  friends: [
    { name: "손주영", number: 29 },
    { name: "김진성", number: 42 },
    { name: "유영찬", number: 54 },
    { name: "송승기", number: 13 },
  ],
};

interface CheckPlayerProps {
  data: Player;
  changeName(name: string): void;
}

const CheckPlayer = (props: CheckPlayerProps) => {
  const [newName, setNewName] = useState<string>(props.data.name);
  return (
    <>
      <h1>
        #{props.data.number}: {props.data.name}
      </h1>
      <div>{props.data.size.height}, 몸무게 {props.data.size.weight}
      </div>
      <h2>동료 선수들</h2>
      <ul>
        {props.data.friends.map((friend: Friend, idx: number) => (
          <li key={idx}>
            #{friend.number} {friend.name}
          </li>
        ))}
      </ul>
      <input
        type="text"
        value={newName}
        onChange={(e) => setNewName(e.target.value)}
        placeholder="변경할 이름 입력"
      />
      <button onClick={() => props.changeName(newName)}>이름 변경</button>
    </>
  );
};

const App = () => {
  const [myPlayer, setMyPlayer] = useState<Player>(data);
  const changeName = (name: string) => {
    setMyPlayer((myPlayer) => ({ ...myPlayer, name }));
  };

  return (
    <>
      <CheckPlayer data={myPlayer} changeName={changeName} />
    </>
  );
};

export default App;

profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글