Typescript reference

zmin·2022년 8월 21일
0

https://www.typescriptlang.org/docs/handbook/utility-types.html

Utility type

기본으로 제공해주는 타입. 제네릭을 이용하여 주어진 타입을 변화시키는 방향으로 제공

  • Partial : T의 모든 부분집합, 타입들을 선택적으로 사용할 수 있음
  • Required : ? 프로퍼티들도 전부 필수적이게 만드는 타입, -? 연산 해준 것과 같음
  • Readonly : +readonly 해준 것과 같음
  • Record<K, T> : Key에 대해서 각 Type을 매칭 시켜준 것과 같음
    interface CatInfo {
      age: number;
      breed: string;
    }
     
    type CatName = "miffy" | "boris" | "mordred";
     
    const cats: Record<CatName, CatInfo> = {
      miffy: { age: 10, breed: "Persian" },
      boris: { age: 5, breed: "Maine Coon" },
      mordred: { age: 16, breed: "British Shorthair" },
    };
  • Pick<T, K> : 원하는 K들만 골라서 type을 만듦
  • Omit<T, K> : Pick과 반대, 원하는 K들만 제외
    • Pick과 Omit의 경우 string literal 또는 string literal의 union만 사용가능
  • Extract<Type, Union> : Type중에서 Union에 존재하는 애들만 추출
    type Test = Extract<string | number | (() => void), Function>; // type Test = () => void
  • Exclude<UnionType, Member> : 원하는 Member들 Union Type에서 제외
  • NonNullable : T에서 null 과 undefined 제외
  • Parameters: T에 넘겨진 Type이 함수일 때 파라미터들의 타입을 튜플 타입으로 표현
    function fn(arg: { a: number; b: string }): void;
    type Test = Parameters<typeof fn>; // type Test = [arg: { a: number; b: string; }]
  • ThisParameterType : this 의 타입을 정의해준 경우 해당 타입을 가져옴, 만약 정의하지 않았다면 unknown
  • ConstructorParameters : 생성자 함수의 파라미터 타입을 튜플 타입으로 표현
  • ReturnType : 함수의 리턴 타입을 추출함
  • InstanceType : 생성자 함수로 만들어지는 인스턴스의 타입을 반환 → 일반적으로는 Class이름일 것
  • OmitThisParameter : this를 제거한다.
  • ThisType : method의 type을 명시할 때 해당 유틸리티 타입을 통해 method의 thisType을 명시해줄 수 있다.

decorators

https://www.typescriptlang.org/docs/handbook/decorators.html

실험적 기능이라고 안내


tsconfig.jsoncompilerOptions.experimentalDecorators 를 설정해줘야 제대로 작동

{
    "compilerOptions": {
				...
        "experimentalDecorators": true,
				...
    }
}

데코레이터는 @<expression> 의 형태로 작성하며 기본적으로 그 다음에 오는 클래스, 프로퍼티, 메서드, 파라미터를 전달받는다.

  • 클래스 데코레이터 : 생성자 함수가 유일한 인수
    function sealed(constructor: Function) {
      Object.seal(constructor);
      Object.seal(constructor.prototype);
    	// 위와 같이 constructor에 대한 작업을 진행할 수도 있고 아래처럼 새로운 생성자를 정의할 수도 있다
    	return class extends constructor {
    		console.log('new!');
    	}
    }
    
    @sealed
    class BugReport {
      type = "report";
      title: string;
     
      constructor(t: string) {
        this.title = t;
      }
    }
  • 메소드 데코레이터 : function decr (target, property, descriptor) {}
    • target : 데코레이터를 적용할 대상
    • property : 그 대상의 프로퍼티
    • descriptor : 그 프로퍼티의 세부적인 정보, 프로퍼티 어트리뷰트에 대한 정보
      • 문서의 접근자 데코레이터, 프로퍼티 데코레이터들 모두 메소드 데코레이터를 이용하여 새로운 함수/값을 반환하는 형태로 사용된다. → 반환값: 로직이 추가된 메소드 / 바꿀 값
  • 파라미터 데코레이터 : function decr (target, property, parameterIndex) {}
    • target : 대상 객체, 생성자 함수 또는 클래스의 프로토타입..
    • property : 그 대상의 멤버 이름
    • parameterIndex : 그 매개변수의 index
      • 참고로 생성자 또는 메서드 선언에 적용 (일반 함수 X)
      • 반환값 무시

