본문 바로가기

Frontend/TypeScript

TypeScript 사용하기

/* JavaScript */
10 - '1' // 9 (Dynamic Typing)

/* TypeScript */
10 - '1' // 컴파일 시점에 에러로 출력

◼ JavaScript는 타입에 관련된 에러를 런타임 시점에 확인할 수 있다. 이러한 문제점을 개선하기 위해서 TypeScript가 나왔다.

◼ 즉, TypeScript는 JavaScript + Type 문법으로 JavaScript에 타입에 대한 내용을 확장시킨 언어이다.

◼ 컴파일 시점에 타입에 관련된 에러를 보여주기 때문에, 버그를 방지할 수 있다.


프로젝트에 적용하기

✔ 별다른 라이브러리 없이 사용하기

◼ nodejs 설치하기

◼ npm install -g typescript 명령어로 설치한다.

 

{
  "compilerOptions": {
    "target": "ES5",
    "module": "CommonJS"
  }
}

◼ 프로젝트 폴더에 tsconfig.json 파일을 생성 후, 위 코드를 붙여 넣는다.

◼ {파일명}.ts 파일을 만들어서 사용한다.

◼ 브라우저는 JavaScript 밖에 읽을 수 없기 때문에, ts파일을 js로 변환해서 사용해야 한다.

   💬 터미널에 tsc -w 명령어를 입력해두면 ts 파일이 js 파일로 자동 변환된다. 이 과정을 "컴파일"이라고 한다.

 

✔ React에서 사용하기

◼ npm install --save typescript @types/node @types/react @types/react-dom @types/jest 명령어로 설치한다.

◼ npx create-react-app {프로젝트 이름} --template typescript 로 react 프로젝트를 생성한다.


tsconfig 파일 설정하기

{
 "compilerOptions": {

  "target": "es5", 
  "module": "commonjs", 
  "allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지 
  "checkJs": true, // 일반 js 파일에서도 에러체크 여부 
  "jsx": "preserve", // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
  "declaration": true, //컴파일시 .d.ts 파일도 자동으로 함께 생성 (현재쓰는 모든 타입이 정의된 파일)
  "outFile": "./", // 모든 ts파일을 js파일 하나로 컴파일 (module이 none, amd, system일 때)
  "outDir": "./", // js파일 아웃풋 경로 바꾸기
  "rootDir": "./", // 루트경로 바꾸기 (js 파일 아웃풋 경로에 영향줌)
  "removeComments": true, // 컴파일시 주석 제거 

  "strict": true, // strict 관련 모드 전부 켜기
  "noImplicitAny": true, // any 타입 금지 여부
  "strictNullChecks": true, // null, undefined 타입에 이상한 짓 할시 에러
  "strictFunctionTypes": true, // 함수파라미터 타입체크 강하게 
  "strictPropertyInitialization": true, // class constructor 작성 시 타입 체크 강하게
  "noImplicitThis": true, // this 키워드가 any 타입일 경우 에러
  "alwaysStrict": true, // 자바스크립트 "use strict" 모드 켜기

  "noUnusedLocals": true, // 쓰지않는 지역변수 있으면 에러
  "noUnusedParameters": true, // 쓰지않는 파라미터 있으면 에러
  "noImplicitReturns": true, // 함수에서 return 빼먹으면 에러 
  "noFallthroughCasesInSwitch": true, // switch문 이상하면 에러
 }
}

◼ target은 TypeScript 파일을 어떤 버전의 JavaScript로 바꿔줄지 정하는 부분이다.

◼ module은 JavaScript 파일 간 import 문법을 구현할 때, 어떤 문법을 쓸지 정한다.

◼ noImplicitAny는 any 타입이 의도치 않게 발생할 경우 에러를 띄워준다.

◼ strictNullChecks는 null, undefined 타입에 이상한 조작을 하면 에러를 띄워준다.


문법

✔ 변수 타입 지정하기

let myName: string = "choi";
let nameArray: string[] = ["choi", "choi2"];
let nameObject: { name: string } = { name: "choi" };
let nameOptionalObject: { name?: string } = {};
let test = "test" // let test:string = "test"와 동일

