Skip to content

TypeScript

타입스크립트(TypeScript)는 자바스크립트의 슈퍼셋인 오픈소스 프로그래밍 언어이다. 마이크로소프트에서 개발, 유지하고 있으며 엄격한 문법을 지원한다. C#의 리드 아키텍트이자 델파이, 터보 파스칼의 창시자인 Anders Hejlsberg가 개발에 참여한다. 클라이언트 사이드와 서버 사이드를 위한 개발에 사용할 수 있다.

타입스크립트는 자바스크립트 엔진을 사용하면서 커다란 애플리케이션을 개발할 수 있게 설계된 언어이다. 자바스크립트의 슈퍼셋이기 때문에 자바스크립트로 작성된 프로그램이 타입스크립트 프로그램으로도 동작한다.

타입스크립트에서 자신이 원하는 타입을 정의하고 프로그래밍을 하면 자바스크립트로 컴파일되어 실행할 수 있다.

타입스크립트는 모든 운영 체제, 모든 브라우저, 모든 호스트에서 사용 가능한 오픈 소스이다.

Categories

Projects

Type Export

Validation

Generate Declaration File

--declaration, -d
Generate .d.ts files from TypeScript and JavaScript files in your project.
--declarationMap
Create sourcemaps for d.ts files.
--emitDeclarationOnly
Only output d.ts files and not JavaScript files.

NonNullAssertion

또한, 데코레이터에 정의된 모든 멤버 변수에 붙어있는 ! 가 NonNullAssertion 오퍼레이터라는 기능입니다.

! 가 붙은 속성이 Null/Undefined가 아님을 명시합니다.

사전(Dictionary) 타입 선언

interface IPerson {
   firstName: string;
   lastName: string;
}
var persons: { [id: string] : IPerson; } = {};
persons["p1"] = { firstName: "F1", lastName: "L1" };

위의 방법 보다 JavaScript:Map을 사용하면 된다.

Override toString method

class Foo {
    private id: number = 23423;
    public toString = () : string => {
        return `Foo (id: ${this.id})`;
    }
}

Type Casting

Integer parser:

string numStr = "123";
int numNum;

bool isParsable = Int32.TryParse(numStr, out numNum);

Casting:

let whatNum: any = 42;
let reallyNum = <number>whatNum;
console.log(typeof reallyNum); // number

as syntax:

const clueless: unknown = "1";
const clueNum: number = <number>clueless;

// another format
const clueNumPreferred = clueless as number;

Disable type checking

코드에 인라인 하는 주석은 다음과 같다.

// @ts-ignore

또는 tsconfig.json를 다음과 같이 수정해라:

{
  "compilerOptions": {
    ...
    "checkJs": false
    ...
  }
}

Exsits element

"key" in obj // true, regardless of the actual value

Enum name to string

enum Enum {
    A
}
let nameOfA = Enum[Enum.A]; // "A"

String enum type

export type RouterMode = 'hash' | 'history' | 'abstract'

딕셔너리의 키 값을 참조하고 싶다면 다음과 같이 keyof를 사용해도 된다:

export interface ChartTypeRegistry {
  bar: {
    chartOptions: BarControllerChartOptions;
    datasetOptions: BarControllerDatasetOptions;
    defaultDataPoint: number;
    metaExtensions: {};
    parsedDataType: BarParsedData,
    scales: keyof CartesianScaleTypeRegistry;
  };
  line: {
    chartOptions: LineControllerChartOptions;
    datasetOptions: LineControllerDatasetOptions & FillerControllerDatasetOptions;
    defaultDataPoint: ScatterDataPoint | number | null;
    metaExtensions: {};
    parsedDataType: CartesianParsedData;
    scales: keyof CartesianScaleTypeRegistry;
  };
  // ...
}

export type ChartType = keyof ChartTypeRegistry;

Export imported interface

// export the default export of a legacy (`export =`) module
export import MessageBase = require('./message-base');

// export the default export of a modern (`export default`) module
export { default as MessageBase } from './message-base';

// when '--isolatedModules' flag is provided it requires using 'export type'.
export type { default as MessageBase } from './message-base';