Declaration merging

  • interface
    • 동일한 이름의 interface 선언은 알아서 합쳐진다.
    • 함수가 아닌 멤버는 다른타입으로 중복되면 안되지만 파라미터가 다른 동일 이름 함수의 경우는 오버로드 된다.
      • 이때 뒤에 선언된 인터페이스의 함수가 더 우선순위를 가지게 된다.
      • 하지만 파라미터 타입이 리터럴이라면 다른 타입들보다 우선순위가 됨
        interface Document {
          createElement(tagName: any): Element;
        }
        interface Document {
          createElement(tagName: "div"): HTMLDivElement;
          createElement(tagName: "span"): HTMLSpanElement;
        }
        interface Document {
          createElement(tagName: string): HTMLElement;
          createElement(tagName: "canvas"): HTMLCanvasElement;
        }
        //////----------------------------------/////
        
        interface Document {
        	// 아래처럼 리터럴 타입이 우선순위
          createElement(tagName: "canvas"): HTMLCanvasElement;
          createElement(tagName: "div"): HTMLDivElement;
          createElement(tagName: "span"): HTMLSpanElement;
          createElement(tagName: string): HTMLElement;
          createElement(tagName: any): Element;
        }
  • namespace
    • interface와 비슷하지만 export 되지 않는 멤버는 다른 동일이름의 namespace에서 참조할 수 없다 → 해당 네임스페이스 스코프가 따로 생기는 것이 아님.
    • namespace와 클래스, 함수, enum과의 병합도 namespace끼리 병합할 때와 동일하게 처리.
      • 이때 namespace에 정의된 값들은 static 멤버로 여겨진다

      • 하지만 namespace에 정의된 멤버를 클래스, 함수, enum에서 바라보고 싶다면 export 필수

        function buildLabel(name: string): string {
            return buildLabel.prefix + name + buildLabel.suffix;
        }
        
        namespace buildLabel {
            export let suffix = "!!!";
            export let prefix = "Hello, ";
        }
        
        console.log(buildLabel("Sam")); // Hello, Sam!!!

이렇게 다른 위치에서 선언된 모듈을 import 하여 사용하면서 해당 모듈 자체에 대한 augmentation이 필요한 경우가 있을 수 있다. 다른 처리 없이 바로 하게되면 해당 모듈에서 그 부분을 정의한 적이 없기 때문에 정보를 알 수 없다. 작동이 안되는 것은 아니지만 타입 검사와 자동완성이 안되는 것.. 공식 문서의 예제가 잘 나와있어 첨부

// observable.ts
export class Observable<T> {
  // ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
  interface Observable<T> {
    map<U>(f: (x: T) => U): Observable<U>;
  }
}
Observable.prototype.map = function (f) {
  // ... another exercise for the reader
};
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());

그래서 위와 같이 map.ts 라는 새로운 파일에 declare로 해당 모듈에 보강할 내용에 대한 정보를 적어주고 사용할 곳에서 map.ts 를 같이 호출하게 되면 Observable 모듈의 class는 ./map속의 declare된 interface와 merge 되게 된다. 단 이때 export는 named export를 사용해야한다.

declare global {} 을 이용하면 전역 스코프에 대해 선언을 추가할 수 있다.

declare global {
  interface Array<T> {
    someMethod(): <T>;
  }
}
Array.prototype.someMethod = function () {
  // ...
};

Enum

다른 언어에서 쓰는 그 enum, 이름 붙여진 상수들의 집합

실제 런타임에 해당 member를 프로퍼티로 가지는 객체로 존재함

enum Direction { Up = 3421, Right, Down, Left} // 3421 3422 3423 3424

이름 Direction이 해당 enum의 타입을 의미

function test(param: Direction): void { ... };
test(Direction.Right);

enum에 할당할 수 있는 값들은 다음과 같다.

  • 숫자 (명시하지 않을 경우 0부터 시작하거나 명시한 값에서 1씩 증가)
  • 문자열 리터럴 (자동증가X 모두 지정해줘야함)
  • 숫자와 문자열을 섞어서 enum을 만들 수 있긴한데 하지 않는 것을 추천

enum에는 지정된 값 말고도 계산되어야하는 값도 올 수 있다. 그러나 computed member는 constant member를 모두 선언해준 뒤에 해야한다.