◼ {변수명}: {타입}으로 타입을 지정해서 해당 타입으로만 사용이 가능하게 한다.

   💬 type 종류: string, number, boolean, null, undefined, bigint, [], {} 등...

◼ Object에서 key 값 옆에 "?"를 붙이면 Optional 해져서 없어도 된다.

💥 타입 지정은 원래 자동으로 되기 때문에, 타입 지정 문법을 생략할 수 있다.

 

let test: { [key: string]: string } = { name: "name", age: "123" }; // O
let test2: { [key: string]: string } = { name: "name", age: 123 }; // X

◼ Object의 키 값들이 너무 많은 경우에는 [key: string]: string으로 지정해서 key은 모두 string 형식으로 지정할 수 있다.

 

✔ 변수 타입을 지정하기 애매할 경우

type MyType = string;
let unionType: MyType | number = 10;

 "|" 기호를 사용하면, 타입을 여러 개 지정할 수 있다.

◼ union 타입이라 부른다.

 

let allVar: any = "test";
allVar = 10;
allVar = "10";

◼ any 타입을 사용하면, 모든 타입을 담을 수 있기 때문에, TypsScript 기능을 해제하는 것과 같다.

 

let anyVal: any = 10;

let unknownVal: unknown = 10;
unknownVal = "10";

let otherVal1: string = unknownVal; // 에러
let otherVal2: string = anyVal; // 가능

unknownVal = 10;

unknownVal - 10; // 에러
anyVal - 10; 가능

◼ unknown 타입도 any 타입과 마찬가지로 모든 타입을 담을 수 있다.

◼ TypeScript에서는 같은 타입끼리의 연산만 가능하기 때문에, unkown 타입에서 연산을 하면 에러가 발생한다.

   💬 타입이 지정된 변수에 unkown 타입의 변수를 대입하면 에러가 발생하지만, any 타입의 변수는 넣을 수 있고 연산에 대한 제약도 없어서, any 타입보다 안전하다.

 

✔ 함수 타입을 지정하기

function myFunction(x: number): number {
  return x + 1;
}

function voidFunction(x: number): void {
  console.log(x);
}

myFunction(); // 에러

◼ 함수에서도 파리미터, 리턴 타입을 지정할 수 있다.

◼ 반환 값이 없으면 void를 붙일 수 있다.

◼ 파라미터에 타입을 붙이면 반드시 인자로 넘겨야 한다.

 

function myFunction(x?: number): number {
  return x;
}

myFunction(); // 가능

◼ 파라미터가 Optional 할 경우, "?"를 붙일 수 있다.

◼ "?"가 붙은 변수는 number | undefined와 동일하게 취급되기 때문에, x + 2와 같은 연산 시 에러가 발생한다.

 

function muFunction(x: number | string): void {
  console.log(x + 3); // 에러
}

◼ x 변수가 number와 string이 될 수 있는 union 타입이여서 에러가 발생한다.

 

✔ Narrowing

function muFunction(x: number | string): void {
  if (typeof x === "number") {
    x + 2;
  } else {
    x + "2";
  }
}

◼ 타입이 아직 하나로 확정되지 않았을 경우, 사용한다.

◼ "typeof"로 변수의 타입을 확정 시키고 연산을 적용한다. 단, typeof는 number, string, boolean, object 등의 기본 타입만 확인할 수 있다.

◼ Narrowing으로 판정해주는 문법에는 3가지가 존재한다. ("typeof" 해당 타입인지, "in" Object의 속성인지, instanceof 클래스의 instance인지) 

 

function test1(a: string | undefined) {
  if (a && typeof a === "string") {
    console.log(typeof a); // string
  }
}

test1("test");

◼ null과 undefined 타입 체크하는 경우 유용하게 사용할 수 있다.

◼ && 기호는 null이나 undefined, NaN 같은 falsy 값이 있으면 해당 값을 반환하기 때문에 && 기호를 사용하면, if 문이 실행되지 않는다. 

 

type Lion = {
  run: string;
};

