TypeScript

Microsoft에서 개발하고 유지/관리하는 Apache 라이센스가 부여된 오픈소스

왜 타입스크립트를 써야할까?

강한 타입 언어는 높은 가독성과 코드 품질 등을 제공 가능하며 런타임이 아닌 컴파일 환경에서 에러가 발생해 치명적인 오류들을 더욱 더 쉽게 잡아낼 수 있음
JS는 타입 시스템이 없는 동적 프로그래밍 언어로, 변수는 문자열, 숫자, 불린 등 여러 타입의 값을 가질 수 있다. 이를 약한 타입 언어라고 표현할 수 있으며 비교적 유연하게 개발할 수 있는 환경을 제공하는 한편 런타임 환경에서 쉽게 에러가 발생할 수 있는 단점을 가짐
타입스크립트는 이러한 자바스크립트에 강한 타입 시스템을 적용해 대부분의 에러를 컴파일 환경에서 코드를 입력하는 동안 체크할 수 있음
TypeScript는 Compiled Language
(JS는 Interpreter Language)

TypeScript 사용법

JavaScript가 .js 확장자를 가진 파일로 작성되는 것과 같이 TypeScript는 .ts 확장자를 가진 파일로 작성
작성 후 TypeScript 컴파일러를 통해 JavaScript 파일로 컴파일하여 사용하게 됨
$ tsc sample.ts
Shell
복사

TypeScript의 기능

크로스 플랫폼 지원
객체 지향 언어
정적 타입
DOM 제어
최신 ECMAScript 기능 지원 : ES6 이상의 최신 자바스크립트 문법을 손쉽게 지원

설치 및 사용

TS Node

