/* 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를 업데이트 한다.