-
[TS] TypeScript 기초 - 6programing/Language 2020. 2. 1. 16:06
안녕하세요,Einere입니다.
(ADblock을 꺼주시면 감사하겠습니다.)
2020/01/24 - [programing/JavaScript] - [TS] TypeScript 기초 - 1
2020/01/25 - [programing/JavaScript] - [TS] TypeScript 기초 - 2
2020/01/26 - [programing/JavaScript] - [TS] TypeScript 기초 - 3
2020/01/28 - [programing/JavaScript] - [TS] TypeScript 기초 - 4
2020/01/31 - [programing/JavaScript] - [TS] TypeScript 기초 - 5
2020/02/01 - [programing/JavaScript] - [TS] TypeScript 기초 - 6
이번 포스트에서는 타입스크립트 핸드북을 간단하게 정리한 글입니다.
한글로 번역된 좋은 페이지도 있으므로, 글 하단에 링크 남겨두겠습니다.
Modules
TS는 ES6에 도입된 모듈 개념을 도입했습니다. 모듈은 각각 고유한 스코프를 가지며, 이는 export를 이용해 외부에 공개하기 전까지는 해당 모듈 내의 변수, 함수, 클래스 등 모든 것에 접근할 수 없다는 것을 의미합니다. (node같은 경우엔 commonJS를 따르기 때문에
module.exports
를 이용해 모듈을 공개하죠.)모듈은 선언적입니다. 즉, 모듈간 관계는 파일 레벨에서
import
와export
를 이용해 정의합니다.TS에서는 ES6와 같이, 탑 레벨 import와 export 선언을 가지는 파일을 모듈로 간주합니다. 그렇지 않은 파일은 글로벌 스코프에서 사용가능한 스크립트로 간주합니다.
Module Loader
모듈은 모듈 로더(module loader)에 의해 임포트 & 익스포트 됩니다.
모듈 로더는 런타임에 모듈들의 모든 의존성을 실행하고 위치시킵니다.
잘 알려진 모듈 로더에는 node앱을 위한 CommonJS와 RequireJS, 웹 앱을 위한 AMD가 있습니다.
Export
Exporting a declaration
// stringValidator.ts export interface StringValidator { isAcceptable(s: string): boolean; }
// ZipCodeValidator.ts import { StringValidator } from "./StringValidator"; export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } }
모든 선언은
export
키워드를 통해 익스포트할 수 있습니다.Export statements
// ZipCodeValidator.ts class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator };
익스포트문은 외부로 공개할 모듈의 이름을 변경할 때 유용합니다. 위 코드같은 경우, 기본적으로
ZipCodeValidator
를ZipCodeValidator
로 익스포팅하며, 추가로ZipCodeValidator
를mainValidator
로 앨리어싱해서 익스포팅합니다.Re-exports
// ParseIntBasedZipCodeValidator.ts export class ParseIntBasedZipCodeValidator { isAcceptable(s: string) { return s.length === 5 && parseInt(s).toString() === s; } } // Export original validator but rename it export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
종종 모듈은 다른 모듈을 상속하고 일부 기능만 공개해야 할 때가 있습니다. 이런 경우 리익스포팅이 유용합니다.
위 파일은
ParseIntBasedZipCodeValidator
클래스와ZipCodeValidator
를RegExpBasedZipCodeValidator
로 익스포팅합니다.// AllValidators.ts // exports 'StringValidator' interface export * from "./StringValidator"; // exports 'ZipCodeValidator' and const 'numberRegexp' class export * from "./ZipCodeValidator"; // exports the 'ParseIntBasedZipCodeValidator' class // and re-exports 'RegExpBasedZipCodeValidator' as alias of the 'ZipCodeValidator' class from 'ZipCodeValidator.ts' module. export * from "./ParseIntBasedZipCodeValidator";
혹은
export * from "moduleName"
을 이용해서 모듈을 모두 리익스포팅할 수 있습니다.// index.ts export * as utilities from "./utilities";
// app.ts import { utilities } from "./index";
TS 3.8부터는
export * as name from "moduleName"
을 사용할 수 있습니다. 이 경우, 가져오는 모듈의 의존성도 모두 가져옴을 의미합니다.Default exports
모든 모듈은 default키워드를 붙여 기본 익스포트를 할 수 있습니다. 그러나 한 모듈에는 최대 한개의 기본 익스포트가 존재할 수 있습니다.
기본 익스포트로 공개된 모듈은 임포트할때도 특별한 구문을 사용해야 합니다.
// JQuery.d.ts declare let $: JQuery; export default $;
// App.ts import $ from "jquery"; $("button.continue").html( "Next Step..." );
다른 임포트문과 달리, 기본 익스포트로 공개된 모듈은 임포트 할 때 비구조화 할당을 사용하지 않습니다.
// StaticZipCodeValidator.ts const numberRegexp = /^[0-9]+$/; // export anonymous function export default function (s: string) { return s.length === 5 && numberRegexp.test(s); }
// Test.ts import validate from "./StaticZipCodeValidator"; let strings = ["Hello", "98052", "101"]; // Use function validate strings.forEach(s => { console.log(`"${s}" ${validate(s) ? "matches" : "does not match"}`); });
클래스나 함수 선언은 바로 기본 익스포팅을 할 수 있습니다. 이 경우, 클래스나 함수의 이름은 옵션입니다. 즉, 익명 클래스 혹은 익명 함수를 익스포팅할 수 있습니다
Import
외부로 공개된 모듈은
import
키워드를 이용해 가져와 사용할 수 있습니다.기본 익스포트로 공개된 모듈 외에는 모두 비구조화 할당을 이용해 임포트한다는 점, 유의해주세요!
Import a single export from a module
import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator();
import와 비구조화 할당을 이용해 임포트할 수 있습니다.
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV();
익스포트와 마찬가지로, 임포트할 때 리네임(rename)할 수 있습니다.
Import the entire module into a single variable, and use it to access the module exports
import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
외부로 공개된 모듈을 하나의 변수로써 임포트할 수도 있습니다.
Import a module for side-effects only
import "./my-module.js";
권장되지는 않지만, 특정 모듈이 글로벌 상태를 설정하는 역할을 수행하는 모듈이 있을 수 있습니다.
이런 모듈을 사용하고자 하는 경우, 위 코드처럼 임포트하면 됩니다.
Importing Types
// Re-using the same import import {APIResponseType} from "./api"; // Explicitly use import type import type {APIResponseType} from "./api";
TS 3.8 미만버전에서는 자료형을 임포트할 때 단순히
import
만 사용합니다. 3.8 이후부터는import type
을 이용해 명시적으로 자료형을 임포트할 수 있습니다.명시적으로 자료형을 임포트하는 경우, JS로 컴파일된 코드에서 해당 코드를 제거하는것을 항상 보장합니다.
Namespaces
위에서 많이 본, 검증자 함수들을 한 파일에 구현한다고 가정해봅시다.
interface StringValidator { isAcceptable(s: string): boolean; } let lettersRegexp = /^[A-Za-z]+$/; let numberRegexp = /^[0-9]+$/; class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } } class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: StringValidator; } = {}; validators["ZIP code"] = new ZipCodeValidator(); validators["Letters only"] = new LettersOnlyValidator(); // Show whether each string passed each validator for (let s of strings) { for (let name in validators) { let isMatch = validators[name].isAcceptable(s); console.log(`'${ s }' ${ isMatch ? "matches" : "does not match" } '${ name }'.`); } }
JS같은 경우에는 위 코드와 같이, 빈 객체에 속성으로 넣고 쓰는 방법을 자주 사용합니다.
Namespacing
위의 인터페이스와 클래스들을 외부로 공개하기 위해, 모든 검증자들을 Validation이라는 네임스페이스로 묶어줍시다.
Namespaced Validators
namespace Validation { export interface StringValidator { isAcceptable(s: string): boolean; } const lettersRegexp = /^[A-Za-z]+$/; const numberRegexp = /^[0-9]+$/; export class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } } export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } } // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: Validation.StringValidator; } = {}; validators["ZIP code"] = new Validation.ZipCodeValidator(); validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator for (let s of strings) { for (let name in validators) { console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`); } }
외부로 공개될 필요가 없는
lettersRegexp
와numberRegexp
는 공개하고 싶지 않으므로,export
키워드를 붙이지 않습니다.Splitting Across Files
앱의 규모가 커짐에 따라, 유지보수를 위해 파일을 분리합니다.
Multi-file namespaces
// Validation.ts namespace Validation { export interface StringValidator { isAcceptable(s: string): boolean; } }
// LettersOnlyValidator.ts /// <reference path="Validation.ts" /> namespace Validation { const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } } }
// ZipCodeValidator.ts /// <reference path="Validation.ts" /> namespace Validation { const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } }
// Test.ts /// <reference path="Validation.ts" /> /// <reference path="LettersOnlyValidator.ts" /> /// <reference path="ZipCodeValidator.ts" /> // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: Validation.StringValidator; } = {}; validators["ZIP code"] = new Validation.ZipCodeValidator(); validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator for (let s of strings) { for (let name in validators) { console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`); } }
각 파일마다 reference 태그를 추가하므로써, TS 컴파일러에게 각 파일 간 의존성을 알려줍니다.
이제 모든 컴파일된 코드가 로드되도록 해야 합니다. 여기엔 두가지 방법이 있습니다.
첫번째로, 의존 관계가 있는 네임스페이스 코드를 하나의 js파일로 컴파일 하는 방법이 있습니다.
tsc --outFile sample.js Test.ts
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
모든 의존관계 파일을 나열할 수도 있습니다.
<!-- MyTestPage.html --> <script src="Validation.js" type="text/javascript" /> <script src="LettersOnlyValidator.js" type="text/javascript" /> <script src="ZipCodeValidator.js" type="text/javascript" /> <script src="Test.js" type="text/javascript" />
혹은 각 파일을 개별적으로 컴파일 한 뒤, 위 코드처럼 차례대로 불러오는 방법도 가능합니다.
Aliases
namespace Shapes { export namespace Polygons { export class Triangle { } export class Square { } } } import polygons = Shapes.Polygons; let sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'
같은 파일 내에 있다면, 위 코드처럼 import를 이용해 앨리어싱할 수 있습니다. (이것을 보통 앨리어스라고 부른다고 하네요.)
앨리어스는 require를 사용하는 대신, 식별자에 바로 할당합니다.
앨리어스는 var와 유사하지만, 임포트한 심볼의 자료형과 네임스페이스 의미에 대해서도 작동합니다. (뭔소리여)
특히 값에 대한 측면에서 import는 개별 레퍼런스이므로, var로 앨리어싱된 변수의 변경은 원본 변수에 대해 영향을 끼치지 않습니다. (대충 깊은 복사라는 느낌인 것 같네요.)
참고
https://www.typescriptlang.org/docs/handbook/modules.html
https://www.typescriptlang.org/docs/handbook/namespaces.html
https://typescript-kr.github.io/
'programing > Language' 카테고리의 다른 글
[TS] TS with React - hook 사용하기 1 (0) 2020.02.23 [TS] Interface vs Type (0) 2020.02.20 [TS] TypeScript 기초 - 5 (0) 2020.01.31 [TS] TypeScript 기초 - 4 (0) 2020.01.28 [TS] TypeScript 기초 - 3 (0) 2020.01.26 댓글