type Cat = {
  walk: string;
};

function print(animal: Lion | Cat) {
  if ("run" in animal) {
    console.log(animal.run);
  } else if ("walk" in animal) {
    console.log(animal.walk);
  }
}

◼ Object 타입이 들어올 때, animal.run을 입력하면 타입이 불확실하기 때문에 에러가 발생한다.

◼ 따라서, 속성이 다른 Object 타입을 검사할 때는 "in" 키워드를 사용해서 해당 속성이 Object 인스턴스에 들어있는지 확인 후 출력해야 에러가 발생하지 않는다.

 

✔ Assertion

function muFunction(x: number | string): void {
  (x as number) + 1
}

◼ as를 사용해서 타입을 확정 지을 수 있다.

◼ as 문법은 union 타입에서 타입을 하나로 확정 지을 때 사용하며, 무슨 타입으로 들어올지 정확히 알 때, 사용해야 한다.

◼ 만일 위 코드에서 x로 "1234" 값이 들어왔다면 +1 값은 "12341"이 된다.

 

✔ type alias

type Data = { code: number; messages: string[]; data?: {} };
let res: Data;

Data = { code: string }; // 재정의 불가능

type Member = { [key: string]: string };
let test: Member = { name: "name", age: "123" };

◼ type 키워드를 사용해서 타입을 변수로 선언해서 사용할 수 있다.

◼ Object나 Array에 type을 지정해서 사용하기 편리하다.

◼ type alias는 재정의가 불가능하다.

 

const hometown: { name: string } = { name: "seoul" };
hometown.name = "paju"; // 가능

type Hometown = { readonly name: string };
const hometown: Hometown = { name: "seoul" };
hometown.name = "paju"; // 에러

◼ const 키워드는 할당 값의 변경을 방지할 뿐, 실제 Object나 Array의 속성 값의 변경은 막아주지 못한다.

◼ type alias를 적용하고 readonly를 붙여주면, 속성 값의 변경도 막아줄 수 있다.

💥 에디터에서만 막아주고, 실제 js 파일에서는 실행된다.

 

type PositionX = { x: number };
type PositionY = { y: number };
type Position = PositionX & PositionY;

let position: Position = { x: 10, y: 20 };

◼ & 키워드로 object 타입을 extend 할 수 있다.

 

✔ 함수 type alias

type FunctionType = (x: string) => number;
let exFunction: FunctionType = function (x) {
  return Number(x);
};

exFunction("10");

 함수 type alias를 사용할 때는 항상 arrow function을 이용해야 한다.

함수 표현식을 사용해서 함수 type alias를 적용할 수 있다.

 

type testDataType = {
  name: string;
  age: number;
  burk: (x: string) => void;
};

let testData: testDataType = {
  name: "choi",
  age: 10,
  burk: (x) => {
    console.log(x);
  },
};

위와 같이, Object 안의 함수에 type alias 적용할 수 있다.

Object의 type alias를 지정한 뒤, Object를 생성할 때 적용해서 사용한다.

 

✔ Literal types

let mood: "angry" | "sadness";
mood = "angry" // 가능
mood = "other" // 에러

function printMood(mood: "angry" | "sadness"): void {
  console.log(mood);
}
printMood("angry"); // 가능
printMood("other"); // 에러

◼ 변수에 대입할 수 있는 값을 지정할 수 있다.

◼ 함수 파라미터에 받을 수 있는 값만 지정할 수 있다.

◼ const 변수와 유사하게 사용할 수 있으며, 지정한 값으로만 값을 대입해서 사용할 수 있다.

 

let literalData = { name: "choi" };

function printLiteralData(name: "choi"): void {
  console.log(name);
}

printLiteralData(literalData.name); // 에러

◼ literalData.name은 분명 "choi"인데, printLiteralData에 파라미터로 넣으면 에러가 발생한다.

◼ 그 이유는, printLiteralData의 파리미터 name은 "choi"라는 타입을 받는데, literalData.name은 string 타입이여서 에러가 발생한다.

