TypeScript는 공백 상태가 아닙니다. JavaScript 생태계를 바탕으로 구축되었으며, 오늘날 많은 JavaScript가 존재합니다. JavaScript 코드 베이스를 TypeScript로 변환하는 것은 다소 지루하지만 어렵진 않습니다. 이 튜토리얼에서 어떻게 시작하는지 살펴보겠습니다. TypeScript 코드를 작성하기 위해 핸드북을 충분히 읽었다고 가정하고 설명하겠습니다.

React 프로젝트를 변환하고자 한다면, React 변환 가이드를 먼저 읽어보는 것을 추천합니다.

디렉토리 설정하기 (Setting up your Directories)

일반 JavaScript로 작성하는 경우, src, lib 또는 dist 디렉터리에 있는 .js 파일이 JavaScript를 직접 실행한 다음, 원하는 대로 실행했을 가능성이 높습니다.

이 경우, 작성한 파일은 TypeScript에 입력으로 사용되고, 그로 인한 출력을 실행하게 됩니다. JS에서 TS로의 전환하는 동안, TypeScript가 입력 파일을 겹쳐 쓰는 것을 방지하기 위해 입력 파일을 분리할 필요가 있습니다. 만약 출력 파일이 특정 디렉터리에 위치해야 하는 경우, 그 위치가 출력 디렉터리가 되어야 합니다.

또한 JavaScript에서 번들링을 하거나 바벨 같은 트랜스파일러를 사용하는 것처럼, 중간 단계를 실행할 수 있습니다. 이러한 경우, 이렇게 설정된 폴더 구조를 가지고 있을 수 있습니다.

이 시점부터, 디렉터리가 다음과 같이 설정되었다고 가정하겠습니다:

projectRoot
├── src
│   ├── file1.js
│   └── file2.js
├── built
└── tsconfig.json

만약 src 디렉터리 바깥에 tests 폴더가 존재한다면, srctests 내부에 각각 tsconfig.json이 존재할 수 있습니다.

설정 파일 작성하기 (Writing a Configuration File)

TypeScript는 어떤 파일을 포함하고, 어떤 종류의 체크가 수행되어야 하는지와 같은 프로젝트 옵션을 관리하기 위해, tsconfig.json이라 불리는 파일을 사용합니다. 프로젝트의 뼈대를 구성해 보겠습니다:

{
    "compilerOptions": {
        "outDir": "./built",
        "allowJs": true,
        "target": "es5"
    },
    "include": [
        "./src/**/*"
    ]
}

TypeScript에 대한 몇 가지 사항을 명시하고 있습니다:

  1. src 디렉터리에서 해석되는 모든 파일을 읽습니다 (include 포함).
  2. JavaScript 파일을 입력으로 허용합니다 (allowJs 포함).
  3. built 내부의 모든 출력 파일을 내보냅니다 (outDir 포함).
  4. 최신 JavaScript 구성을 ECMAScript 5와 같은 이전 버전으로 변환합니다(target 사용).

이 시점에서, 프로젝트의 루트에서 tsc를 작동시키려면, 반드시 built 디렉터리에 있는 출력 파일이 표시되어야 합니다. built 안의 레이아웃 파일은 src의 레이아웃과 동일해야 합니다. 이제 프로젝트가 TypeScript로 작동할 것입니다.

초기 혜택 (Early Benefits)

TypeScript가 프로젝트를 이해하는 것으로부터 몇 가지 큰 혜택을 받을 수 있습니다. VS CodeVisual Studio 에디터를 열어보면, 자동완성과 같은 툴링 지원을 받는 것을 볼 수 있습니다. 또한 다음 옵션이 들어 있는 특정 버그도 잡을 수 있습니다:

  • 함수의 마지막에 return을 빠뜨리는 것을 방지하는 noImplicitReturns
  • switch 블록의 case 사이에 break를 빠뜨리는 것을 절대 잊지 않기 위한 noFallthroughCasesInSwitch