// export an interface from a legacy module
import Types = require('./message-types');
export type IMessage = Types.IMessage;

// export an interface from a modern module
export { IMessage } from './message-types';

특정 파일의 전체 내용 export. index.ts 같은 파일에 유용.

export * from "./StringValidator";

대표 Export 객체로 하위 객체 접근 가능하도록 하기 (* as를 지우는 방법)

import * as MyLib from 'my-lib'; 과 같은 방식이 아닌, * as를 지운 import MyLib from 'my-lib'; 방식으로 Export 하고싶다면:

import Button from './Button.tsx';
export {type ButtonProps} from './Button.tsx';

export {Button};

export const YourUi = {
  Button,
};
export default YourUi;

extends vs implements

다음과 같은 차이점 존재:

  • extends 키워드는 class 선언문이나 class 표현식에서 만들고자하는 class의 하위 클래스를 생성할 때 사용한다.
  • implements 키워드는 class의 interface에 만족하는지 여부를 체크할 때 사용된다.

Mixin

TypeScript:Mixins 항목 참조.

Declaration files

선언 파일. *.d.ts 파일로 되어있다.

// token-service.d.ts

import Vue from 'vue'
import TokenService from '../path/to/token-service'

declare module 'vue/types/vue' {
  interface Vue {
    $tokenService: TokenService
  }
}

answer 프로젝트에서 다음과 같이 사용했다:

import Vue from 'vue'
import { REST_API } from "./services/api";

declare module 'vue/types/vue' {
  interface Vue {
    $api: REST_API;
  }
}

Current class and method name?

Object.getOwnPropertyNames 함수에 클래스의 프로토타입(class.prototype)을 인자로 넘기면 된다.

class Foo {
    bar() {
        console.log(Object.getOwnPropertyNames(Foo.prototype)); // ["constructor", "bar"]
    }
}
new Foo().bar();

이제 없애야할 나쁜 TypeScript 습관들

  • 10 bad TypeScript habits to break this year
    1. strict 모드를 사용하지 않는 것
    2. 디폴트 값을 || 로 정의 하는 것
      • ??를 쓰거나, 파라미터 단위 폴백 정의할 것
    3. any를 타입으로 쓰는 것
      • unknown 으로 교체할 것
    4. val as SomeType
      • 타입가드 함수로 체크할 것
    5. 테스트에서 as any 사용하는 것
    6. Optional 속성
    7. 한글자 Generic
      • 이름만으로 설명가능한 풀 타입 네임 사용
    8. Non-boolean boolean 검사
    9. !! 연산자
    10. != null

TypeScript 지원

대부분의 경우, 타입 선언 패키지 이름은 항상 npm 상의 패키지 이름과 같아야 하지만, @types/ 가 앞에 붙어야 합니다. 하지만 필요시 https://aka.ms/types 를 방문해 선호하는 라이브러리의 패키지를 찾으세요.

타입 관련 충돌시 점검 사항

package.json 파일의 resolutions 속성:

  "resolutions": {
    "@types/react": "18.2.0",
    "@types/react-dom": "18.2.0"
  },

tsconfig.json의 컴파일러 옵션들:

{
  "compilerOptions": {
    "paths": {
      "react": ["./node_modules/@types/react"]
    },
    "types": ["jest", "react", "react-dom"],
  }
}

Troubleshooting

Could not find a declaration file for module 'xxxxx'

Method 1 - To avoid the issue I just replace it by the standard require()

:

const vue-xxxxxx = require('vue-xxxxxx');
// import { vue-xxxxxx } from 'vue-xxxxxx'
Method 2 - To avoid the issue I just replace these line in the tsconfig.json

:

{
  compilerOptions: {
    "noImplicitAny": false,
    "allowJs": true,
  }
}

TS2307: Cannot find module '@/xxxx' or its corresponding type declarations

다음과 같은 에러가 IDE(e.g. webstorm) 또는 tsc에서 출력될때,

TS2307: Cannot find module '@/crypto' or its corresponding type declarations.