constant member로 취급되는 것은 다음과 같다.

  • 문자열 리터럴 & 숫자 리터럴
  • 이전에 정의된 enum이나 같은 enum에서 이미 정의된 constant enum member
  • 방금 말한 constant enum member를 이용한 단항/이중 연산자 표현식 → 단, 결과가 NaN, Infinity인 경우는 컴파일오류

모든 member 가 constant일 때 union type처럼 생각할 수도 있다. 물론 정말 유니온 타입처럼 member 값만 넘겨줄 수는 없고 이를 이용하려면 keyof typeof 구문을 사용해야한다

enum E {
    Foo,
    Bar,
}

// E 가 constant member들로만 이루어진 enum이기 때문에 타입으로 사용 가능
function f1(x: E) { } // f1(E.Foo); 
function f2(x: keyof typeof E) {} // f2('Foo')

constant member으로 이루어진 enum 키워드 앞에 const 를 붙여서 변화할 수 없는 enum을 만들 수도 있다. const를 사용하지 않는 일반 열거형과 다른 점은 컴파일 후에 enum의 흔적이 사라진다는 것. 해당 member들이 정의했던 constant 값으로 변경된다.

Iteratable

Iterable 값들을 위한 Iterable interface를 제공

// T타입으로 이루어진 순환가능한 객체(이터러블)를 배열로 변환해서 반환
// 이터러블에 스프레드 연산자를 사용 가능하기 때문에 아래와 같은 선언을 사용 가능#
function toArray<T>(xs: Iterable<T>): T[] {
  return [...xs]
}

JSX

typescript는 jsx를 javascript로 변환하는 것도 지원한다. .tsx 확장자를 사용하고 config에서 jsx에 대한 옵션을 지정해주면 된다.

이를 사용하게 되면 원래 <type> 으로 설정하던 type assertion을 as type 으로 사용해야한다.

당연한 얘기지만 jsx → js 컴파일할 때의 규칙과 tsx → js의 규칙이 동일하다. 컴포넌트 생성시 사용하는 내장 함수가 다르기 때문에 사용자가 만든 element인 경우 그 이름이 대문자로 시작해야한다는 것

만약 소문자로 시작하는 내장 요소를 직접 지정하고 싶다면 JSX.IntrinsicElements 를 아래 declare를 이용하여 지정할 수 있다.

declare namespace JSX {
    interface IntrinsicElements {
        my: any,
				el: {
					// 어트리뷰트 값도 지정해줄 수 있음
					// 옵셔널을 사용하지 않으면 필수 어트리뷰트가 됨
					attr?: boolean,
				}				
    }
}

<my />;

Value-based elements를 정의할 때는 함수형 컴포넌트 해석 → 클래스형 컴포넌트 해석 → 다 안되면 에러 의 과정을 거친다.

  • 함수형 컴포넌트는 파라미터가 props 객체이며 JSX.Element에 할당 가능한 값(해당 타입을 포함해야함)을 반환하는 함수로 정의된다.
  • 클래스형 컴포넌트는 타입을 정의하게 되는데 요소의 클래스 타입은 해당 컴포넌트의 이름(대문자로 시작하는 그거)로 가지게 되고 요소의 인스턴스 타입은 이런 클래스 타입의 생성자/호출 시그니처(new 키워드 사용X)가 반환하는 값에 의해 결정된다. 요소 인스턴스 타입은 JSX.ElementClass에 할당 가능해야한다.

위 예시에서 어트리뷰트 값을 지정해주는 것처럼 JSX.ElementChildrenAttribute 에 children 단일 프로퍼티를 명시하여 타입을 지정해줄 수 있지만 만약 React를 사용한다면 기본으로 제공해주는 children과 props 관련 타입들을 활용하는 것이 더 좋아보인다. 괜히 만들어두지 않았을 것…

mixin

제네릭을 활용하여 믹스인 함수를 정의하여 사용할 수도 잇음. 공식 문서에 나와있는 것은 클래스에 특정 프로퍼티를 추가해주는 예제

또한 클래스의 경우 인터페이스와 다르게 단일 상속만 가능하기 때문에 클래스를 정의하고 이후 mixin class를 이용하여 다중 상속 interface로 merging하면 다중상속된 클래스를 구현할 수 있다.

