캡틴판교님의 타입스크립트 입문 - 기초부터 실전까지를 듣고 정리해보았다.
타입스크립트의 기본 타입에는 크게 12가지가 있다.
Boolean, Number, String, Object, Array ,Tuple, Enum, Any, Void, Null, Undefined, Never
// 문자열
const str: string = 'hi';
// 숫자
const num: number = 10;
// 배열
const arr: number[] = [1,2,3];
//튜플
const address: [string, number] = ['gangnam', 100];
// 객체
const obj: object = {};
// 진위값
const show: boolean = true;
// any => 모든 타입에 대해서 허용
const str: any = 'hi';
// void => 반환 값이 없다
function log(a: string): void {
console.log(a);
}
// 함수의 파라미터와 반환 값에 타입을 정의
function sum(a: number, b: number): number {
return a + b;
}
타입스크립트에서 함수의 매개변수는 정의된 매개변수 값만 받을 수 있다.
function sum(a: number, b: number): number {
return a + b;
}
sum(10); // error, too few parameters
sum(10,20,30); // error, too many parameters
매개변수의 갯수만큼 인자를 넘기지 않도록 하고싶다면 ?
을 이용해서 아래와 같이 정의하면 된다.
// 함수의 옵셔널 파라미터
function log(a: string, b?: string, c?: string) {}
log('hello world');
sum('hello','ts');
// 변수에 인터페이스 활용
interface User {
age: number;
name: string;
}
var seho: User = {
age: 33,
name: '세호'
}
// 함수에 인터페이스 활용
function getUser(user: User) {
console.log(user);
}
var capt: User = {
age: 100,
name: '캡틴'
}
getUser(capt);
// 함수의 스펙에 인터페이스 활용
interface SumFunction {
(a: number, b: number): number;
}
var sum: SumFunction;
sum = function(a: number, b: number): number {
return a + b;
}
// 인덱싱
interface StringArray {
[index: number]: string
}
var arr: StringArray = ['a','b','c'];
// 딕셔너리 패턴
interface StringRegexDictionary {
[key: string]: RegExp
}
var obj: StringRegexDictionary = {
sth: /abc/,
cssFile: /\.css$/,
jsFile: /\.js$/
}
// 인터페이스 확장
interface Person {
age: number;
name: string;
}
interface Developer extends Person {
language: string;
}
// string 타입을 사용할 때
const name: string: 'capt';
// 타입 별칭을 사용할 때
type MyName = string;
const name: MyName = 'capt';
간단한 타입 뿐만아니라 interface
레벨의 복잡한 타입에도 별칭을 부여할 수 있다.
type Developer = {
name: string;
skill: string;
}
타입 별칭은 새로운 타입 값을 하나 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름을 부여하는 것과 같다.
타입 별칭과 인터페이스의 가장 큰 차이점은 인터페이스는 확장이 가능하지만 타입은 확장이 불가능하다는 것이다.
🔥 좋은 소프트웨어는 언제나 확장이 용이해야 한다는 원칙에 따라 가급적 확장이 가능한 인터페이스로 선언하면 좋다 🔥
function logMessage(value: string | number) {
console.log(value);
}
logMessage('hello');
logMessage(100);
유니온 타입을 사용했을 때의 장점은 타입 가드를 할 수 있다는 것이다.
// any를 사용하는 경우
function getAge(age: any) {
age.toFixed(); // 에러 발생, age의 타입이 any로 추론되기 때문에 숫자 관련된 API를 작성할 때 코드가 자동 완성되지 않는다.
return age;
}
// 유니온 타입을 사용하는 경우
function getAge(age: number | string) {
if (typeof age === 'number') {
age.toFixed(); // 정상 동작, age의 타입이 `number`로 추론되기 때문에 숫자 관련된 API를 쉽게 자동완성 할 수 있다.
return age;
}
if (typeof age === 'string') {
return age;
}
return new TypeError('age must be number or string');
}
interface Developer = {
name: string;
skill: string;
}
interface Person = {
name: string;
age: number;
}
function askSomeone(someone: Developer | Person) {
someone.name; // 유니온 타입을 이용했을 때 공통된 속성까지만 접근할 수 있다.
// 나머지는 타입 가드를 이용해서 접근 가능하다.
}
function askSomeone(someone: Developer & Person) {
// someone은 Developer의 속성과 Person의 속성을 모두 갖는 타입이므로 모두 접근 가능하다.
someone.name;
someone.skill;
someone.age;
}
이넘은 특정 값들의 집합을 의미하는 자료형이다.
숫자형 이넘은 초기 값 부터 차례로 1씩 증가한다. 초기값을 정해주지 않으면 0부터 차례로 1씩 증가한다.
// 숫자형 이넘
enum Shoes {
Nike,
Adidas
}
var myShoes = Shoes.Nike;
console.log(myShoes); // 0
// 문자형 이넘
enum Shoes {
Nike = '나이키',
Adidas = '아디다스'
}
var myShoes = Shoes.Nike;
console.log(myShoes); // '나이키'
드롭다운처럼 목록이 필요한 경우에 이넘을 사용하면 더 정확한 코드를 작성할 수 있고, 예외처리 케이스들이 줄어들 것이다.
enum Answer {
Yes = 'Y',
No = 'N'
}
function askQuestion(answer: Answer) {
if (answer === Answer.Yes) {
console.log('정답입니다');
}
if (answer === Answer.No) {
console.log('오답입니다');
}
}
askQuestion(Answer.Yes);
타입스크립트로 클래스를 선언하면 특정 속성의 접근과 할당에 대해 제어할 수 있다.
class Person {
private name: string;
public age: string;
readonly log: string;
constructor (name:string, age:string) {
this.name = name;
this.age = age;
}
}
재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이자 문법이다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용된다.
function logText<T>(text: T): T {
return text;
}
logText<number>(10);
logText<string>('hello');
😎 제네릭을 사용했을 때의 이점
any를 사용하게 된다면 함수에 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수가 없는데T
라는 타입을 추가하면 함수를 호출할 때 넘긴 타입에 대해 타입스크립트가 추정할 수 있게된다.
또한 타입이 다른 인터페이스를 여러개 작성할 필요없이 하나의 인터페이스로 여러가지의 타입을 커버할 수 있다.
// 인터페이스에 제네릭을 선언하는 방법
interface Dropdown<T> {
value: T;
selected: boolean;
}
// 제네릭 타입 제한 1
function logTextLength<T>(text: T[]): T[] {
console.log(text.length);
}
logTextLength<string>(['hi','abc']);
// 제네릭 타입 제한 2
interface LengthType {
length: number;
}
function logTextLength<T extends LengthType>(text: T): T{
console.log(text.length);
}
logTextLength('a');
// 제네릭 타입 제한 3
interface ShoppingItem {
name: string;
price: number;
stock: number;
}
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOptions: T): T {
return itemOptions;
}
// 키 값들만 들어갈 수 있다.
getShoppingItemOption('name');
x에 대한 타입을 따로 지정하지 않더라도 일단 x는 number로 간주된다. 이렇게 변수를 선언하거나 초기화 할 때 타입이 추론된다.
let x = 3;
타입은 보통 몇 개의 표현식(코드)을 바탕으로 타입을 추론한다. 그리고 그 표현식을 이용하여 가장 근접한 타입을 추론하게 되는데 이 가장 근접한 타입을 Best Common Type이라고 한다.
// Best Common Type - var arr: (number | boolean) []
var arr = [1, 2, true];
타입스크립트 보다 개발자가 타입을 더 잘 알고 있다하면 개발자가 정한 타입으로 간주하는 것이다. 특히, DOM API 조작의 경우.
var a ='a';
var b = a as string;
interface Person {
name: string;
age: number;
}
interface Developer {
name: string;
skill: string;
}
// 타입 가드 정의
function isDeveloper(target: Developer | Person): target is Developer {
retrun (target as Developer).skill !== undefined;
}
타입 호환이란 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미한다.
구조적 타이핑(structural typing)이란 코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단하는 것이다.
interface Person {
name: string;
}
interface Developer {
name: string;
skill: string;
}
var developer = Developer;
var person: Person;
person = developer;
위 코드에서 developer
가 person
타입에 호환이 될 수 있는 이유는 developer
가 name
속성을 가지고 있기 때문에 developer
는 Person
타입에 호환될 수 있다.
타입스크립트에서 가리키는 모듈이라는 개념은 ES6+의 Modules 개념과 유사하고, 모듈은 전역 변수와 구분되는 자체 유효 범위를 가지며 export, import와 같은 키워드를 사용하지 않으면 다른 파일에서 접근할 수 없다.