또한 allowUnreachableCodeallowUnusedLabels 각각을 사용해, TypeScript는 도달할 수 없는 코드와 라벨에 대한 경고를 할 것입니다.

빌드 툴과 통합하기 (Integrating with Build Tools)

파이프라인에 더 많은 제작 단계가 있을 수 있습니다. 각각의 파일에 무언가를 연결할 수도 있습니다. 개별 빌드 도구는 다르지만, 빌드 도구의 핵심을 다루기 위해 최선을 다할 것입니다.

Gulp

만약 Gulp를 어떤 방식으로 사용하고 있다면, TypeScript와 Gulp를 사용하는 방법과 Browserify, Babelify, Uglify 같은 일반적인 빌드 툴과 통합하는 방법에 대한 튜토리얼이 있습니다. 그곳에서 더 많은 내용을 볼 수 있습니다.

Webpack

Webpack 통합은 꽤 간단합니다. 쉬운 디버깅을 위해 source-map-loader와 결합한 TypeScript 로더, awesome-typescript-loader를 사용할 수 있습니다. 단순히 실행하고

npm install awesome-typescript-loader source-map-loader

다음 옵션에서 webpack.config.js 파일과 병합하세요:

module.exports = {
    entry: "./src/index.ts",
    output: {
        filename: "./dist/bundle.js",
    },

    // webpack의 출력을 디버깅하기 위해 소스맵을 활성화 합니다.
    devtool: "source-map",

    resolve: {
        // 해석 가능한 확장자로 '.ts' 와 '.tsx' 를 추가합니다.
        extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
    },

    module: {
        loaders: [
            // '.ts' 나 '.tsx' 확장자로 끝나는 모든 파일은 'awesome-typescript-loader'에 의해 처리됩니다.
            { test: /\.tsx?$/, loader: "awesome-typescript-loader" }
        ],

        preLoaders: [
            // 모든 출력 '.js' 파일은 'source-map-loader'에 의해 재처리된 소스맵을 갖습니다.
            { test: /\.js$/, loader: "source-map-loader" }
        ]
    },

    // 다른 옵션들...
};

awesome-typescript-loader는 다른 로더가 .js 파일을 처리하기 전에 실행되어야 한다는 점을 유의하세요.

Webpack을 위한 또 다른 TypeScript 로더, ts-loader도 같습니다. 여기에서 둘 사이의 차이점을 읽을 수 있습니다.

React와 Webpack 튜토리얼에서 Webpack 사용에 관한 예제를 볼 수 있습니다.

TypeScript 파일로 이동하기 (Moving to TypeScript Files)

이제, TypeScript 파일을 사용해 시작할 준비가 되었을 것입니다. 첫 번째 단계는 .js 파일을 .ts 파일로 이름을 바꾸는 것입니다. 파일이 JSX를 사용한다면 이름을 .tsx로 변경하세요.

그 단계를 마치셨나요? 좋습니다! JavaScript에서 TypeScript로 파일을 성공적으로 마이그레이션 했습니다!

당연히, 그것이 바로 느껴지지는 않을 것입니다. TypeScript를 지원하는 에디터로 파일을 열어보면 (또는 tsc --pretty를 실행하면), 특정 줄에 빨간 구불구불한 선이 나타날 것입니다. Microsoft Word 같은 에디터의 빨간 구불구불한 선처럼 생각하면 됩니다. Word가 문서를 여전히 프린트할 수 있는 것처럼, TypeScript도 여전히 코드를 해석할 수 있습니다.

만약 그것이 너무 느슨해 보인다면, 더 엄격하게 행동할 수 있습니다. 예를 들어, 오류 발생 시 JavaScript를 TypeScript로 컴파일 하지 않으려면, noEmitOnError 옵션을 사용할 수 있습니다. 그러한 의미에서, TypeScript는 이상적인 엄격함을 갖고 있고, 원하는 만큼 그 기준을 높일 수 있습니다.