정적 속성을 사용하는 경우에는 아예 새로운 클래스를 반환하는 형태로 작성해야 믹스인을 이용하여 정의한 클래스들이 하나의 정적 속성을 공유하지 않는다(싱글톤 패턴이기 때문에 발생할 수 있는 문제)

Module

esm을 node js에서도 사용할 수 있게 되면서 package.json을 정의할 때 module: 로 모듈 포맷을 명시해줬었는데 typescript를 사용하게되면 이 부분을 type: 으로 사용한다. 이에 따라 타입스크립트를 자바스크립트로 컴파일 할 때 모듈 포맷을 변경할 것인지 유지할 것인지 결정한다.

명시적인 것을 위해 .mts 확장자 지원

https://www.typescriptlang.org/docs/handbook/modules.html

esm처럼 import/export 구문을 사용가능하며 AMD나 Common JS의 workflow를 구현하기 위해 export = 문법도 지원한다. 이렇게 export하게되면 사용할 때 꼭 require() 문을 이용해야한다.

→ 작성된 것들이 컴파일될 때 지정된 모듈의 포맷으로 바뀜

ambient 모듈

타입스크립트로 작성되지 않은 모듈들도 분명히 존재.

그렇다면 사용하기 전에 해당 모듈들에 대해 타입을 정의해줘야함. .d.ts 이 이런 정의를 모아둔 파일. 일반적모든 모듈마다 하기보단 패키지당 하나씩 하는 경우가 많음 편리하니까..

declare module 'my-module' {
	export interface {
		name: string,
		version: string,
	}
	export function customPrint(text: string): void;
}

만약 다른 파일로 정의해줬다면 해당 import 앞쪽에 /// <reference path="선언파일.d.ts"/> 을 적어줘서 해당 선언을 사용할 수 있다.

선언을 따로 작성하지 않고 그냥 export 하게된다면(shorthand ambient modules) any 타입을 가짐

모듈 구조화에 대한 가이드

  • 너무 중첩되지 않도록 주의(namespace라든지 객체안의 객체안의 객체안의…)
  • 하나의 클래스나 함수만 export 하는 경우는 default export를 이용
  • 기존 모듈을 확장해야할 때 해당 파일을 직접 수정하지말고 import/export를 이용해서 새로운 확장된 모듈을 만들기 → 기존의 것도 사용할 수 있도록 유지하라는 것
  • 모듈에는 자체 스코프가 적용되기 때문에 네임스페이스를 사용할 필요가 거의 없다. 사실 없다. 하나의 모듈 안에서 이름이 충돌할 일이 생길리가 없음. 생기면 안됨 잘못만든 거임

모듈 import 시에 경로를 나타내는 방식에 따라 어떤 모듈인지 분석하는 방식 → node의 패키지/모듈 분석 방식을 따름

tsconfig.json에 baseUrl을 설정할 수 있으며 해당 위치에 존재하지 않을 수도 있는 모듈이지만 런타임에 맵핑되는 경로가 있다면 paths에 작성해줄 수 있다. 아래는 jquery의 예시다.

"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
  "jquery": ["node_modules/jquery/dist/jquery"] // baseUrl에 상대적
}

또한 어떤 모듈을 exclude 하겠다고 tsconfig.json에 명시했더라도 컴파일되는 모듈에 import 되어있으면 exclude는 무시된다. → 컴파일 된다는 말

namespace

위에서 계속 말하긴 했지만 그래도 정리하면..

namespace 스코프를 만들어준다고 생각할 수 있다. 또한 공식문서의 note에서 말하듯이 파일 내부에 포함되어있는 내부 모듈을 namespace라고 부른다.

그렇기 때문에 위에서 모듈 설명했던 것처럼 여러 파일에 걸쳐 확장할 수 있다. 내부 모듈이니까

외부 모듈의 전체를 내부 모듈로 감싸는 것은 정말 불필요한 행위. 해당 외부 모듈에서 내부 모듈로 사용해야할 이유가 없는데 전체 코드를 네임스페이스로 감싸지 말자.

Symbol

https://www.typescriptlang.org/docs/handbook/symbols.html es6에 추가된 심볼 사용법은 javascript와 동일

unique Symbol 을 이용하면 type annotation으로 symbol을 지정해줄 수 있으며 해당 식별자(변수, 함수 둘다 됨)은 const이거나 readonly static이어야 한다.

const sym: unique symbol = Symbol();

Triple-Slash Directives

