본 글은 제로초님의 강의 [리뉴얼] 타입스크립트 올인원 : Part1. 기본 문법편 을 듣고 스스로 정리한 내용임을 밝힙니다.
이번 포스팅에서는 TS 기본 문법을 총 정리 해보려고 한다.
자바스크립트로 쓰여진 코드를 타입스크립트로 마이그레이션 하는 것이 목표이다.
따라서, 타입스크립트 동작원리를 현 시점에서 깊게 파고들기 보다는 사용법을 빠르게 익혀 코드에 적용해보려고 한다.
const a: number = 5;
function add(x: number, y: number): number { return x + y }
const add: (x: number, y: number) => number = (x, y) => x + y;
const obj: { lat: number, lon: number } = { lat: 37.5, lon: 127.5 };
const z: {} = 5;
const zz: {} = null // Type 'null' is not assignable to type '{}'
type Add = (x: number, y: number) => number
const add2: Add = (x, y) => x + y;
const a = 5; // const a: 5
const b = '3'; // const b: "3"
const c = a + b; // const c: string
function add(x: number, y: number) { return x + y } // function add(x: number, y: number): number
const obj: { lat: number, lon: number } = { lat: 37.5, lon: 127.5 };
const obj = { lat: 37.5, lon: 127.5 };
const a = document.querySelector('#root') as HTMLDivElement;
const a = document.querySelector('#root');
function add<T>(x: T, y: T): T { return x + y }
function add(x, y) { return x + y }
interface A {};
type A = {};
let x = 5;
x = 'hello'; // Type 'string' is not assignable to type 'number'.(2322)
const head = document.querySelector('#head')!; // const head: Element
console.log(head);
const head = document.querySelector('#head'); // const head: Element | null
if (head) {
console.log(head);
}
const a: string = 'hello';
const b: String = 'hell';
let arr: string[] = []; // let arr: string[]
let arr2: Array<string> = []; // let arr2: string[]
function rest(...args: string[]) {} // function rest(...args: string[]): void
const tuple: [string, number] = ['1', 1]; // const tuple: [string, number]
tuple[2] = 'hello'; // Type '"hello"' is not assignable to type 'undefined'.(2322)
tuple.push('hello'); // 에러가 안남에 주의!!!!
type World = "world" | "hell";
// type Greeting = "hello world"
type Greeting = `hello ${World}`; // type Greeting = "hello world" | "hello hell"
const enum EDirection {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// Using the enum as a parameter
function walk(dir: EDirection) {}
walk(EDirection.Left);
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
// It requires an extra line to pull out the keys
type Direction = typeof ODirection[keyof typeof ODirection]; // ODirection의 value값들만 들고오는 코드
function run(dir: Direction) {}
run(ODirection.Right);
// union: 또는, 둘 중 하나의 속성만 있으면 된다.
type A = { hello: 'world' } | { zero: 'cho' };
const a: A = { hello: 'world' }; // OK
// intersection: and, 모든 속성이 다 있어야 한다.
type B = { hello: 'world' } & { zero: 'cho' };
const b: B = { hello: 'world' }; // error
const bb: B = { hello: 'world', zero: 'cho' }; // OK
type Animal = { breath : true };
type Poyuryu = Animal & { breed : true };
type Human = Poyuryu & { think : true };
const john:Human = { think : true, breath : true, breed : true }; // OK
interface A {
breath: true
}
interface B extends A {
breed: true
}
const b: B = { breath: true, breed: true }; //OK
interface A { a: string }
interface A { b: string }
// const obj1: A = { a: 'hello'}; // error
const obj1: A = { a: 'hello', b: 'haha'}; // OK
type B = { a: string } // Duplicate identifier 'B'.
type B = { b: string } // Duplicate identifier 'B'.
const obj2: B = { a: 'hello', b: 'world' } // error
type A = { name: string };
type B = { age: number};
type AB = A | B;
type C = A & B;
// 타입C가 타입AB 보다 더 구체적임 -> 좁은 타입
// 따라서 더 넓은 타입을 좁은 타입에 대입할 수 없음
const ab: AB = { name: 'john' };
const c: C = ab; // error
// 반대는 가능 (좁은타입 -> 넓은타입)
const c: C = { name : 'john', age : 20 };
const ab: AB = c;
좁은 타입과 넓은 타입을 검사할 때, 객체 리터럴을 바로 집어 넣으면 타입에 선언된 속성 외에 속성이 있는지 체크한다. 있으면 에러
선언된 속성 외에 속성이 있는 경우 변수로 한 번 빼주고 대입하면 문제 없음
// 잉여 속성 검사에 걸리는 예
type A = { name: string };
type B = { age: number};
type AB = A | B;
type C = A & B; // { name: string, age: number };
const c: C = { name: 'john' , age : 20 , married: false }; // error
// 변수로 빼주면 해결됨!
type A = { name: string };
type B = { age: number};
type AB = A | B;
type C = A & B; // { name: string, age: number };
const obj = { name: 'john' , age : 20 , married: false }
const c: C = obj ; // OK
원칙: any를 쓸 바에는 unknown을 쓴다고 알아두자
any의 문제점
unknown
코드 예
interface A {
talk: () => void;
}
const a: A = {
talk() { return 3; }
}
// any를 쓰는 예
const b: any = a.talk();
b.method(); // 존재하지 않는 타입을 써도 검사 x
// unknown을 쓰는 예
const b: unknown = a.talk();
b.talk(); // Object is of type 'unknown'.(2571)
(b as A).talk(); // OK
// unknown이 나오는 가장 흔한 예는 try ~ catch문
try {
// var error: unknown
} catch(error) {
//error.message // error
(error as Error).message // OK
}
타입 가드란 어떠한 스코프내에서 타입을 체크해주는(좁혀주는) 표현식을 뜻함
type of
function numOrString(a: number | string) {
// Property 'toFixed' does not exist on type 'string'.(2339)
a.toFixed(1);
}
function numOrString(a: number | string) {
if (typeof a === 'number') {
// 이 스코프 안에 들어온 매개변수 a의 타입이 number임을 보장
a.toFixed(1);
} else {
// 이 스코프 안에 들어온 매개변수 a의 타입이 string임을 보장
a.charAt(3);
}
}
numOrString('123');
numOrString(1);
Array.isArray()
function numOrNumArray(a: number | number[]) {
if (Array.isArray(a)) { // Array.isArray() 사용
a.concat(4);
} else {
a.toFixed(3);
}
}
numOrNumArray([1, 2, 3]);
numOrNumArray(1);
{속성} instanceof {클래스}
class A {
aaa() {}
}
class B {
bbb() {}
}
// 클래스 자체를 타입자리에 쓸 수 있다.
// 실제로 인자로 들어가는 것은 개체
function aOrB(param: A | B) {
if (param instanceof A) {
param.aaa();
}
}
aOrB(new A());
aOrB(new B());
값
과 속성
으로 구별type B = { type: 'b', bbb: string};
type C = { type: 'c', ccc: string};
type D = { type: 'd', ddd: string};
// 값으로 구별하는 방법
function typeCheck(a: B | C | D){
if (a.type === 'b') {
a.bbb;
} else if (a.type ==='c') {
a.ccc;
} else {
a.ddd;
}
}
// 속성명(메서드명 포함)으로 구별하는 방법
function typeCheck(a: B | C | D){
if ('bbb' in a) {
a.type;
} else if ('ccc' in a) {
a.ccc;
} else {
a.ddd;
}
}
보통 객체의 경우, 값으로 구별하는 방법을 많이 사용
const human = { type: 'human' };
const dog = { type: 'dog' };
const cat = { type: 'cat' };
interface Cat { meow: number }
interface Dog { bow: number }
function catOrDog(a: Cat | Dog): a is Dog {
// 타입 판별을 직접 만들어야 함
if ((a as Cat).meow) { return false }
return true;
}
const cat: Cat | Dog = { meow: 3}
if (catOrDog(cat)) {
console.log(cat.meow);
}
if ('meow' in cat) {
console.log(cat.meow);
}
const x: {} = 'hello';
// const xx: {} = undefined; // error
// const xxx: {} = null; // error
const xxxx: {} = 1234;
const y: Object = 'hi';
const xx: object = 'hi'; // error
const yy: object = { hello: 'world' }; // 실제로 객체 타이핑을 할때는 object 지양. interface, type, class 사용할 것
const z: unknown = 'hi';
// unknown = {} | null | undefined
if (z) {
z; // const z: {}
} else {
z; // const z: unknown
}
객체의 속성이 너무 많을 때, 모든 속성과 값의 타입을 지정해주는 방법
type A = {[key: string]: number};
const a: A = { a: 1, b: 2, };
const aa: A = { a: 'a', b: 'b' } // 값이 number가 아니므로 error
type B = 'Human' | 'Mammal' | 'Animal';
type A = { [key in B]: number}; // A 타입의 속성값에는 B의 타입들이 와야 함
const a:A = { Animal: 3, Human: 2, Mammal: 0 };
type AA = { [key in B]: B}; // 값에도 B의 값 중 하나가 와야 함
const aa: AA = {Animal: "Mammal", Human: "Human", Mammal: "Animal"};
class A {
a: string;
b: number;
constructor(a: string, b: number = 123) {
this.a = a;
this.b = b;
}
method() {
}
}
const a = new A('123'); // { a: "123", b: 123 }
class A {
a: string = '10';
b: number = 3;
method() {
}
}
const a: A = new A(); // { a: "10", b: 3 }
typeof
OOclass A {
a: string;
b: number;
constructor(a: string, b: number = 123) {
this.a = a;
this.b = b;
}
method() {
}
}
const a: A = new A('123');
const aa: typeof A = new A('45'); // error
const b: typeof A = A; // OK
interface A {
readonly a: string;
b: string;
}
class B implements A {
readonly a: string = '123';
b: string = 'world';
}
class B {
private a: string = '123';
protected b: string = 'world';
public c: string = 'wow';
}
class C extends B {
method() {
console.log(this.a); // error
console.log(this.b); // ok
console.log(this.c); // ok
}
}
function abc(a: number, b?: number, c?: number) {}
abc(1);
abc(1, 2);
abc(1, 2, 3);
abc(1, 2, 3, 4); // error
function abc(...args: number[]) {}
abc(); //OK
abc(1); //OK
abc(1, 2, 3, 4, 5); //OK
abc('1', 2, 3); // error
let obj: { a: string, b?: string } = { a: 'hello', b: 'world' }
let obj2: { a: string, b?: string } = { a: 'hello', } // OK
function add<T extends number | string>(x: T, y: T): T {
return x + y;
}
add(1, 2); // 3
add('1', '2') // '12'
add('1', 2); // error
add('2', 1); // error
function add<T extends number, Y extends string>(x: T, y: Y): T {
return x + y;
}
add(1, 2); // error
add('1', '2') // error
add('1', 2); // error
add(1, '2'); // OK
function add<T extends { a: string }>(x: T): T { return x };
add({ a: 'hello' });
function add<T extends string[]>(x: T): T { return x };
add([ 'a', 'b', 'c' ]);
function add<T extends (a: string) => number>(x: T): T { return x };
add((a)=>+a)
// 모든 함수
function add<T extends (...args: any) => any>(x: T): T { return x };
// 생성자
function add <T extends abstract new (...args: any) => any>(x: T): T { return x };