자바스크립트의 모든 기능을 포함하며 정적 타입을 지원하는 언어다. 정적 타입은 왜 필요하고 타입스크립트는 어떤 장점이 있을까?
const size: number = 1;
const isBig: boolean = false;
const msg: string = 'abc';
const values: number[] = [1,2];
const values2: Array<number> = [1,2];
const a: undefined = undefined;
const b: null = null;
문자열 리터럴과 숫자 리터럴을 타입으로 정의할 수도 있다.
let a: 1 | 2 | 3;
a = 4 //타입 에러
let b: '다빈' | '욱창';
b = '시원스쿨' //타입에러
any 타입에는 숫자, 문자열뿐 아니라 함수도 입력될 수 있다. 하지만 타입스크립트를 사용하는 의미가 퇴색되므로 피하는게 좋다.
함수 타입
// void: 아무 값도 반환하지 않는 경우
function f1(): void {
console.log('hello')
}
// never: 항상 예외가 발생해서 비정상적으로 종료되거나 무한 루프 때문에 종료되지 않는 경우
function f2(): never {
throw new Error('some error');
}
object 타입
let v: object;
v = {name : '다빈'};
console.log(v.name) // 타입 에러
교차 타입(교집합)과 유니온 타입(합집합)
// 교차 타입 &
// 유니온 타입 |
let v1: (1| 2 | 3) & (3 | 4);
v1 = 4; //타입 에러
type 키워드로 별칭을 부여할 수 있다.
type Width: number | null;
let width: Width;
width = 2;
width = '2'; //타입 에러
열거형 타입은 enum을 사용해 정의한다.
enum Food {
Pasta,
Salad,
Pizza,
}
const v1: Food = Food.Pasta; //Pasta를 값으로 사용
const v2: Food.Pasta | Food.Pizza = Food.Pizza; //Pasta를 타입으로 사용
// 명시적으로 원소의 값 입력하기
enum Food {
Pasta, //첫번째 원소에 값을 할당하지 않으면 자동으로 0이 할당된다.
Salad = 5,
Pizza, //값을 할당한 원소 다음 원소는 자동으로 이전 원소 + 1 이 할당된다.
}
console.log(Food[5]) //Pasta
// 각 원소의 값과 이름이 양방향으로 매핑되어 값을 이용해 이름을 가져올 수도 있다.
// 원소에 문자열을 할당하는 경우에는 단방향으로 매핑되어 값을 이용해 이름을 가져올 수 없다. 참고!
열거형 타입은 컴파일 후에도 남아 있어 번들 파일의 크기가 커진다. 상수(const) 열거형 타입을 사용하면 컴파일 결과에 객체를 남기지 않을 수 있다.
const enum Food {
Pasta,
Salad,
Pizza,
}
const pasta: Food = Food.Pasta;
//컴파일 결과
const pasta = 0;
// 하지만 열거형 타입을 상수로 정의하면 열거형 타입의 객체를 사용할 수 없으니 주의하자
함수타입
// 변수를 함수 타입으로 정의하는 방법
const getInfoText: (name: string, age?: number) => function(name, age) {
}
function getInfoText(name: string, age: number): string {
}
선택 매개변수 오른쪽에 필수 매개변수를 정의하면 컴파일 에러가 발생한다.
이 경우에는 undefined를 사용한다.
function getInfoText(
name: string,
language: string | undefined,
age: number,
): string {
//...
}
getInfoText('mike', undefined, 23);
기본값이 있으면 선택 매개변수가 된다.
function getInfoText(
name: string,
age: number = 15,
): string {
//...
}
나머지 매개변수는 배열로 정의한다.
function getInfoText(name: string, ...rest: string[]): string {
//...
}
함수의 this 타입을 정의하지 않으면 기본적으로 any 타입이 사용된다.
function getParam(this: string, index: number): string {
const params = this.splt(','); //타입에러
}
원시 타입에 메서드 추가하려면 인터페이스를 사용한다.
interface String {
getPram(this: string, index: number): string;
}
String.prototype.getParam = getPram;
// 이제 문자열에 등록된 getParam 메서드를 호출할 수 있다.
함수 오버로드: 여러개의 타입 정의하기
// 1 매개변수와 반환 타입의 모든 가능한 조합을 정의한 뒤 실제 구현하는 쪽에서 정의한 타입은 함수 오버로드의 타입 목록에서 제외된다.
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: number | string, y: number | string): number | string {
//...
}
// 2 명명된 매개변수
interface Param {
name: string;
age?: number;
language?: string;
}
function getInfoText({ name, age = 15, language }: Param): string {
}
객체 타입 정의하기
interface Person {
name: string;
age?: number;
}
const p1: Person = {name: 'mike', age: 23 };
// ?는 선택속성
// 하지만 undefined로 정의하는 경우 해당 속성을 입력해야 타입 에러가 발생하지 않는다.
interface Person {
name: string;
age: number | undefined;
}
const p1: Person = {name: 'mike' }; //타입에러
// 읽기 전용 속성: 변수를 정의하는 시점만 값을 할당할 수 있으며 이후에는 수정이 불가능하다.
interface Person {
readonly name: string;
age: number | undefined;
}
// 정의되지 않은 속성값을 할당하는 경우 타입 에러가 난다.
인터페이스로 정의하는 인덱스 타입
a. 속성 이름을 구체적으로 정의하지 않고 타입만 정의하는 것을 인덱스 타입이라 한다.
interface Person {
readonly name: string;
age: number;
[key: string]: string | number;
}
// 여러 개의 인덱스를 정의하는 경우
interface YearPriceMap {
[key: number]: number;
[year: string]: string | number;
}
b. 함수 타입 정의하기
interface GetInfoText {
(name: string, age: number): string;
}
const getInfoText: GetInfoText = function(name, age) {
return '';
}
// 함수를 정의할 때에는 속성 이름 없이 정의한다.
c. 클래스 구현하기
interface Person {
name: string;
age: number;
isYoungerThan(age: number): boolean;
}
Class SomePerson implements Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
isYoungerThan(age: number) {
return this.age < age;
}
}
// implements 키워드를 사용해 클래스를 구현할 수 있다. 인터페이스에서 정의한 세 속성을 클래스 내부에서 구현했으며, 하나의 속성이라도 구현하지 않으면 컴파일 에러가 발생한다.
d. 인터페이스 확장하기
interface Person {
name: string;
age: number;
}
interface Programmer {
favoriteLanguage: string;
}
interface Korean extends Person, Programmer {
isLiveInSeoul: boolean;
}
const dabin: Korean = {
name: 'dabin',
age: 25,
favoriteLanguage: 'javascript',
isLiveInSeoul: true
}
// 인터페이스 합치기
type KoreanProgrammer = Programmer & Korean;
숫자와 문자열
인터페이스
인터페이스 A가 인터페이스 B에 할당 가능하려면
B에 있는 모든 필수 속성의 이름이 A에도 존재해야 한다.
같은 속성 이름에 대해 A의 속성이 B의 속성에 할당 가능해야 한다.
interface Person {
name: string;
age: number;
}
interface Product {
name: string;
age: number;
}
const person: Person = { name: 'mike', age: 23 };
const product: Product = person;
선택 속성이 타입 호환성에 미치는 영향
A를 B에 할당하려면 B 값의 집합이 A 값의 집합보다 커야 한다.
interface Person {
name: string;
age: number;
}
interface Product {
name: string;
age?: number;
}
// Person은 Product에 할당 가능하지만, Product는 Person에 할당이 불가능하다.
추가 속성과 유니온 타입이 타입 호환성에 미치는 영향
interface Person {
name: string;
age: number;
gender :string;
}
interface Product {
name: string;
age?: number | string;
}
// 추가 속성이 있으면 값의 집합은 더 작아지므로 Person을 Product에 할당할 수 있다.
// Persond의 집합이 Product에 포함된다.
* 함수의 타입 호환성
함수는 호출하는 시점에 문제가 없어야 할당 가능한다.
A를 B에 할당할 때
- A의 매개변수 개수가 B의 매개변수 개수보다 적어야 한다.
- 같은 위치의 매개변수에 대해 B의 매개변수가 A의 매개변수로 할당 가능해야 한다.
- A의 반환값은 B의 반환값으로 할당 가능해야 한다.
function addOne(value: number) {
return value + 1;
}
const result = [1,2,3].map<number>(addOne);
//(value: number, index: number, array: number[]) => number
타입 정보가 동적으로 결정되는 타입이다. 같은 규칙을 여러 타입에 적용할 수 있어 중복 코드를 제거할 수 있다는 장점이 있다.
function makeArray<T>(defaultValue: T, size: number): T[] {
const arr: T[] = [];
fot (let i = 0; i < size; i++) {
arr.push(defaultValue);
}
return arr;
}
const arr1 = makeArray<number>(1,10);
// 사실.. 함수의 첫 번재 매개변수 타입을 알면 T를 알 수 있기 때문에 제네릭을 명시적으로 전달하지 않아도 된다.
extends 키워드로 제네릭 타입을 제한할 수도 있다.
function identity<T extends number | string>(p1: T): T {
return p1;
}
// 이 함수에는 숫자나 문자열 타입만 매개변수로 입력될 수 있다.
keyof 키워드는 인터페이스의 모든 속성 이름을 유니온 타입으로 만들어 준다.
function swapProperty<K extends keyof Person>{
...
}
기존 인터페이스의 모든 속성을 선택 속성 또는 읽기 전용으로 만들 때 주로 사용된다.
type T1 = { [K in 'prop1' | 'prop2']: boolean };
// { prop1: boolean; prop2: boolean;}
type T1 = Person['name'] //string
// 인터페이스에서 특정 속성의 타입을 추출할 때 사용되는 문법이다.
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Partial<T> = { [P in keyof T]?: T[P] };
// 모든 속성을 선택 속성으로 만들어 준다.
type P2 = Readonly<Person>
// Person의 모든 속성은 읽기 전용 속성으로 만든다.
Pick 내장 타입은 원하는 속성만 추출할 때 사용된다.
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
interface Person {
name: string;
age: number;
language: string;
}
type T1 = Pick<Person, 'name' | 'language'>;
Record 내장 타입은 입력된 모든 속성을 같은 타입으로 만들어준다.
type Record<K extends string, T> = { [P in K]: T };
type T1 = Record<'p1' | 'p2', Person>;
//type T1 = { p1: Person; p2: Person; }
// K로 입력된 모든 문자열을 속성 이름으로 하면서 T를 각 속성의 타입으로 만든다.
맵드 타입을 이용하면 열거형 타입의 활용도를 높일 수 있다.
enum Fruit {
Apple,
Banana,
}
const FRUIT_PRICE: { [key in Fruit]: number } = {
[Fruit.Apple]: 10000
}
// 객체가 enum의 모든 원소를 속성으로 가지고 있어야 한다. 따라서 바나나가 없기 때문에 타입 에러가 난다.
조건부 타입은 입력된 제네릭 타입에 따라 타입을 결정할 수 있는 기능이다. 조건부 타입은 extends 키워드와 ? 기호를 사용해서 정의한다.
type IsStringType<T> = T extends string ? 'yes' : 'no';
type T1 = IsStringType<string>: //'yes'
Exclude는 서브타입을 제거해주고 Extract는 반대로 작동한다.
type Exclude<T, U> = T extends U ? never : T;
type T2<1 | 3 | 5 | 7, 1 | 5 | 9>; // 3 | 7
type Extract<T, U> = T extends U ? T : never;
type T3 = Extract<1 | 3 | 5 | 7, 1 | 5 | 9>; // 1 | 5
ReturnType 내장 타입은 함수의 반환 타입을 추출한다.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type T1 = ReturnType<() => string>; //string
// 타입 추론을 위해 infer 키워드가 사용되었으며, infer 키워드는 조건부 타입을 정의할 때 extends 키워드 뒤에 사용한다.
타입추론
let 변수는 재할당 가능하기 때문에 융통성 있게 타입이 결정되며, 변수 선언시 값의 타입을 추론해 타입 에러를 발생시킨다.
const 변수는 리터럴 자체가 타입이 된다. 즉, 123을 입력할 경우 number가 아닌 123이 타입이 된다.
[1,2,3] 배열의 경우 number[]로 추론이 된다.
비구조화 할당에도 타입 추론이 되며 다른 타입을 비교하려고 하면 타입 에러가 난다.
함수의 매개변수와 반환값도 타입 추론이 적용된다.
타입 가드
조건문을 이용해 타입의 범위를 좁히는 기능이다. 불필요한 타입 단언 코드를 피할 수 있으므로 생산성과 가독성이 높아진다.
// typeof를 통해 value를 숫자로 인식하기 때문에 숫자의 메서드를 호할 수 있다.
function print(value: number | string) {
if (typeof value === 'number') {
console.log(value.toFixed(2));
}
}
// instanceof: 클래스의 경우 이 키워드가 타입 가드로 사용될 수 있다.
// 식별 가능한 유니온 타입
// 두 인터페이스에 type이라는 같은 이름의 속성을 정의하고 각 속성을 고유의 문자열 리터럴 타입으로 정의한다.
// type 속서의 값을 비교하는 것으로 타입 가드가 동작한다.
// switch 문에서 사용하기 좋다.
interface Person {
type: 'person';
name: string;
age: number;
}
interface Product {
type: 'product';
name: string;
price: number;
}
function print(value: Person | Product) {
switch (value.type) {
case 'person':
console.log(value.age);
break;
case 'product':
console.log(value.price);
break;
}
}
// in키워드: 속성이 있는지 검사하면 속성 이름의 존재를 검사하는 것으로 타입 가드가 동작한다.
function print(value: Person | Product) {
if ( 'age' in value ) {
console.log(value.age);
} else {
console.log(value.price);
}
}
이벤트 처리 함수의 타입을 자주 작성하기 때문에 미리 타입을 만들어 놓으면 편하다.
type EventObject<T = HTMLElement> = React.SyntheticEvent<T>;
type EventFunc<T = HTMLElement> = (e: EventObject<T>) => void;
함수형 컴포넌트 타입 정의
interface Props {
name: string;
age?: number;
}
export default function MyComponent({name, age = 23}: Props) {
return...
}
const MyComponent: React.FunctionComponent<Props> = function({name, age = 23}) {
return...
}