세 개의 슬래시 /// 를 사용하고 xml태그를 사용한 주석 → 컴파일할 때 지시어로 사용된다.
하지만 이는 파일의 상단에 있을 때만 적용되고 중간에 적혀있는 경우 그냥 주석으로 처리

  • /// <reference path="..." />
    • 기본으로 사용되는 지시어로 컴파일시 파일 의존성을 추가할 때 사용.
  • /// <reference types="..." />
    • 위 지시어와 동일하지만 패키지 단위의 의존성을 추가할 때 사용
  • /// <reference lib="..." />
    • 기존 내장 라이브러리를 사용할 때 컴파일시 포함하라고 지시
    • 일반적으로 내장 JS 타입에 의존하는 경우 이 지시어가 필요하다.
  • /// <reference no-default-lib="true"/>
    • 해당 파일이 기본 라이브러리라고 표시하여 컴파일하지 않도록 지시
  • /// <amd-module name="..."/>
    • 기본적으로 익명 모듈을 생성하는 AMD를 위해 이름 명시

Type Compatibility

어떤 타입에 값을 할당할 때 해당 값이 그 타입과 완전히 일치하지 않아도 된다.

해당 타입이 포함된다면 할당 가능

type Person = { name: string };

const me: Person = { name: 'kim', age: 15 }; //  가능

함수

함수의 경우에는 파라미터와 반환값을 고려해야하는데 포함관계가 약간 반대다

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

x의 파라미터가 y파라미터들에 포함되어 있으므로 y = x가능.
값이 전달되지 않을 수도 있는 것이 자바스크립트에선 자연스럽기 때문
let x = () => ({name: "Alice"});
let y = () => ({name: "Alice", location: "Seattle"});

y의 반환값이 x의 반환값을 포함하고 있다. 최소한 반환해야할 것은 반환하고 추가로 더 반환하는 것
x = y 가능

이런점 때문에 파라미터로 타입을 지정해줘도 해당 타입을 포함하는 타입이라면 파라미터로 전달이 가능하다
→ 사실 이런 현상을 막기위해 타입을 지정해주는 것이었는데 약간은 타입스크립트의 목적과 벗어나는 시나리오
→ 그치만 막지 않는 이유는 자바스크립트의 다양한 패턴이 이를 통해 이루어지기 때문

클래스

는 타입 비교를 할 때 정적 멤버에 대한 타입 비교는 실행하지 않고 인스턴스 멤버만 비교

class Animal {
  feet: number;
  constructor(name: string, numFeet: number) {}
}
class Size {
  feet: number;
  constructor(numFeet: number) {}   // 생성자 역시 고려대상이 아님
}
let a: Animal;
let s: Size;
a = s; // 가능
s = a; // 가능

protectedprivate 멤버는 비교에 사용

제네릭

의 경우는 타입인수를 적용한 구조가 같은지 아닌지를 따져서 호환성을 판단한다.

만약 타입인수가 정해지지 않은 제네릭 타입의 경우 해당 타입 인수를 any 로 여기고 호환성을 판단함

추가적인 타입 할당 가능성 정의

https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability

  • 모든 것은 any와 unknown에 넣을 수 있다.
    • 그러나 unknown 타입은 다른 것에 넣을 수 없음

      any, unknown = 모든 타입
      오직 unknown 또는 any = unknown
  • 모든 것은 unknown에 넣을 수 있고, never은 모든 것에 넣을 수 있다.
    모든 것은 never에 넣을 수 없고, unknown은 모든 것(any 제외)에 넣을 수 없다

type Inference

타입을 따로 지정해주지 않으면 기본적으로 알아서 타입을 추론한다.

배열에 대한 타입을 추론할 때는 최대한 해당 값들을 포괄할 수 있는 타입으로 추론을 하게 되는데
아래와 같은 경우 각 인스턴스들이 사실 Animal을 상속받았지만 주어진 정보만으로 얻은 타입들 중에는 다른 것들을 포함할 수 있는 타입이 없어 주석처럼 타입을 추론한다.

let zoo = [new Rhino(), new Elephant(), new Snake()];
// let zoo: (Rhino | Elephant | Snake)[]
// 원래는 let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]; 이 가능

variable declaration

https://www.typescriptlang.org/docs/handbook/variable-declarations.html

자바스크립트와 동일

profile
308 Permanent Redirect

0개의 댓글