최대한 엄격한 세팅을 사용할 계획이라면, 지금 설정하는 것이 좋습니다(더 엄격한 체크하기 참조). 예를 들어, TypeScript가 명시적 설명 없이는 타입을 any로 추론하지 않기를 원한다면, 파일을 수정하기 전 noImplicitAny를 사용할 수 있습니다. 다소 부담스럽게 느껴질 수 있지만, 장기적으로 훨씬 이득입니다.

오류 제거하기 (Weeding out Errors)

언급했던 것처럼, 전환 후 에러 메시지가 뜨는 것은 예상하지 못했습니다. 중요한 점은 실제로 하나하나의 오류를 어떻게 처리할 것인지 결정하는 것입니다. 종종 이것이 합법적인 버그가 될 수 있지만, 때때로 TypeScript에게 무엇을 더 잘하려고 하는지 설명해야 합니다.

모듈로부터 import 하기 (Importing from Modules)

시작할 때 Cannot find name 'require'., and Cannot find name 'define'. 같은 에러가 많이 나타날 수 있습니다. 이러한 경우, 모듈을 사용할 수 있습니다. 아래의 선언을 통해 TypeScript에게 이러한 기능이 존재한다고 납득시킬 수 있지만

// For Node/CommonJS
declare function require(path: string): any;

또는

// For RequireJS/AMD
declare function define(...args: any[]): any;

이러한 호출을 제거하고 import를 위한 TypeScript 구문을 사용하는 것이 더 낫습니다.

먼저, TypeScript module 플래그를 설정함으로써 모듈 시스템을 활성화해야 합니다. 유효한 옵션은 commonjs, amd, system, 그리고 umd입니다.

만약 다음과 같은 Node/CommonJS 코드를 갖고 있다면:

var foo = require("foo");

foo.doStuff();

또는 다음의 RequireJS/AMD 코드를 갖고 있다면:

define(["foo"], function(foo) {
    foo.doStuff();
})

그러면 다음의 TypeScript 코드를 작성해야 합니다:

import foo = require("foo");

foo.doStuff();

선언 파일 시작하기 (Getting Declaration Files)

만약 TypeScript import로 전환을 시작했다면, Cannot find module 'foo'. 같은 오류가 발생할 수 있습니다. 여기서 문제는 라이브러리를 설명하는 선언 파일이 없을 가능성이 높다는 것입니다. 다행히 해결 방법은 꽤 쉽습니다. 만약 TypeScript가 lodash 같은 패키지에 대해 경고를 발생시키면, 그냥 작성하면 됩니다

npm install -S @types/lodash

commonjs 말고 다른 모듈 옵션을 사용한다면, moduleResolutionnode로 설정해야 합니다.

그 후, lodash를 문제없이 import 할 수 있고, 정확하게 완성할 수 있습니다.

모듈 export 하기 (Exporting from Modules)

전형적으로, 모듈을 export 하는 것은exports 혹은 module.exports 같은 값을 프로퍼티에 추가하는 것을 포함합니다. TypeScript는 최상위-레벨의 export 문을 허용합니다. 예를 들어, 함수를 이렇게 export 했다면:

module.exports.feedPets = function(pets) {
    // ...
}

그것을 다음과 같이 작성할 수 있습니다:

export function feedPets(pets) {
    // ...
}

때로 exports 객체를 완전히 재작성할 수 있습니다. 아래 예제처럼 즉시 호출하기 위해 이러한 흔한 패턴을 사용합니다:

var express = require("express");
var app = express();

전에는 이렇게 작성했을 수 있습니다:

function foo() {
    // ...
}
module.exports = foo;

TypeScript에서, 이것을 export = 구문을 사용하여 모델링 할 수 있습니다.

function foo() {
    // ...
}
export = foo;