직접 yarn tsc를 돌려, 에러가 있나 확인해 보자. 일반적인 문제는 다음과 같다.

  • tsconfig.json의 compilerOptions.moduleResolutionnode로 설정.
  • tsconfig.json의 compilerOptions.paths에 교체할 경로 추가. 주로 '@/source' 처럼 되어있다면, "@/*": ["src/*"]를 추가.
  • 위의 compilerOptions.paths에 추가된 내용이 상대경로가 아닐 경우 (e.g. "@/*": ["src/*"]) compilerOptions.baseUrl추가. 일반적으로 "baseUrl": "." 이다.
  • tsconfig.json의 include, exclude에 포함, 비포함 경로 추가. 다음과 같이:

{

"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"],
"exclude": ["node_modules", "dist"]

}

</syntaxhighlight>

추가: 만약 *.d.ts 파일과 같이 타입 선언을 목적으로 한 파일이 문제가 된다면 compilerOptions.skipLibChecktrue로 하면 해결된다.

SVG 같은 이미지의 경우

TypeScript와 함께 비코드 애셋을 사용하려면 이러한 import에 대한 타입을 연기해야 합니다. 이를 위해서 프로젝트에 TypeScript에 대한 사용자 정의를 나타내는 custom.d.ts 파일이 필요합니다.

여러 순수 TypeScript Library 에서 파일 타입 자체 크게 관심 없을 경우

declare module "*.png";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.svg";
declare module "*.gif";

SVG 파일 하나 Default Export:

declare module '*.svg' {
  const content: any;
  export default content;
}

여기에서는 .svg로 끝나는 import를 지정하고 모듈의 content를 any로 정의하여 SVG를 위한 새로운 모듈을 선언합니다. 타입을 문자열로 정의하여 URL이라는 것을 더 명확하게 할 수 있습니다. CSS, SCSS, JSON 등을 포함한 다른 애셋에도 동일한 개념이 적용됩니다.

React를 사용한다면:

declare module '*.svg' {
  const value: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
  export default value;
}

TypeScript: Property 'xxx' does not exist on type 'MyComponent'

vue-class-component와 같은 라이브러리를 사용할 때, 별도의 위치에서 추가된 프로퍼티를 사용하는 방법:

:

class MyComponent
{
    private xxx!: number;
}

!를 사용하여 멤버 변수를 추가하면 된다. #NonNullAssertion 항목 참조.

Cannot import exported interface - export not found

다음과 같은 인터페이스가 있을 경우,

export interface B {
    b: number;
}

다음과 같이 import 할 수 있다:

import { B } from './test.model';

이 경우 export not found 이란 경고가 출력될 수 있다. 다음과 같이 해결할 수 있다.

import type { B } from './test.model';

this 가 undefined 일 경우

다음과 같이 생성자에서 this를 사용한 콜백 메서드를 넘기면 해당 콜백에서 thisundefined인 현상이 발생된다.

export default class ApiV2 {
    constructor() {
        this.api = AxiosLib.create();
        this.api.interceptors.response.use(
            this.onResponseFulfilled,
            this.onResponseRejected,
        );
    }
    async onResponseRejected(error) {
        const self = this; // self is 'undefined'
    }
}

이 현상을 해결하기 위해, 생성자에서 Anonymouse Lambda 를 사용하여 메서드를 호출하는 방법이 있다.

export default class ApiV2 {
    constructor() {
        this.api = AxiosLib.create();
        this.api.interceptors.response.use(
            (response) => {
                return response;
            },
            async (error) => {
                return this.onResponseRejected(error);
            },
        );
    }
    onResponseRejected(error) {
        const self = this; // ok !!
    }
}

추정이지만, 람다식을 사용해, this 를 캡쳐하는게 아닐까?

catch block error message

Use compiler option '--downlevelIteration' to allow iterating of iterators

Type 'Set<any>' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.

Entry module "..." is using named and default exports together.

Entry module "src/tailwind/index.ts" is using named and default exports together. Consumers of your bundle will have to use `your-tailwind.default` to access the default export, which may not be what you want. Use `output.exports: "named"` to disable this warning.

집입점 모듈에 명명된 Export (export const Name = ...;) 와 기본 Export (export default ...) 를 함께 사용했다. export default ... 로 압축하면 된다.

See also

Favorite site

Webpack

Tutorials

Guide

Article