◼ literalData를 생성할 때, let literalData = { name: "choi" }  as const; 과 같이 as const를 붙여서 생성하면, Object를 literal type으로 지정해서 생성하기 때문에 에러가 해결된다.

 

✔  TypeScript으로 HTML 조작하기

💬 tsconfig.json 파일에서 "strictNullChecks" 옵션이 true로 설정되어 있을 때의 경우이다.

// html 파일
<h4 id="title">TypeScript</h4>

// .ts 파일
let titleElement = document.querySelector("#title");

if (titleElement instanceof Element) {  // 1번
  titleElement.innerHTML = "other";
}

if (titleElement !== null) {  // 2번
  titleElement.innerHTML = "other";
}

if (titleElement?.innerHTML != undefined) {  // 3번
  titleElement.innerHTML = "other";
}

document.querySelector("#title").innerHTML = "other"; 로 HTML 값을 변경하고자 하면, 에러가 발생한다.

document.querySelector("#title")의 반환 값은 Element | null 이기 때문에 발생하는 에러이다.

◼ 1번, 2번, 3번 방법의 narrowing 을 통해서 위와 같이 해결할 수 있다.

 

// html 파일
<a href="https://naver.com" class="link">네이버 바로가기</a>

// .ts 파일
let linkElement = document.querySelector(".link");
if (linkElement instanceof HTMLAnchorElement) {
  linkElement.href = "https://kakao.com";
}

TypeScript에서 Element 타입에는 HTMLAnchorElement, HTMLHeadingElement, HTMLButtonElement 타입이 존재하며, HTMLAnchorElement 타입에서 href, style, class 등을 사용할 수 있어, 위와 같이 href 속성을 변경할 수 있다.

 

let buttonElement = document.querySelector("#button");
buttonElement?.addEventListener("click", function () {
  console.log("Clicked!!");
});

EventListener를 추가할 때, 위와 같이 추가할 수 있다.

?. 연산자를 통해, null이 아닌 경우에 EventListener를 추가한다.

 

✔  class 문법

// JavaScript
class Dog {
  constructor(name) {
    this.name = "name";
  }
}

// TypeScript
class Dog {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

let dog1 = new Dog("choi1");
let dog2 = new Dog("choi2");

JavaScript에서는 필드 값 없이 constructor에서 바로 this.{필드명}으로 접근해서 사용할 수 있지만, TypeScript에서는 필드 값이 미리 선언되어 있어야 constructor에서 사용할 수 있다.

 

class Dog {
  name: string;
  constructor(name: string) {
    this.name = name;
  }

