자바스크립트에 타입을 부여한 언어
장점(드디어 각종 에러에서 벗어날 수 있는 것인가)
String
let str: string = 'hi';
Number
let num: number = 10;
Boolean
let isLoggedIn: boolean = false;
Object
Array
let arr1: number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];
Tuple: 배열의 길이가 고정되고 각 요소의 타입이 지정되어 있음
let arr: [string, number] = ['hi', 10];
Enum: C, Java에서 쓰이는 타입으로 특정 값의 집합을 의미함
enum Avengers { Capt, IronMan, Thor }
let hero: Avengers = Avengers.Capt;
Any: 모든 타입을 허용, 남발한다면 타입스크립트를 쓰는 이유가 없겠지
let str: any = 'hi';
let num: any = 10;
let arr: any = ['a', 2, true];
Void: 변수에는 undefined, null만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입
let unuseful: void = undefined;
function notuse(): void {
console.log('sth');
}
Null
Undefined
Never: 함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입
// 이 함수는 절대 함수의 끝까지 실행되지 않는다는 의미
function neverEnd(): never {
while (true) {
}
}
자바스크립트 함수 선언 방식에서 매개변수와 함수의 반환 값에 타입을 추가
function sum(a: number, b: number): number {
return a + b;
}
optional argument 설정
function sum(a: number, b?: number): number {
return a + b;
}
변수 디폴트 값 설정
function sum(a: number, b = '100'): number {
return a + b;
}
Rest 문법 적용
function sum(a: number, ...nums: number[]): number {
const totalOfNums = 0;
for (let key in nums) {
totalOfNums += nums[key];
}
return a + totalOfNums;
}
상호 간에 정의한 규칙을 말함
interface personAge {
age: number;
}
function logAge(obj: personAge) {
console.log(obj.age);
}
let person = { name: 'Capt', age: 28 };
logAge(person);
// 'logAge()의 인자는 personAge 라는 타입을 가져야한다'는 것을 알 수 있음
// 인터페이스를 인자로 받아 사용할 때는 인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 상관 없으며, 인터페이스에 선언된 속성 순서를 지키지 않아도 됨
옵션 속성
interface 인터페이스_이름 {
속성?: 타입;
}
읽기 전용 속성: 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 이후에는 변경 불가능한 속성
interface CraftBeer {
readonly brand: string;
}
읽기 전용 배열
let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error
인터페이스에서 정의하지 않은 속성들을 추가하고 싶을 때
interface CraftBeer {
brand?: string;
[propName: string]: any;
}
인터페이스에서 함수의 타입 정의하기
interface login {
(username: string, password: string): boolean;
}
let loginUser: login;
loginUser = function(id: string, pw: string) {
console.log('로그인 했습니다');
return true;
}
인터페이스 확장
interface Person {
name: string;
}
interface Developer extends Person {
skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';
하이브리드 타입: 여러 가지 타입 조합
interface CraftBeer {
(beer: string): string;
brand: string;
brew(): void;
}
function myBeer(): CraftBeer {
let my = (function(beer: string) {}) as CraftBeer;
my.brand = 'Beer Kitchen';
my.brew = function() {};
return my;
}
let brewedBeer = myBeer();
brewedBeer('My First Beer');
brewedBeer.brand = 'Pangyo Craft';
brewedBeer.brew();
숫자형 Enum
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Captain Pangyo", Response.Yes);
문자형 Enum
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
숫자형 Enum과 문자형 Enum을 혼합한 복합 Enum이 있지만 사용을 권고 하지 않음
런타임 시점에서 Enum은 실제 객체 형태로 존재함
enum E {
X, Y, Z
}
function getX(obj: { X: number }) {
return obj.X;
}
getX(E); // 이넘 E의 X는 숫자이기 때문에 정상 동작
-> 그렇지만 컴파일 시점에서는 keyof 대신 keyof type of를 사용할 것
enum LogLevel {
ERROR, WARN, INFO, DEBUG
}
// 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
type LogLevelStrings = keyof typeof LogLevel;
function printImportant(key: LogLevelStrings, message: string) {
const num = LogLevel[key];
if (num <= LogLevel.WARN) {
console.log('Log level key is: ', key);
console.log('Log level value is: ', num);
console.log('Log level message is: ', message);
}
}
printImportant('ERROR', 'This is a message');
Reverse Mapping
enum Enum {
A
}
let a = Enum.A; // 키로 값을 획득 하기
let keyName = Enum[a]; // 값으로 키를 획득 하기
Union Type
// logText의 파라미터 text에는 string이나 number가 모두 올 수 있음
function logText(text: string | number) {
// ...
}
Intersection Type
interface Person {
name: string;
age: number;
}
interface Developer {
name: string;
skill: number;
}
type Capt = Person & Developer;
// 이것은 결국
// {
// name: string;
// age: number;
// skill: string;
// }
readonly
class Developer {
readonly name: string;
constructor(readonly name: string) {
this.name = theName;
}
}
let john = new Developer("John");
john.name = "John"; // error! name is readonly.
Accessor: 객체의 특정 속성의 접근과 할당에 대해 제어 가능(get, set 사용)
class Developer {
private name: string;
get name(): string {
return this.name;
}
set name(newValue: string) {
if (newValue && newValue.length > 5) {
throw new Error('이름이 너무 깁니다');
}
this.name = newValue;
}
}
Abstract Class: 특정 클래스의 상속 대상이 되면서 좀 더 상위 레벨에서 속성, 메서드의 모양을 정의함
abstract class Developer {
abstract coding(): void; // 'abstract'가 붙으면 상속 받은 클래스에서 무조건 구현해야 함
drink(): void {
console.log('drink sth');
}
}
class FrontEndDeveloper extends Developer {
coding(): void {
// Developer 클래스를 상속 받은 클래스에서 무조건 정의해야 하는 메서드
console.log('develop web');
}
design(): void {
console.log('design web');
}
}
const dev = new Developer(); // error: cannot create an instance of an abstract class
const josh = new FrontEndDeveloper();
josh.coding(); // develop web
josh.drink(); // drink sth
josh.design(); // design web
타입을 함수의 파라미터처럼 사용하는 것, 즉 함수를 호출할 때 함수 안에서 사용할 타입을 넘겨줄 수 있음
function getText<T>(text: T): T {
return text;
}
아래 코드는 T라는 변수 타입을 받고, 인자 값으로는 배열 형태의 T를 받는다.
예를 들어 [1,2,3]을 배열을 받고 number를 돌려줌
function logText<T>(text: T[]): T[] {
console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
return text;
}
첫 번째 코드를 제네릭 인터페이스로 쓰면?
interface GenericLogTextFn {
<T>(text: T): T;
}
function logText<T>(text: T): T {
return text;
}
let myString: GenericLogTextFn = logText; // Okay
Generics class
class GenericMath<T> {
pi: T;
sum: (x: T, y: T) => T;
}
let math = new GenericMath<number>();
제네릭 제약 조건을 사용하여 두 객체 비교하기
// 첫번째 인자로 받는 개체에 없는 속성은 접근할 수 없게 제한함
function getProperty<T, O extends keyof T>(obj: T, key: O) {
return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.
타입스크립트가 코드를 해석해 나가는 동작을 의미함
보통 변수를 선언하거나 초기화 할 때 그리고 변수, 속성, 함수의 반환 값 등을 설정할 때 타입이 추론됨
타입 추론 방식
타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미함
구조적 타이핑: 코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단하는 것
interface Avengers {
name: string;
}
let hero: Avengers;
// 타입스크립트가 추론한 y의 타입은 { name: string; location: string; } 입니다.
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;
// capt의 속성 중에 name이 있기 때문에 capt이 hero 타입에 호환될 수 있음
Soundness: 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입은 일단 안전하다고 봄
특정 타입이나 인터페이스를 참조할 수 있는 타입 변수
새로운 타입 값을 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름만 붙이는 것
// string 타입을 사용할 때
const name: string = 'capt';
// 타입 별칭을 사용할 때
type MyName = string;
const name: MyName = 'capt';
type vs. interface
인터페이스는 확장 가능, 타입은 확장 불가능
-> 좋은 소프트웨어는 언제나 확장이 용이해야 한다는 원칙에 따라 가능한 type 보다는 interface로 선언해서 사용하는 것이 좋다
모듈: ES6+의 Modules의 개념과 유사하며 export, import와 같은 키워드를 사용한다
선언 파일
// 전역 변수
declare const pi = 3.14;
// 전역 함수
declare namespace myLib {
function greet(person: string): string;
let name: string;
}
myLib.greet('캡틴');
myLib.name = '타노스';
인덱싱
// 타입스크립트에서 배열 요소와 객체의 속성에 접근 할 때는 인터페이스를 이용하자
interface StringArray {
[index: number]: string;
}
const arr: StringArray = ['Thor', 'Hulk'];
arr[0]; // 'Thor'
Utility type
interface Address {
email: string;
address: string;
}
type MayHaveEmail = Partial<Address>;
const me: MayHaveEmail = {}; // 가능
const you: MayHaveEmail = { email: 'test@abc.com' }; // 가능
const all: MayHaveEmail = { email: 'capt@hero.com', address: 'Pangyo' }; // 가능
interface Hero {
name: string;
skill: string;
}
const human: Pick<Hero, 'name'> = {
name: '스킬이 없는 사람',
};
interface AddressBook {
name: string;
phone: number;
address: string;
company: string;
}
const phoneBook: Omit<AddressBook, 'address'> = {
name: '재택근무',
phone: 12342223333,
company: '내 방'
}
const chingtao: Omit<AddressBook, 'address'|'company'> = {
name: '중국집',
phone: 44455557777
}
Mapped type
기존에 정의되어 있는 타입을 새로운 타입으로 변환해주는 문법
// 기본 문법
{ [ P in K ] : T }
{ [ P in K ] ? : T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ? : T }
// 기본 예제
type Heroes = 'Hulk' | 'Thor' | 'Capt';
type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
Hulk: 54,
Thor: 1000,
Capt: 33,
}
// 실용 예제
// 사용자 프로필 조회 API
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
function fetchUserProfile(): UserProfile {
// ...
}
// 프로필 정보 수정 API
interface UserProfileUpdate {
username?: string;
email?: string;
profilePhotoUrl?: string;
}
function updateUserProfile(params: UserProfileUpdate) {
// ...
}
// 이때 동일한 타입에 대해 반복 선언하는 것은 좋지 않음
type UserProfileUpdate = {
username?: UserProfile['username'];
email?: UserProfile['email'];
profilePhotoUrl?: UserProfile['profilePhotoUrl'];
}
// 이것에 keyof를 적용하면?
type UserProfileUpdate = {
[p in keyof UserProfile]?: UserProfile[p]
}
tsconfig.json 파일은 타입스크립트를 자바스크립트로 변환할 때의 설정을 정의해놓는 파일
'tsc app.ts'라고 치면 app.ts파일이 app.js파일로 컴파일 됨
@types 라이브러리:일반적으로 index.d.ts 파일과 package.json 파일로 구성되어 있음