TypeScript : Everyday Types

김가영·2021년 6월 21일
1

JavaScript

목록 보기
7/9
post-thumbnail

타입스크립트 documentation의 Handbook-Everyday Types
Enum
를 참고하였다.

  • 자바스크립트에 존재하는 여러 타입들과, 타입스크립트로 이를 이용할 수 있는 방법에 대해 공부해보자.

Arrays

number[] 또는 Array<number>를 통해 숫자로 구성된 배열을 선언할 수 있다.

[number] 는 number 하나를 원소로 갖는 tuple을 의미한다.

any

특정 value를 지정하지 않기 위하여 이용된다.
any로 지정된 변수의 어떤 property에도 접근이 가능하며(역시 any type이 된다), 함수처럼 호출도 가능하고, 아무 타입에 할당하여도 compile error가 발생하지 않는다.

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed 
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

타입스크립트는 타입 추론이 불가할 때 any 타입을 이용하게 되는데, noImplicitAny 옵션을 통해 사용자가 직접 명시하지 않은 any에 대해 ERROR를 발생시킬 수 있다.

Functions

Parameter Type Annotations

// Parameter type annotation
function greet(name: string) {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

parameter의 type annotation을 추가하지 않으면, argument의 개수가 올바른지만 체크한다.

Return Type Annotations

function getFavoriteNumber(): number {
  return 26;
}

보통 타입스크립트가 자동으로 리턴타입을 추론하기 때문에 명시적으로 추가해줄 필요는 없다.

Anonymous Functions

타입스크립트가 파라미터의 타입을 추론할 수 있으면, 파라미터 타입이 자동으로 설정된다.

// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];

// Contextual typing for function
names.forEach(function (s) {
  console.log(s.toUppercase());
--ERROR : Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

// Contextual typing also applies to arrow functions
names.forEach((s) => {
  console.log(s.toUppercase());
--ERROR: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

Object Types

// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
  • pt: {x: number; y: number} 와 같은 방식으로 xy, 두개의 property를 갖는 object를 선언할 수 있다.
    각 property를 구분하기 위해서는 ; 또는 ,를 이용할 수 있다.

  • property의 타입을 지정하지 않을 수도 있다. 이 경우 property는 any 타입이 된다.

  • Optional Properties
    object의 property가 optional함을 의미하기 위해서는 이름에 ?를 추가해주자.

function printName(obj: { first: string; last?: string }) {
  // ...
}
  • 위 함수 내에서 optional property인 last에 접근하면 (possibly) undefined가 된다. → 접근/이용 전에 undefined 여부를 체크해주도록 하자.

Union Types

여러개의 type들의 합집합. 그 중 하나의 type을 갖게된다.

  • union type 변수를 이용할 때에는 항상 모든 멤버 타입에 유효하게 동작해야 한다.
    예를 들어, string | number 타입인 경우 해당 변수는 string에만 적용 가능한 toUpperCase() 함수는 사용불가하다.

  • 대신 typeof (또는 Array.isArray())를 이용하자.

function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

Type Aliases

Type에 object나 Union type을 바로 넣는 대신 새로운 타입을 생성하여 이름을 부여할 수도 있다.

type Point = {
  x: number;
  y: number;
};

// Exactly the same as the earlier example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

type ID = number | string

Type Alias는 타입에 이름(별칭)을 부여하는 것이지, 새로운 타입을 생성하는 것이 아니다.
type newString = string; 일때,
newString으로 타입을 선언하는 것과 string으로 선언하는 것은 완전히 동일하다.

Interfaces

object type에 이름(별칭)을 부여하는 또다른 방법

interface Point {
  x: number;
  y: number;
}

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

타입스크립트는 항상 값의 shape (또는 structure)를 통해 type을 판단한다는 것을 기억하자. type을 이용하든, interface를 이용하든, shape 가 동일하다면 타입스크립트는 이들을 같은 타입으로 판단한다.

Differences Between Type Alias and Interfaces
interface 에는 새로운 property를 추가할 수 있고, type는 불가하다.

interface Person {
  name: string
}
interface Person {
  age: number
}
const getPersonInformation = (person: Person) => {
  console.log(person.name);
  console.log(person.age)
}
const person : Person = { name: 'Jujube', age: 25 };
getPersonInformation(person);

위를 인터페이스 병합Merging 이라고 부른다. Merging은 두 선언의 멤버를 같은 이름을 갖는 하나의 interface로 결합시키는 것이다.

type Student = {
  name: string
}
type Student = {
  age: number
}
// ERROR : Duplicate identifier 'Student'.

Extending in type and interface

interface Animal {
  name: string
}
interface Bear extends Animal {
  honey: boolean
}
const bear = getBear() 
bear.name
bear.honey
type Animal = {
  name: string
}
type Bear = Animal & { 
  honey: Boolean 
}
const bear = getBear();
bear.name;
bear.honey;

Type Assertions

타입을 좀 더 구체적으로 명시하는 방법

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

그냥 document.getElementById를 쓰면 Typescript는 HTML element중 하나가 반환된다는 것만 알 수 있지만, 이를 좀 더 구체적으로 명시해줄 수 있다.

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

위의 방식으로 <>를 이용할 수도 있다.

  • Type assertion은 좀 더 구체적으로 또는 덜 구체적으로 타입을 변환할 수 있다. 이러한 규칙 때문에 실제로는 가능한 Type assertion이 금지되기도 한다.
    → 이때는 anyunknown으로 한 번 변환 한 후, 다시 원하는 타입으로 변환하도록 한다.
const a = (expr as any) as T;

Literal Types

그냥 일반적인 string 또는 number 타입 대신, 특정 string이나 number 만을 가질 수 있는 타입을 말한다.

  • 예를 들어, const를 이용하여 변수를 선언할 때 이러한 리터럴 타입을 이용하게 된다.

  • 보통 Union과 함께 이용된다.

  • boolean literals
    type true 또는 type false
    type booleantrue | false 유니언 타입의 별칭이다.

Literal Inferance

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

위 코드에는

Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

라는 에러가 뜬다.

req.method가 string 타입이기 때문.

  • 첫번째 해결 방법은 둘 중 한 위치에 type assertion을 추가하는 것이다.
const req = { url: "https://example.com", method: "GET" as "GET" };
또는 
handleRequest(req.url, req.method as "GET");
  • 또는 as const를 이용하여 객체 전체를 literal 타입으로 변환하는 것이다. 이는 객체의 모든 property에 리터럴 타입의 값이 할당됨을 보장한다.
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

null undefined

nullundefined는 존재하지 않거나, 초기화되지 않은 값에 이용된다.

strictNullChecks

  • 설정되지 않았을 때 : 어떤 값이 null 또는 undefined일 수 있더라도 접근이 가능하다.
  • 설정되었을 때 : 접근이 불가하다. undefined 또는 null일 경우를 따로 처리해주어야 한다.

Non-null Assertion Operator (Postfix !)

  • 해당값이 null이나 undefined가 절대로 아님을 프로그래머가 단언하는 것. 정말로 아닌 경우에만 이용하자.

Enums (열거형)

  • typeScript가 제공하는, type-level이 아니라 언어, 런타입 수준에 추가되는 기능.
  • 이름이 있는 named constants 들을 정의할 수 있게 한다.
  • numeric and string-based enum 두가지를 모두 제공한다.

Enums

  • Numeric enums
enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}

enum 키워드를 사용하여 정의한다. Up1로 정의되었고, 뒤의 멤버들의 값은 auto-increment한다!! 신기해!!!!!!
모두 초기화하지 않으면 0부터 시작하여 auto-increment한다. 이용할 때에는 Direction.Up 처럼 이용이 가능하다.

  • member 값에 추가적인 계산이 필요할 경우 auto-increment가 작용하지 않는다. → 초기화되지 않은 멤버가 계산이 필요한 멤버 뒤에 올 수 없다.
enum E {
  A = getSomeValue(),
  B,
}
-- ERROR : Enum member must have initializer.
  • String Enum
    각 멤버들은 string literal 또는 또다른 String Enum의 member로 초기화돼야 한다. (auto-increment 기능 없음)
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

bigint, symbol

자주 안쓰이는 primitive~~~

bigint : number로 표현되지 않는 아주아주 큰 정수
symbol : 고유한 참조값을 생성한다. Symbol()를 통해 사용된다. Symbol() 의 argument로 같은 값이 들어가더라도 리턴값은 항상 다른 값 이 된다.

const firstName = Symbol("name");
const secondName = Symbol("name");

if (firstName === secondName) {
This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  // Can't ever happen
}
profile
개발블로그

0개의 댓글