  bulk() {
    console.log("Wong! Wong!");
  }
}

let dog1 = new Dog("choi1");
let dog2 = new Dog("choi2");
dog1.bulk();

bulk 함수와 같이 prototype 함수를 지정해서 사용할 수 있다.

 

✔  interface 문법

type DopType1 = {
  name: string;
  age: number;
};

interface DogType2 {
  name: string;
  age: number;
}

let poodle1: DopType1 = { name: "poodle1", age: 10 };
let poodle2: DogType2 = { name: "poodle2", age: 10 };

Object의 타입을 정의할 때, type 키워드와 interface 키워드를 사용할 수 있다.

◼ interface는 class 처럼 대입 없이 중괄호를 사용한다.

 

💬 type 키워드 vs interface 키워드

interface Animal {
  name: string;
  age: number;
}

// interface Dog { // 1번
//   name: string;
//   age: number;
//   bulk: () => void;
// }

interface Dog extends Animal { // 2번
  bulk: () => void;
}

let poodle: Dog = {
  name: "poodle",
  age: 10,
  bulk: () => {
    console.log("Wong!!");
  },
};

◼ interface는 type과 다르게 extends로 상속시켜 사용할 수 있다.

◼ 1번과 달리 2번은 공통된 name, age 필드를 Animal 인터페이스를 상속해서 사용한다.

◼ type 키워드의 경우 interface Dog { bulk = ()  => void } & Animal; 코드를 통해 위와 같은 기능을 제공할 수 있다. 이 경우 상속을 통한 구현이 아닌 & 기호(intersection type)를 사용해야 한다.

 

interface T1 {
  data: string;
}

interface T2 extends T1 { // 에러 발생 시점
  data: number;
}

type T3 = { data: string };
type T4 = { data: number } & T3;
let test2: T4 = { name: "choi" }; // 에러 발생 시점

◼ extends를 사용하여 두 개의 type alias를 합쳤을 때, 두 개가 같은 속성 이름, 다른 타입을 가지고 있으면 에러가 발생하지만, type의 경우 & 기호를 사용하기 때문에 바로 에러가 출력되지 않고 타입을 사용해서 변수 생성 시점에서 에러가 발생한다.

 

interface Animal {
  name: string;
  age: number;
}

interface Animal {
  nickname: string;
}

let animal: Animal = {
  name: "animal",
  age: 10,
  nickname: "ani",
};

💥 가장 큰 차이점은 interface는 중복 선언이 가능하지만, type은 중복 선언이 안된다.

◼ interface는 중복 선언이 가능해서, 중복 선언을 하면 자동으로 Animal 2개가 합쳐져 name, age, nickname 속성을 가진다.

◼ type 은 중복 선언 시, 에러가 발생한다.

 

✔  never 타입

function test1(): never {
  throw new Error();
}

function test2(): never {
  while (true) {}
}

◼ function return 값에 붙일 수 있는 타입으로 void와 유사하지만 더욱 엄격하다.

◼ return 값이 없어야 한다. 즉, 함수 실행이 끝나지 않아야 한다.

◼ 사용할 상황에는 마지막에 throw 발생, 무한 루프 같은 상황에 사용한다.

◼ 즉, 있을 수 없는 상황에서 발생하는 타입으로, 잘 사용되지 않는다.

 

✔  접근 지정자

class User {
  public name: string;
  protected age: number;
  private secret: string;
  constructor(name: string, public data: string, age: number) {
    this.name = name;
    this.age = age;
    this.secret = "secret";
  }
}

let user = new User("something", "choi", 20);
console.log(user.name); // choi
console.log(user.data); // something
console.log(user.age); // 에러
console.log(user.secret); // 에러
console.log(user.staticSecret); // 에러
console.log(User.staticSecret); // staticSecret

◼ public, protected, private, static 키워드를 사용할 수 있다.

◼ public: 모든 인스턴스, 자식들에서 사용 가능하다. public 키워드를 파라미터 안에서 사용하면, this로 할당하는 작업이 필요 없어진다.

◼ protected: class와 자식 class 내부에서만 사용 가능하다. 프로퍼티로 접근할 수 없다.

◼ private: class 내부에서만 사용 가능하다. 프로퍼티로 접근할 수 없다.

◼ static: Class.{변수명}으로 사용할 수 있다. 프로퍼티 및 class 내부에서 사용할 수 없다. 다른 접근 지정자랑 같이 사용할 수 있다.

 

✔  Generic

function genericTest(vals: unknown[]) {
  return vals[0];
}

let firstVal = genericTest([1, 2]);
console.log(firstVal + 1); // 에러

// Generic
function genericTest<T>(vals: T[]) {
  return vals[0];
}

let firstVal = genericTest<number>([1, 2]);
console.log(firstVal + 1); // 2

◼ firstVal는 unkown 타입이기 때문에 에러가 발생한다.

◼ Generic을 사용해서 파라미터로 타입을 지정할 수 있다.

 

function addTen<T>(val: T) {
  return val + 10;
}

console.log(addVal<number>(10)); // 에러 발생

◼ T 자리에 number가 아닌 string이 들어올 수 있어, val가 어떤 타입이 될지 불확실하기 때문에 연산에서는 에러가 발생한다.

 

function addVal<T extends number>(val: T) {
  return val + 10;
}

console.log(addVal<number>(10)); // 20

◼ extends 키워드를 통해 타입을 제한해서 Narrowing을 대신할 수 있다.

💥 Generic 타입의 타입 검사는 extends를 사용해서 해야 한다.

 

interface NameCheck {
  name: string;
}

function getName<T extends NameCheck>(val: T) {
  console.log(val.name);
}

◼ interface로 선언한 NameCheck 타입을 extends로 타입을 제한하면, 파라미터에 해당 속성이 존재하는지 확인하기 때문에 Narrowing을 대신할 수 있다.

 

✔  Spread Operator 타입 지정

let arr = [4, 5, 6];
let arr2: [number, number, number, ...number[]] = [1, 2, 3, ...arr]; // 1번
let arr3: number[] = [1, 2, 3, ...arr]; // 2번

◼ 위 코드와 같이 2가지 방법으로 Spread Operator에 대한 타입 지정을 할 수 있다.

 

✔  declare

// data.js
let a = 10;
let b = { name: "kim" };

// test.ts
console.log(a); // 에러로 표시되지만 실행은 된다.

// test.html
<script src="data.js"></script>
<script src="test.js"></script>

◼ .js에 있는 변수를 .ts에서 사용하기 위한 문법이다.

◼ 실행은 되지만, ts 파일에서 에러로 표시되는 것이 거슬리기 때문에 declare를 사용해서 해결한다.

 

declare let a: number;

console.log(a);

◼ ts 파일을 위와 같이 수정하면, a의 에러가 사라진다.

◼ 외부 js 라이브러리를 사용할 때, 변수 사용의 에러를 해결할 때 사용한다.

💥 모든 .ts 파일은 ambient module(글로벌 모듈)로 모든 ts 파일에서 그냥 사용할 수 있다. ambient module을 사용하지 않는 법은 import나 export가 파일에 존재하면 로컬 모듈로 자동으로 변경된다.

 

💬 외부 라이브러리를 사용할 때, 타입 지정이 안 되어 있다면?

해당 라이브러리의 타입을 찾아서 설치해야 한다.

node_modules에 타입이 설치되고 자동으로 타입이 지정되어 에러 없이 사용할 수 있다.

 

// test1.ts
declare global {
  type Name = string;
}

export {}; // export로 인해 로컬 모듈

// test2.ts
let name: Name = "choi"

◼ ts 파일에 export나 import가 존재하여 로컬 모듈이 되었을 때, 글로벌 변수를 만들고 싶을 때도 declare를 사용할 수 있다.

◼ 타입 변수를 전역 변수로 사용하고 싶을 때, declare 문법을 사용할 수 있다.

 

✔  .d.ts 파일

// data.d.ts
export type Name = string;
export interface Test {
  name: string;
}

// index.ts
import { Name, Test } from "./data";

let name: Name = "myName";

◼ 프로젝트에서 사용하는 타입 보관용 파일이다.

◼ .d.ts 파일은 export, import를 사용하지 않아도 자동으로 로컬 모듈로 동작한다.

◼ export, import로 사용할 수 있다.

◼ .d.ts 파일에 너무 많은 타입이 있으면, 사용하는 ts 파일에서 import * as {변수명} from "./data"로 사용할 수 있다.

 

{
  "compilerOptions": {
    "target": "ES5",
    "module": "CommonJS",
    "strictNullChecks": true,
    "noImplicitAny": true,
    "declaration": true
  }
}

◼ tsconfig.json 파일에 "declaration": true 옵션을 넣으면, ts 파일에서 사용한 모든 타입이 .d.ts파일로 자동 생성된다.

 

✔  implements

interface DogType {
  name: string,
  age: number
}

// 에러 X
class Dog implements DogType {
  name: string;
  age: number;
}

// 에러 O
class Dog implements DogType { 
  name: string
}

◼ class 옆에 implements {interface 타입}을 사용하면, class 속성에 interface에서 정의 타입의 속성이 전부 존재하는지 확인한다.

 

✔  keyof

interface Test {
  age: number;
  name: string;
}

type TestTypes = keyof Test; // "age" | "name"

let testKeyOf: TestTypes = "age"; // age와 name만 할당할 수 있다.

◼ keyof 문법을 사용해서 Test interface에 있는 타입 속성을 가지고 literal union 타입으로 만들어 준다.

 

interface WrongType {
  name: boolean;
  nickname: boolean;
}

type TypeChanger<T> = {
  [key in keyof T]: string;
};

type CorrectType = TypeChanger<WrongType>;

◼ keyof 문법을 사용해서 위와 같이 name, nickname 속성의 타입을 string으로 변경할 수 있다.


React + TypeScript

✔ 세팅하기

◼ npx create-react-app {프로젝트명} --template typescript 명령어로 설치한다.