너무 많은/너무 적은 인수 (Too many/too few arguments)

때로 너무 많은/너무 적은 인수를 갖고 있는 함수를 호출할 때가 있습니다. 전형적인 버그이지만, 그러나 몇몇 경우, 어떠한 매개변수를 쓰는 대신 arguments 객체를 사용하는 함수를 선언할 수 있습니다:

function myCoolFunction() {
    if (arguments.length == 2 && !Array.isArray(arguments[1])) {
        var f = arguments[0];
        var arr = arguments[1];
        // ...
    }
    // ...
}

myCoolFunction(function(x) { console.log(x) }, [1, 2, 3, 4]);
myCoolFunction(function(x) { console.log(x) }, 1, 2, 3, 4);

이 경우, TypeScript를 사용해서 호출자에게 함수 오버로드를 사용해 myCoolFunction이 호출되는 방법을 알려주어야 합니다.

function myCoolFunction(f: (x: number) => void, nums: number[]): void;
function myCoolFunction(f: (x: number) => void, ...nums: number[]): void;
function myCoolFunction() {
    if (arguments.length == 2 && !Array.isArray(arguments[1])) {
        var f = arguments[0];
        var arr = arguments[1];
        // ...
    }
    // ...
}

myCoolFunction에 오버로드 시그니처 두 개를 추가했습니다. 첫 번째 검사는 myCoolFunction이 (number를 인수로 갖는) 함수와 number 배열을 가진다는 것을 명시합니다. 두 번째 검사는 myCoolFunction이 마찬가지로 함수를 가지고, 나머지 연산자(...nums)를 사용하여 그 외의 인수는 몇개의 인수든 number가 되어야 함을 명시합니다.

순차적으로 추가된 프로퍼티 (Sequentially Added Properties)

어떤 사람들은 객체를 생성하고 다음과 같이 동적으로 프로퍼티를 추가하는 것이 미관상 더 보기 좋다고 생각합니다:

var options = {};
options.color = "red";
options.volume = 11;

TypeScript는 options을 프로퍼티가 없는 {}로 인식했기 때문에 colorvolume을 할당할 수 없다고 할 것입니다. 만약 선언을 리터럴 객체로 변경하면, 오류가 발생하지 않습니다:

let options = {
    color: "red",
    volume: 11
};

또한 options의 타입을 정의해야 하고 객체 리터럴에 대한 타입 단언을 추가해야 합니다.

interface Options { color: string; volume: number }

let options = {} as Options;
options.color = "red";
options.volume = 11;

대신, options이 단순히 타입any를 갖는다고 명시할 수 있는데, 이 방법은 가장 쉬운 방법이지만 가장 적은 장점을 가지고 있습니다.

any, Object, and {}

Object는 대부분의 경우 가장 일반적인 타입이므로, 값이 어떤 프로퍼티도 가질 수 있다고 말하기 위해 Object{}를 사용하고 싶을 수 있습니다. 하지만 any가 가장 유연한 타입이므로, 이러한 경우에는 실제로 any가 가장 적절한 타입 입니다.

예를 들어, Object를 타입으로 선언한 경우 toLowerCase()같은 메서드를 호출할 수 없습니다. 더 일반적으로 사용한다는 것은 타입으로 더 적은 일을 할 수 있다는 것을 의미하지만, any는 어떤 일이든 할 수 있게 하는 동시에 가장 일반적인 타입이라는 점에서 특별합니다. 그것은 any를 호출하고, 구성하고, 프로퍼티에 접근하는 등의 일을 할 수 있다는 것을 의미합니다. 그러나 any를 사용하면 TypeScript가 제공하는 대부분의 타입 검사와 에디터 지원을 받을 수 없다는 것을 명심하세요.

만약 Object{}로 결정이 내려지면, {}를 선택해야 합니다. 이 둘은 거의 같지만, 특정 난해한 상황에서 기술적으로 {}Object보다 더 일반적인 타입입니다.

