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
복사