   💥 만일, 기존 react 프로젝트에 typescript만 더하고 싶다면, npm install --save typescript @types/node @types/react @types/react-dom @types/jest 명령어를 실행하고, .js 파일을 .ts 파일로 바꿔서 사용한다.

◼ 컴포넌트 파일은 .tsx 확장자를 사용해야 한다.

 

✔ 사용하기

import React from "react";
import "./App.css";

function App() {
  let text: string = "Hello";
  return (
    <div>
      <h4>{text}</h4>
    </div>
  );
}

export default App;

◼ 변수나 함수를 만들 때, 타입을 지정해서 사용한다.

 

✔ JSX 타입 지정

let box: JSX.Element = <div></div>;

function MyButton(): JSX.Element {
  return <button>내 버튼</button>;
}

◼ JSX.Element를 사용해서 컴포넌트를 담을 수 있다.

 

✔ Props 타입 지정

import "./App.css";

function App() {
  return (
    <div>
      <Box title="Hello"></Box>
      <Box2 title="Hello"></Box2>
    </div>
  );
}

function Box(props: { title: string }): JSX.Element {
  return (
    <div>
      <h4>{props.title}</h4>
    </div>
  );
}

interface Title {
  title: string;
}

function Box2({ title }: Title): JSX.Element {
  return (
    <div>
      <h4>{title}</h4>
    </div>
  );
}

export default App;

◼ 위와 같이 Object 타입으로 지정해서 사용할 수 있다.

◼ Box1 방법과 Box2 방법을 사용해서 Props에 타입을 지정할 수 있다.

 

✔ useState 타입 지정

let [title, setTitle] = useState("hello");

◼ useState의 초기 값을 string으로 설정했기 때문에, title은 자동으로 string이다.

 

let [title, setTitle] = useState<string | number>("hello");

◼ 초기 값을 string, number 등 union으로 하고 싶다면, 위 코드처럼 지정할 수 있다.

 

✔ Redux 타입 지정

redux 설치: npm install @reduxjs/toolkit

 

// dataSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

const data: { count: number } = { count: 0 };

export const dataSlice = createSlice({
  name: "counter",
  initialState: data,
  reducers: {
    up(state) {
      state.count += 1;
    },
    down(state) {
      state.count -= 1;
    },
    update(state, action: PayloadAction<number>) {
      state.count = action.payload;
    },
  },
});

export const { up } = dataSlice.actions;

◼ PayloadAction<type>을 사용해서 dispatch() 할 때, payload의 타입을 지정한다.

 

// store.ts
import { configureStore } from "@reduxjs/toolkit";
import { dataSlice } from "./store/dataSlice";

export default configureStore({
  reducer: {
    dataSlice: dataSlice.reducer,
  },
});

◼ dataSlice를 import 해서 store에 등록한다.

 

import store from "./store";
import { Provider } from "react-redux";

root.render(
  <Provider store={store}>
      <App />
  </Provider>
);

export type Rootstate = ReturnType<typeof store.getState>;

◼ Rootstate를 export 해서 store의 타입을 사용할 수 있게 한다.

 

import { useSelector, useDispatch } from "react-redux";
import { Dispatch } from "redux";

const data = useSelector((state: Rootstate) => state.dataSlice);
const dataDispatch: Dispatch = useDispatch();

interface Box2 {
  data: number;
  dispatch: Dispatch;
}

function Box2({ data, dispatch }: Box2): JSX.Element {
  return (
    <div>
      <h4>
        {data}{" "}
        <button onClick={() => {dispatch(up());}}>증가</button>
      </h4>
    </div>
  );
}

◼ useSelector를 사용해서 Redux에 저장한 데이터를 가져오고, useDispatch를 사용해서 state를 업데이트 한다.