더 엄격한 체크하기 (Getting Stricter Checks)

TypeScript는 프로그램에 대한 안정성과 분석을 제공하는 특정한 검사를 갖고 있습니다. TypeScript로 코드베이스를 시작하면, 향상된 안전성을 위한 검사를 활성화할 수 있습니다.

암시적인 any는 피하기 (No Implicit any)

어떤 타입이어야 하는지 TypeScript가 파악할 수 없는 경우가 있습니다. 최대한 유연하게 대응하기 위해, 그 자리에 any를 사용하기로 결정할 것입니다. 이것은 마이그레이션에는 좋지만, any를 사용한다는 것은 다른 곳에서 받을 수 있는 어떠한 타입 안정성과 툴링 지원도 받지 못한다는 것을 의미합니다. TypeScript가 이런 부분에 플래그와 에러를 띄울 수 있도록 noImplicitAny옵션을 사용할 수 있습니다.

엄격한 null & undefined 검사 (Strict null & undefined Checks)

기본적으로, TypeScript는 nullundefined이 모든 타입의 도메인에 존재한다고 가정합니다. number로 선언된 타입이 null 혹은 undefined이 될 수 있다는 의미입니다. nullundefined는 JavaScript 와 TypeScript 에서 빈번한 버그 원인이기 때문에, TypeScript 에는 이러한 문제의 걱정을 덜어주는 strictNullChecks 옵션이 있습니다.

strictNullChecks가 활성화되면, nullundefined는 각각 nullundefined라는 자체 유형을 가져옵니다. 어떤 것이 null될 가능성이 있는 상황에서, 원래 타입과 함께 유니언 타입을 사용할 수 있습니다. 예를 들어, 만약 numbernull이 될수 있는 경우, number | null로 타입을 작성할 수 있습니다.

TypeScript가 null/undefined라고 생각할 수 있는 값을 갖고 있지만, 타입에 대해 더 잘 알고 있는 경우, 후위 연산자 !를 사용해 다르게 사용할 수 있습니다.

declare var foo: string[] | null;

foo.length;  // error - 'foo' is possibly 'null'

foo!.length; // okay - 'foo!' just has type 'string[]'

앞으로, strictNullChecks를 사용할 때, 의존성이 strictNullChecks를 사용하도록 업데이트 되어야 할 수 있습니다.

this에 대한 암시적 any 피하기 (No Implicit any for this)

this 키워드를 클래스 밖에서 사용할 때, 기본적으로 any 타입을 가집니다. 예를 들어, Point 클래스를 상상해 보세요, 그리고 메서드로 추가하고 싶은 함수를 상상해보세요:

class Point {
    constructor(public x, public y) {}
    getDistance(p: Point) {
        let dx = p.x - this.x;
        let dy = p.y - this.y;
        return Math.sqrt(dx ** 2 + dy ** 2);
    }
}
// ...

// 인터페이스를 다시 열어보세요.
interface Point {
    distanceFromOrigin(point: Point): number;
}
Point.prototype.distanceFromOrigin = function(point: Point) {
    return this.getDistance({ x: 0, y: 0});
}

위에서 언급 한 것과 같은 문제가 있습니다 - getDistance의 철자를 틀리기 쉽고 에러가 발생하지 않았습니다. 이러한 이유 때문에, TypeScript 에 noImplicitThis 옵션이 있습니다. 이 옵션이 설정되면, TypeScript는 this가 명시적 타입 없이 사용될 때 에러를 발생시킵니다. 해결책은 인터페이스나 함수 자체에서 명시적 타입을 전달하기 위해 this-매개변수를 사용하는 것입니다:

Point.prototype.distanceFromOrigin = function(this: Point, point: Point) {
    return this.getDistance({ x: 0, y: 0});
}

results matching ""

    No results matching ""