NodeJS 환경에서 테스트하기
$ mkdir typescript-test $ cd typescript-test $ npm init -y $ npm install -D typescript @types/node ts-node
Shell
복사
typescript 모듈은 typescript 코드를 ES5 형식의 자바스크립트 코드로 변환만 할 뿐, 실행하지 않음
ts-node를 설치해야 변환과 실행을 동시에 할 수 있다.
타입스크립트는 기본적으로 ESNext 자바스크립트 문법을 포함하고 있으나 자바스크립트와는 완전히 다른 언어
자바스크립트로 개발된 라이브러리들은 추가로 타입 라이브러리들을 제공해야한다.
→ @types/node 패키지를 설치해야한다.
디렉토리와 설치하려는 npm 라이브러리의 이름이 같다면 오류가 발생하므로 디렉토리 명을 확인할 것!
tsc —init 명령어를 사용해 tsconfig.json 파일을 생성하고 원하는 옵션을 추가합니다.
{ "compilerOptions": { "target": "es5", /* 컴파일 후 생성될 파일의 ECMAScript 버전 */ "module": "commonjs", /* 컴파일 후 생성될 파일이 사용하는 모듈 버전 버전: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "sourceMap": true, /* Generates corresponding '.map' file. */ "outDir": "dist", /* build 후 파일이 생성될 폴더 */ "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ "moduleResolution": "node", "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ "paths": { "*": ["node_modules/*"]}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ }, "include": ["src/**/*"] // 사용할 폴더 및 파일 }
JSON
복사
tsconfig.json 파일에서 작성한 include 항목은 설정된 디렉터리에 이 프로젝트의 모든 타입스크립트 소스 파일이 있다는 것을 의미
→ 해당 디렉터리 생성
src/index.ts src/utils/makePerson.ts 파일을 생성하고 타입스크립트 코드를 입력합니다.
import { testMakePerson } from './utils/makePerson'; testMakePerson();
TypeScript
복사
src/index.ts
export function makePerson(name: string, age: number) { return {name: name, age: age} } export function testMakePerson() { console.log( makePerson('Jane', 22), makePerson('Jack', 33) ) }
TypeScript
복사
src/utils/makePerson.ts
scripts 항목에 dev와 build 명령을 추가한다.
{ "name": "typescript-practice", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc && node dist", "dev": "ts-node src" }, "author": "", "license": "ISC", "dependencies": {}, "devDependencies": { "@types/node": "^16.7.1", "ts-node": "^10.2.1", "typescript": "^4.3.5" } }
JSON
복사
package.json
dev 명령은 개발 중 엔트리 함수를 실행하는 용도
build 명령은 개발 완료 후 프로그램을 배포하기 위해 dist 디렉터리에 ES5 자바스크립트 파일을 만들 때 사용

Types

타입 지정

일반 변수, 매개 변수(Parameter), 객체 속성(Propertyp) 등에 : TYPE과 같은 형태로 타입을 지정할 수 있습니다.
function someFunc(a: TYPE_A, b: TYPE_B): TYPE_RETURN { return a + b; } let some: TYPE_SOME = someFunc(1, 2);
TypeScript
복사
예시)
a와 b가 number 타입이라고 지정했고 함수의 반환 값은 추론되어 sum도 number 타입이어야한다고 지정됨.
function add(a: number, b: number) { return a + b; } const sum: number = add(1, 2); console.log(sum); // 3
TypeScript
복사

타입 에러

sum이 number가 아닌 string 타입이라고 지정된다면 컴파일조차 하지 않고 코드를 작성하는 시점에서 에러 발생
function add(a: number, b: number) { return a + b; } const sum: string = add(1, 2); console.log(sum);
TypeScript
복사
TS2322: Type 'number' is not assignable to type 'string'.
Plain Text
복사

타입 선언

boolean : 참 / 거짓
let isBoolean: boolean; let isDone: boolean = false;
TypeScript
복사
number : 모든 부동 소수점 값 사용
let num: number; let integer: number = 6; let float: number = 3.14; let hex: number = 0xf00d; // 61453 let binary: number = 0b1010; // 10 let octal: number = 0o744; // 484 let infinity: number = Infinity; let nan: number = NaN;
TypeScript
복사
string : 문자열
let str: string; let red: string = 'Red'; let green: string = "Green"; let myColor: string = `My color is ${red}.`; let yourColor: string = 'Your color is' + green;
TypeScript
복사
array<type> : 배열
// 문자열만 가지는 배열 let fruits: string[] = ['Apple', 'Banana', 'Mango']; let fruits: Array<string> = ['Apple', 'Banana', 'Mango']; // 숫자만 가지는 배열 let oneToSeven: number[] = [1, 2, 3, 4, 5, 6, 7]; let oneToSeven: Array<number> = [1, 2, 3, 4, 5, 6, 7]; // 유니언 타입의 배열 선언 let array: (string | number)[] = ['Apple', 1, 2, 'Banana', 'Mango', 3]; let array: Array<string | number> = ['Apple', 1, 2, 'Banana', 'Mango', 3]; // 배열이 가지는 항목의 값을 단언할 수 없을 때 let someArr : any[] = [0, 1, {}, [], 'str', false]; // 인터페이스 interface IUser { name: string, age: number, isValid: boolean } // 커스텀 타입 let userArr: IUser[] = [ { name: 'Neo', age: 85, isValid: true }, { name: 'Lewis', age: 52, isValid: false }, { name: 'Evan', age: 36, isValid: true } ]; // 특정한 값으로 타입을 대신해 작성 let array = 10[]; array = [10]; array.push(10); array.push(11); // Error - TS2345 // 읽기 전용 배열 let arrA: readonly number[] = [1, 2, 3, 4]; let arrB: ReadonlyArray<number> = [0, 9, 8, 7]; arrA[0] = 123; // Error - TS2542: Index signature in type 'readonly number[]' only permits reading. arrA.push(123); // Error - TS2339: Property 'push' does not exist on type 'readonly number[]'. arrB[0] = 123; // Error - TS2542: Index signature in type 'readonly number[]' only permits reading. arrB.push(123); // Error - TS2339: Property 'push' does not exist on type 'readonly number[]'.
TypeScript
복사
Tuple : 정해진 타입의 고정된 길이 배열
0let tuple: [string, number]; tuple = ['a', 1]; tuple = ['a', 1, 2]; // Error - TS2322 tuple = [1, 'a']; // Error - TS2322 // Tuple let user: [number, string, boolean] = [1234, 'HEROPY', true]; let user: [number, string, boolean][] = [[1, 'Neo', true], [2, 'Evan', false], [3, 'Lewis', true]]; let tuple: [1, number]; tuple = [1, 2]; tuple = [1, 3]; // Tuple은 정해진 타입의 고정된 길이 배열을 표현하지만 .push() .slice() 을 통해 값을 넣는 행위를 막을 수는 없음 let tuple: [string, number]; tuple = ['b', 1]; tuple.push(3); console.log(tuple); // ['b', 1, 3] tuple.push(true); // Error - TS2345: Argument of type 'true' is not assignable to paramerter of type 'string | number' // 읽기 전용 튜플 생성 let a: readonly [string, number] = ['Hello', 123]; a[0] = 'World'; // Error - TS2540: Cannot assign to '0' because it is a read-only property
TypeScript
복사
Emum : 숫자 혹은 문자열 값 집합에 이름(Member)을 부여할 수 있는 타입, 값의 종류가 일정한 범위로 정해져 있는 경우 유용
enum Week { Sun, Mon, Tue, Wed, Thu, Fri, Sat }
TypeScript
복사
Any : 모든 타입
let any: any = 123; any = 'Hello world'; any = {}
TypeScript
복사
Unknown : 알 수 없는 타입
Any와 같이 Unknown에는 어떤 타입의 값도 할당할 수 있지만, Unknown을 다른 타입에 할당 불가
let a: any = 123; let u: unknown = 123; let v1: boolean = a; // 모든 타입(any)은 어디든 할당할 수 있습니다. let v2: number = u; // 알 수 없는 타입(unknown)은 모든 타입(any)을 제외한 다른 타입에 할당할 수 없습니다. let v3: any = u; // OK! let v4: number = u as number; // 타입을 단언하면 할당할 수 있습니다.
TypeScript
복사
Object : 객체
컴파일러 옵션에서 엄격한 타입 검사(strict)를 true로 설정하면 null은 포함하지 않음
let obj: object = {}; let arr: object = []; let func: object = function () {}; let nullValue: object = null; let date: object = new Date(); // ...
TypeScript
복사
반복적으로 사용할 때는 interface나 type을 사용
interface IUser { name: string, age: number } let userA: IUser = { name: 'HEROPY', age: 123 }; let userB: IUser = { name: 'HEROPY', age: false, // Error email: 'thesecon@gmail.com' // Error };
TypeScript
복사
Null과 Undefined
모든 타입의 하위 타입이므로 모든 타입에 할당 가능
컴파일 옵션 strictNullChecks : true일 경우 할당 불가능 but, void에는 할당 가능
Void : 값을 반환하지 않는 함수
undefined를 반환
function hello(msg: string): void { console.log(`Hello ${msg}`); } const hi: void = hello('world'); // Hello world console.log(hi); // undefined
TypeScript
복사
Never : 절대 발생하지 않을 값, 어떠한 타입도 적용 불가
function error(message: string): never { throw new Error(message); }
TypeScript
복사
Union : 2개 이상의 타입을 허용하는 경우
let union: (string | number); union = 'Hello type!'; union = 123; union = false; // Error - TS2322: Type 'false' is not assignable to type 'string | number'.
TypeScript
복사
Intersection : &를 상요해 2개 이상의 타입을 조합하는 경우
// 기존 타입들이 조합 가능하다면 인터섹션을 활용할 수 있습니다. interface IUser { name: string, age: number } interface IValidation { isValid: boolean } const heropy: IUser = { name: 'Heropy', age: 36, isValid: true // Error - TS2322: Type '{ name: string; age: number; isValid: boolean; }' is not assignable to type 'IUser'. }; const neo: IUser & IValidation = { name: 'Neo', age: 85, isValid: true }; // 혹은 기존 타입(IUser, IValidation)과 비슷하지만, 정확히 일치하는 타입이 없다면 새로운 타입을 생성해야 합니다. interface IUserNew { name: string, age: number, isValid: boolean } const evan: IUserNew = { name: 'Evan', age: 36, isValid: false };
TypeScript
복사
Function
// myFunc는 2개의 숫자 타입 인수를 가지고, 숫자 타입을 반환하는 함수. let myFunc: (arg1: number, arg2: number) => number; myFunc = function (x, y) { return x + y; }; // 인수가 없고, 반환도 없는 경우. let yourFunc: () => void; yourFunc = function () { console.log('Hello world~'); };
TypeScript
복사

타입 추론

명시적으로 타입이 선언 되어있지 않은 경우, 타입을 추론해 제공
타입을 추론하는 경우
초기화된 변수
기본값이 설정된 매개 변수
반환 값이 있는 함수
// 초기화된 변수 `num` let num = 12; // 기본값이 설정된 매개 변수 `b` function add(a: number, b: number = 2): number { // 반환 값(`a + b`)이 있는 함수 return a + b; }
TypeScript
복사

타입 단언

타입 추론을 통해 판단할 수 있는 타입의 범주를 넘는 경우, 더 이상 추론하지 않도록 지시
// Error - TS2533: Object is possibly 'null' or 'undefined'. function fnA(x: number | null | undefined) { return x.toFixed(2); } // if statement function fnD(x: number | null | undefined) { if (x) { return x.toFixed(2); } } // Type assertion function fnB(x: number | null | undefined) { return (x as number).toFixed(2); } function fnC(x: number | null | undefined) { return (<number>x).toFixed(2); } // Non-null assertion operator function fnE(x: number | null | undefined) { return x!.toFixed(2); }
TypeScript
복사

타입 가드

// 기존 예제와 같이 `isNumber`를 제공(추상화)하지 않아도 `typeof` 연산자를 직접 사용하면 타입 가드로 동작합니다. // https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/typeof function someFuncTypeof(val: string | number) { if (typeof val === 'number') { val.toFixed(2); isNaN(val); } else { val.split(''); val.toUpperCase(); val.length; } } // 별도의 추상화 없이 `in` 연산자를 사용해 타입 가드를 제공합니다. // https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/in function someFuncIn(val: any) { if ('toFixed' in val) { val.toFixed(2); isNaN(val); } else if ('split' in val) { val.split(''); val.toUpperCase(); val.length; } } // 역시 별도의 추상화 없이 `instanceof` 연산자를 사용해 타입 가드를 제공합니다. // https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/instanceof class Cat { meow() {} } class Dog { woof() {} } function sounds(ani: Cat | Dog) { if (ani instanceof Cat) { ani.meow(); } else { ani.woof(); } }
TypeScript
복사

인터페이스

타입스크립트 여러 객체를 정의 하는 일종의 규칙이며 구조
interface IUser { name: string, age: number } // Or interface IUser { name: string; age: number; } // Or interface IUser { name: string age: number } let user1: IUser = { name: 'Neo', age: 123 };
TypeScript
복사
속성에 ?를 사용하면 선택적 속성으로 정의할 수 있음
interface IUser { name: string, age: number, isAdult?: boolean // Optional property } // `isAdult`를 초기화하지 않아도 에러가 발생하지 않습니다. let user: IUser = { name: 'Neo', age: 123 };
TypeScript
복사

읽기 전용 속성

readonly 키워드를 사용하면 초기화된 값을 유지해야 하는 읽기 전용 속성을 정의할 수 있음
// All readonly properties interface IUser { readonly name: string, readonly age: number } let user: IUser = { name: 'Neo', age: 36 }; user.age = 85; // Error user.name = 'Evan'; // Error // Readonly Utility interface IUser { name: string, age: number } let user: Readonly<IUser> = { name: 'Neo', age: 36 }; user.age = 85; // Error user.name = 'Evan'; // Error // Type assertion let user = { name: 'Neo', age: 36 } as const; user.age = 85; // Error user.name = 'Evan'; // Error
TypeScript
복사

함수 타입

interface IUser { name: string } interface IGetUser { (name: string): IUser } // 매개 변수 이름이 인터페이스와 일치할 필요가 없습니다. // 또한 타입 추론을 통해 매개 변수를 순서에 맞게 암시적 타입으로 제공할 수 있습니다. const getUser: IGetUser = function (n) { // n is name: string // Find user logic.. // ... return user; }; getUser('Heropy');
TypeScript
복사

클래스 타입

인터페이스로 클래스를 정의하는 경우 implements 키워드 사용
interface IUser { name: string, getName(): string } class User implements IUser { constructor(public name: string) {} getName() { return this.name; } } const neo = new User('Neo'); neo.getName(); // Neo
TypeScript
복사