티스토리 뷰

Basic Rules

  1. 최대한 타입은 좁은 범위로 작성
  2. 타입 추론을 적극 활용할 것
  3. 타입스크립트에서 타입을 빼면 올바른 자바스크립트 코드여야 한다. 
  4. 타입 명시 자리를 잘 알고 있어야 한다.
  5. 원시타입 명시할 때, 대문자가 아니라 소문자로 써야한다. string과 String은 다르다.
💛콜론 뒤에 타입, 타입 별칭(에일리어스), body가 없는 function, 제네릭, 인터페이스, as, enum, declare, readonly 등은 TS코드에서 JS코드로 변환 시 사라진다.

Implicit Types vs Explicit Types (암시 타입 vs  명시 타입)

  • 암시 타입(Implicit Types)은 타입스크립트에게 해당 데이터의 타입을 추론할 수 있도록 타입을 명시하지 않는 방법을 말한다. (TS가 알아서 판단) -> 기존 JS와 비슷하다. 코드가 간략해지는 장점이 있다. 
  • 명시 타입(Explicit Types)은 내가 직접 명시적으로 데이터의 타입을 지정해주는 방법을 말한다. -> 좋지만 명시타입은 최소로 쓰는 걸 권장한다고 함 
let a = "apple"; // 암시타입
let b : boolean = false // 명시타입
let c : number[] = []; //숫자 요소 배열 타입, string[] 등 배열요소의 타입도 지정할 수 있음
c.push(1);

Types of TS 

🤸‍♀️Object(객체)의 optional parameter(선택적 변수)

 

만일 name 은 전부 가지고 있는 프로퍼티지만 age는 선택적으로 몇몇만 가지고 있는 객체가 있다면

optional parameter(선택적 변수)를 지정하면 된다.  age?:number 에서 ?: 부분에 해당한다. 

(* ?. JS의 optional chaining 과 비슷하게 생겼다. 둘 다 똑같이 값이 없다면 undefined 를 반환한다 )

const player : {
	name:string,
    age?:number
} = {
	name:"cong",

}
//만약 아래의 조건식에서 player.age < 10만 쓰게되면 age가 undefined 인 경우도 있기 때문에
//아래와 같이 써야 빨간 밑줄이 사라진다. 
if(player.age && player.age <10){

}

 

 

🤸‍♀️Alias(별칭, 에일리어스) 타입 

-에일리어스는 객체(Object)에만 유효하지 않는다. 어느 타입에나 적용할 수 있다. 

[문법  type + 대문자 시작 변수명

별칭 타입을 쓰면 여러 줄의 코드를 계속 쓰지 않고 타입을 재활용할 수 있다. 

type Name = string; //별칭 타입은 객체가 아니더라도 이렇게 또 재활용 할 수 있게 분리할 수 있지만
// 누가봐도 큰 의미가 없고 과하게 사용하면 좋지 않기 때문에 필요에 따라서 써야한다. 

type Player = {
	name: Name, //원래는 name:string, 이었음
    age?: number, 
}

const cong : Player = {
	name: "cong"
}
const lynn : Player = {
	name: "lynn",
    age:12
}

 

위에서 const나 let 같은 변수에 type을 할당하는 방법을 알아봤습니다!

const 변수명 : 타입

그렇다면 함수는 어떻게 할까요?

 


🤸‍♀️함수의 return 값의 타입

function playerMaker(name:string){
	return {
    	name:name // JS문법에 따라 키밸류 이름이 같으면 name 하나만 써줘도 된다. 
    }
}

const nico = playerMaker("nico")
nico.age = 12; //이렇게 되면 에러가 난다.

함수의 리턴값은 위에서 했던 문법처럼 function 의 param이 들어가있는 () 소괄호 뒤에 : 타입 을 통해 지정해줄 수 있다!

//위에서 쓴 별칭(에일리어스)의 예시를 가져와서
type Player = {
	name:string,
    age?: number, 
}

function playerMaker(name:string) : Player {
	return {
    	name
    }
}

const nico = playerMaker("nico");
nico.age = 12; // 위에서 playMaker 함수의 리턴 타입을 Player로 지정해줬기 때문에 이제 에러가 나지 않는다.

위에는 가장 베이직한 함수 선언문에 대해서 알아봤습니다.

그렇다면 화살표 함수의 경우는 어떻게 될까요? 아래와 같이 쓰면 됩니다. 

const playerMaker = (name:string) : Player => ({name});

 

🤸‍♀️readonly 속성 부여하기 

  • readonly 키워드로 데이터를 변동할 수 없도록 보호할 수 있다. ->  typescript 보호장치로 불변성을 얻을 수 있다.
  • JS코드로 컴파일되면 물론 객체가 변경 가능하다. 하지만 TS에서 만큼은 JS으로 변환되기 전까지 보호받을 수 있다!
 const names: readonly string[] = ["1", "2"];

🤸‍♀️Tuple(튜플)  for 배열

  • 배열의 요소(item)가 정해진 개수와 순서에 따라 배열로 선언된다 -> 이 조건에 맞춰 배열을 생성
  • readonly 와 같이 쓸 수 있다. (튜플 앞에 readonly 키워드)
const player: [string, number, boolean] = ["안녕", 22, true];
  const cat: readonly [string, number, boolean] = ["나비", 2, false];
  player[0] = "잘 있어!";
  // cat[0] = "멍멍이"; //readonly속성때문에 타입이 같아도 오류 발생, 재할당X

🤸‍♀️ undefined, null, any

  • any: 타입 지정X, 아무 타입이든 ok 라는 의미  -> NEW!! TS의 보호장치로부터 벗어나고 싶을 때 쓴다. 최대한 안쓰는 것을 권장!
  • undefined: 선언X 할당X
  • null: 선언O 할당X
  let a: undefined = undefined;
  let b: null = null;

  //TS
  const func1 = (
    paramName: string
  ): {
    name: string;
    age?: number;
  } => {
    return { name: paramName };
  };
  func1("유저 이름");

  //any를 통해서 TS 탈출!, param에 직접 타입 명시를 해주지 않아도 any가 임의적으로 할당된다.
  //any 키워드 빼고 JS코드와 다를게 없다.
  function sayHello(userName: any) {
    console.log(userName);
  }
  sayHello("도라미");

 

참고 자료: 노마드 코더 타입스크립트 강의


2022 12 17 추가

💛TIP TS는 JS로 변환시키는 것을 명심하면 해당 타입을 지웠을 때, 말이되는 JS가 되면 타입명시자리이다!

고정된 원시값 명시

언제 사용? 항상 값이 true인 상수가 있다면 굳이 boolean일 필요가 없다. 타입은 항상 더 정확할수록 좋기 때문에

const로 선언된 원시값은 명시적으로 써줄 필요가 없다. 타입 추론을 적극 활용하면 된다.
💛타입스크립트가 추론한 타입이 내가 원하는  타입이 아닐 경우에 코드를 명시적으로 수정해주면 된다.
const g: 5 = 5; // 타입 자리에 아예 고정된 원시값을 넣을 수도 있다.e.g. boolean 대신 true

화살표 함수 타입 명시 

 const add: (x: number, y: number) => number = (x, y) => x + y;

type alias(타입 에일리어스, type 키워드)

- type으로 타입을 선언하는 방식, 타입을 별칭으로 빼주는 것 //위의 코드는 인라인으로 썼다면 아래코드는 분리해준 것이라고 생각하면 편할듯, type뒤는 대문자

객체는 타입 에일리어스를 통해 만들거나 인터페이스를 통해 만든다. 
차이점: 간단하게 하고싶다면 type을 쓰기, 객체지향 프로그래밍을 하고싶다 interface 사용
 type Add = (x: number, y: number) => number;
 const add: Add = (x, y) => x + y;
type A = { a: string };
  const a: A = { a: "hello" };

  interface B {
    a: string;
  }
  const b: B = { a: "hello" };

inteferface

인터페이스를 통해 함수를 만드는 법도 있다. 

하지만 주로 타입 에일리어스를 통해 만들거나 함수에 직접 써주기 때문에 인터페이스를 쓰는 경우는 흔치않다고 한다.

interface Add {
    (x: number, y: number): number;
  }
  const add: Add = (x, y) => x + y;

객체 타입 명시

 const obj: { lat: number, lon: number } = { lat: 37.5, lon: 127.5 };

배열 타입 명시1

  const arr: string[] = ["123", "456"];
  const arr2: number[] = [123, 456];
  //다른 배열 표기법(꺽쇄 -> 제네릭)
  const arr3: Array<number> = [123, 456];

배열 타입 명시2 (Tuple, 튜플)

-길이가 고정된 배열을 말한다. 기존 배열은 자리를 마구 늘릴 수 있음(고정길이x)

요소들의 순서와 요소의 타입도 고정

타입 추론을 적극 활용 

🟡위에서 타입들을 명시적으로 써주었는데 사실상 TS는 타입 추론이 가능.
우리는 TS가 타입을 잘못추론하거나 원하는 값으로 조작할 때, 타입을 명시해주면 된다.
명시된 타입을 지웠을 때, 타입추론이 되지않고 any로 뜰 때에는 명시적으로 써준다.
function add(x: number, y: number){
    return x + y;
  }
  const result = add(1, 2); //리턴타입을 명시하지 않아도 result의 타입추론을 보면 number로 뜬다.

 

💥함수 타입부분과 함수 선언부분이 따로 있는 경우도 있다

정확히는 body가 없는 function -> 아래 예시에서 맨 첫번째줄 ts코드에서 js로 변환했을 때 사라진다.

 

function add(x: number, y: number): number; //타입 명시
  function add(x, y) {
    return x + y;
  } // 선언부

-> 물론 vscode에서는 빨간 밑줄이 생기지만 이렇게도 쓸 수 있다. 

as 키워드를 통한 강제 형변환

let aa = 123;
aa = "hello" as unknown as number; //원래라면 aa는 number가 들어가야 한다는 에러메세지

never 타입(해당 내용에 대한 원문 참고를 추천합니다. -> never에 대하여 by Toast UI)

타입은 가능한 값의 집합이다. 예를 들어, string 타입은 무한히 가능한 모든 문자열의 집합을 의미한다.

타입스크립트에서 `never`타입은 값의 공집합이다. 사실 또 다른 인기 자바스크립트 타입시스템인 Flow에서 never 타입은 empty타입과 같다.

집합에 어떤 값도 없기 때문에, `never`타입은 `any`타입의 값을 포함해 어떤 값도 가질 수 없다. 그렇기 때문에 때때로 점유할 수 없는 또는 바닥 타입이라고 불린다.

declare const any: any
const never: never = any // ❌ 'any' 타입은 'never'타입에 할당할 수 없다.

1-1. never 타입이 필요한 이유?

숫자 체계에 아무것도 없는 양을 나타내는 0처럼 문자 체계에도 불가능을 나타내는 타입이 필요하다.

 

TS에서 `불가능`이란 아래와 같은 방법으로 나타낸다.

  • 값을 포함할 수 없는 빈 타입 
    • 제네릭과 함수에서 허용되지 않는 매개변수
    • 호환되지 않는 타입들의 교차 타입
    • 빈 합칩합(무의 합집합)
  • 실행이 끝날 때 호출자에게 제어를 반환하지 않는 함수의 반환 타입
    • e.g)Node의 process.exit
    • void는 호출자에게 함수가 유용한 것을 반환하지 않는다는 것이므로 혼동하지 않도록 주의
  • 절대로 도달할 수 없을 else 분기의 조건 타입(제대로 작동한다면 결코 도달하여 실행될 일이없는 코드)
  • 거부된 프로미스에서 처리된 값의 타입
onst p = Promise.reject('foo') // const p: Promise<never>

1-2. never 타입은 어떻게 쓸까?

많이 사용하진 않지만 아래와 같은 적절한 사용 사례가 있다.

 

1) 허용할 수 없는 함수 매개변수에 제한을 가한다. 

2) switch, if-else 문의 모든 상황을 보장한다.

  • 함수가 단 하나의 never타입 인수만을 받을 수 있는 경우, (TS 컴파일러가 오류를 발생하지 않고는) 해당 함수를 `never`타입 이외의 값으로 호출할 수 없다.
function unknownColor(x: never): never {
    throw new Error("unknown color");
}


type Color = 'red' | 'green' | 'blue'

function getColorName(c: Color): string {
    switch(c) {
        case 'red':
            return 'is red';
        case 'green':
            return 'is green';
        default:
            return unknownColor(c); // 'string' 타입은 'never' 타입에 할당할 수 없음
    }
}

3) 타이핑을 부분적으로 허용하지 않는다.

  • VariantA 또는 VariantB 타입의 매개변수를 받는 함수가 있다. 사용자는 두 타입의 모든 속성을 모두 포함하는 하위 타입을 전달해서는 안된다. 매개변수에는 유니언 타입 | (or) 을 활용할 수 있다.
  • 매개변수에는 유니언 타입 VariantA | VaraintB 를 허용할 수 있다. 단, 타입스크립트의 타입 호환성은 구조적 서브 타이핑을 기반으로 하기 때문에 객체 리터럴을 전달하지 않는 한 파라미터의 타입보다 더 많은 속성을 가진 객체를 함수에 전달하는 것은 허용된다. 
  • 아래의 예시는 오류를 내지 않는다. 
type VariantA = {
    a: string,
}

type VariantB = {
    b: number,
}

declare function fn(arg: VariantA | VariantB): void


const input = {a: 'foo', b: 123 }
fn(input) // 타입스크립트 컴파일러는 아무런 문제도 지적하지 않지만, 우리의 목적에는 맞지 않는다.

위의 예시에서 never를 사용하면 구조적 타이핑을 비활성화하고 사용자가 두 속성을 모두 포함하는 객체를 전달하지 못하도록 할 수 있다.

type VariantA = {
    a: string
    b?: never
}

type VariantB = {
    b: number
    a?: never
}

declare function fn(arg: VariantA | VariantB): void


const input = {a: 'foo', b: 123 }
fn(input) // ❌ 속성 'a'의 타입은 호환되지 않는다.

4) 의도하지 않은 API 사용을 방지한다. 

5) 이론적으로 도달할 수 없는 분기를 표기한다.

  • `infer`를 사용해 조건부 타입 내에 추가 타입 변수를 생성할 경우 모든 `infer`키워드에 대해 else 분기를 추가해야 한다. 
type A = 'foo';
type B = A extends infer C ? (
    C extends 'foo' ? true : false// 이 표현식 내에서 'C'는 'A'를 나타낸다.
) : never // 이 분기는 도달할 수 없지만, 생략도 할 수 없다.

6) 유니언 타입에서 멤버 필터링

 

도달할 수 없는 분기를 나타내는 것 외에도 `never`타입은 조건부 타입에서 원하지 않는 타입을 필터링할 수 있다.

앞에서 언급한 바와 같이, `never`타입은 유니언 타입에서 자동으로 제거된다. 즉, never타입은 유니언 타입에서는 쓸모가 없다. 

 

7) 매핑된 타입의 키 필터링

 

타입스크립트에서 타입은 불변이다. 객체 타입에서 한 속성을 제거하고 싶으면 기존 타입을 변환하고 필터링해 새로운 타이을 만들어야 한다. 매핑된 타입의 키를 `never`로 조건부로 다시 매핑하면 해당 키가 필터링된다.

type Filter<Obj extends Object, ValueType> = {
    [Key in keyof Obj 
        as ValueType extends Obj[Key] ? Key : never]
        : Obj[Key]
}

interface Foo {
    name: string;
    id: number;
}

type Filtered = Filter<Foo, string>; // {name: string;}

8) 제어 흐름 분석의 좁은 타입

함수 반환 값을 `never`로 설정하면 함수는 실행이 끝났을 때 제어권을 호출자에게 반환하지 않는다.

이를 활용하면 흐름 분석을 제어해서 타입을 좁힐 수 있다.

함수는 몇몇 이유로 아예 혹은 절대 리턴을 하지 않는다. e.g.) 모든 코드 경로에 예외를 발생시키거나, 무한 루프이거나, 프로그램에서 종료(Node의 process.exit)되는 경우

foo에 대한 유니언 타입에서 `undefined`를 제거하기 위해 `never`타입을 반환하는 함수를 사용한다.

function throwError(): never {
    throw new Error();
}

let foo: string | undefined;

if (!foo) {
    throwError();
}

foo; // string

또는 || 또는 ?? 연산자 다음에 throwError 를 호출한다.

let foo: string | undefined;

const guaranteedFoo = foo ?? throwError(); // string

9) 호환되지 않는 타입의 불가능한 교차 타입 표시 

호환되지 않는 타입의 교차해서 `never`타입을 얻을 수 있다.

type Res = number & string // never

그리고 아무 타입과 `never` 타입을 교차해서 `never`타입을 얻을 수 있다. 

type Res = number & never // never

+ 객체 타입에 대해서는 더 복잡.. -> 원문 확인하기

 

빈배열 `never`

빈 배열을 사용하다보면 never라는 타입을 만날 수 있다. never타입이 뜨면 해당 배열에는 일반적인 타입이 올 수 없다. 

그렇기 때문에 빈 배열을 할당할 때에는 꼭 타입을 명시하자

try {
    const array: string[] = [];
    // array[0];
    array.push("hello");
  } catch (error) {
    error;
  }

! (느낌표)

- | (Union) 으로 null 이나 undefined가 될 수 있는 변수에 이건 꼭 값이 있어! 라고 보증하는 표시이다. 

const head = document.querySelector("#head")!; //head에 !가 없으면 head의 타입은 Element | null 이다.
head.innerHTML = "hello world"; //느낌표가 없으면 head에 빨간 밑줄이 생기며 null일 수 있다고 메세지를 준다.

하지만, !로 보장하는 일은 최대한 피해야 한다. 절대 있을 거라는 보장을 할 수 없다. 

아래와 같이 조건문을 함께 써주는 게 안전하다.

const head = document.querySelector("#head");
if (head) {
  head.innerHTML = "hello world";
}

커스텀 타입 시, 자동완성(ctrl + space)추천을 제공한다.

 type Word = "world";
 const a :Word = "world" //타입을 Word로 지정했다면 값을 쓸 때, 해당 변수에 들어갈 값으로 world를 추천해준다.

템플릿 리터럴 타입 활용

 type Word = "world" | "hell";
  const a: Word = "world";

  const b = `hello ${a}`;
  type Greeting = `hello ${Word}`;//여기서는 c의 값을 추천해준다.
  type Greeting = `hello ${string}`; //이렇게 하면 c변수의 값에 자동완성 추천을 받을 수 없다. 값을 모르기 때문
  const c: Greeting = "hello world" // 타입을 정교하게 자동완성기능과 함께 만들 수 있다.

레스트 파라미터

function rest(...args: string[]) {
    console.log(args);
}
  
function rest2(a:number, ...args: string[]) {
    console.log(a, args);
}

rest("1", "2", "3"); // ["1", "2", "3"]
rest2(1, "1", "2", "3"); // 1,  ["1", "2", "3"]

튜플 심화

튜플은 길이 제한이 있지만 push로 배열에 추가할 경우, ts에서 막지는 못한다. 에러메세지가 뜨지않음

  const tuple: [string, number] = ["str", 1];
  //tuple[2] = "hello"; //얘는 에러뜨는데
  tuple.push("hello"); //얘는 된다.(ts가 못막음)
  console.log(tuple); //["str", 1, "hello"]

Enum

-값을 지정해주지않으면 0부터 시작한다. 순서대로 +1씩된다.  = 대입연산자를 통해 값을 따로 지정해줄 수 있다. 

-숫자도 되고 문자열도 값으로 넣을 수 있다. 

언제 쓰나요? 여러 개의 변수들을 하나의 그룹으로 묶고 싶을 때 쓴다. -> 그냥 일반 객체써도 된다.

💥TS -> JS 변환 시,  enum은 사라지지만 객체로 선언된 부분은 남아있다. 
const enum EDriection {
    Up,
    Down,
    Left,
    Right,
  }
  const a = EDriection.Up;
  console.log(a); // 0
  
  //불규칙적으로 지정해줄수도 있다.
  const enum EDriection {
    Up = 3,
    Down = 6,
    Left = 4,
    Right = "문자열",
  }
  
  
  //💛enum 대신 객체 활용
  // as const : type assertion의 한 종류로 리터럴 타입의 추론 범위를 줄이고
  //값의 재할당을 막기 위한 목적 -> 객체와 배열의 경우 const 로 선언되었다해도
  //내부의 프로퍼티는 보호받지 못하므로 as const 를 해주면 readonly로 변경되고 각 프로퍼티의
  //타입이 할당된 리터럴 값으로 추론된다(as const를 안할경우 number로 추론).
  
  //as const 를 안쓰려면 객체 타입을 직접 명시해줘야 한다. 
  const ODirection = {
    Up: 0,
    Down: 1,
    Left: 2,
    Right: 3,
  } as const;

  const objectElemA = ODirection.Up;
  console.log(objectElemA); //0

차이점이 있다면 enum은 직접 타입으로 사용이 가능하지만 객체로 타입을 정의했을 경우, 추가적인 코드 작성이 필요하다. dir의 의미는 enum의 4가지 속성 중 하나라는 의미이다.

-> 객체를 사용할 경우는 타입정의가 좀 복잡해진다. 

function walk(dir: EDriection) {} //enum 사용 시

type Direction = typeof ODirection[keyof typeof ODirection]; //추가로 타입 정의 코드
function run(dir: Direction) {} //객체 사용 시

walk(EDriection.Left);
run(ODirection.Down);

keyof,  typeof 

-객체는 자바스크립트에서 값이기 때문에 '타입'으로 쓸 수 없다. 그렇기에 타입으로 쓰고싶다면

값앞에 typeof 를 붙여주어서 type으로 만든 뒤 커스텀 타입으로 쓸 수 있다.

 // as const를 빼면 obj에 마우스를 올렸을 때, 타입추론을 string으로 널널하게 하고있다. 
 // 좀 더 정확하게 하고 싶다면 as const로 범위를 좁혀주면 된다. 
 const obj = { a: "123", b: "hello", c: "world" } as const;
  // 값을 타입으로 사용하고 싶을 떄, typeof를 쓴다.
  // 여기서 키들만 뽑아내고 싶다면 keyof로 해주면 된다.
 
 //💥객체의 key들만 뽑아내고 싶다. 
 type Key = keyof typeof obj;
 
 //💥객체의 value들만 뽑아내고 싶다. 
 type Value = typeof obj[keyof typeof obj];

| (Union) 사용 시 주의

아래의 코드는 논리적으로는 맞는데 결과적으로 코드가 꼬이게된다.

result 가 number가 아니라 string | number 로 추론하기 때문이다. 

  function add(x: string | number, y: string | number): string | number {
    return x + y;
  }
  const result: string | number = add(1, 2); //결과가 숫자인데 추론은 string | number 으로 하고있다. 
  // result.charAt(); 유니언을 남발하면 뒤에 로직이 다 꼬인다.

& (Intersection)

| 와 & 은 OR, AND라고 생각하면 편하다. 
//&(intersection) 의미: AND와 같음 둘 다 만족
  type A = string & number; //원시값
  // const a: A = 1; 에러 

  //주의점: const b는 type O가 &(and)가 아니라 | (or)일때도 만족되어 사용할 수 있다.
  // & - 모든 속성이 다 있어야 한다.
  // | - 여러 개 중에 하나만 있어도 된다.

  //참조값 시 intersection
  type O = { hello: "world" } & { zero: "cho" };
  const b: O = { hello: "world", zero: "cho" };

 

+ type과 &를 같이 쓰기 (마치 상속처럼)

// 타입을 &과 사용하여 상속처럼 사용 가능
  type Animal = { breath: true };
  type Poyouryu = Animal & { breed: true };
  type Human = Poyouryu & { think: true };

  const cong: Human = { breath: true, breed: true, think: true };

인터페이스를 사용해도 된다. type과 interface중 무엇을 쓸 지 결정하면 된다. 

  • 인터페이스와 타입의 차이는 타입은 코드를 간결하게 인라인처럼 코드를 다같이 써줄 수 있지만 인터페이스는 안된다. 인터페이스가 extends라는 키워드를 사용하여 상속의 개념이 직관적이다. 보통은 인터페이스를 쓰는 것이 나을 거 같다. 인터페이스와 타입은 아예 둘이 구분된 것이 아니라 혼용가능하며 타입을 표현하는 방식의 차이라고 생각하면 된다. 
 interface A {
    think: true;
  }
  interface B extends A {
    breed: true;
  }
  const b: B = {
    breed: true,
    think: true,
  };

💥인터페이스는 같은 이름으로 여러 번 선언이 가능하다. 

-> 선언을 할 때마다 합쳐진다. 그래서 라이브러리들이 대부분 타입이 아니라 인터페이스로 만들어 놨다.

내가 다른 사람의 라이브러리를 가져와서 확장하거나 수정할 때 유용하다. 반면에, 타입 에일리어스는 합쳐지지 않는다. 

//인터페이스 특징: 확장성, 중복 선언 가능(동일한 인터페이스에 프로퍼티가 추가된다.)
  interface A {
    talk: () => void;
  }
  interface A {
    sleep: () => void;
  }
  interface A {
    love: () => void;
  }
  interface A {
    eat: () => void;
  }
  const a: A = { talk() {}, eat() {}, love() {}, sleep() {} };

TypeScript Naming Rule

크게 두 가지로 타입에 T를 붙이고 이넘에 E를 붙이고 인터페이스에 I를 앞에 붙이는 것이 있었는데 요즘은 안붙인다.

-> 붙여도 되지만 어차피 마우스에 코드를 올리면 보인다(IDE가 해주는 것), 요즘은 제네릭에만 붙이고 나머지는 안붙인다고 함

 

좁은 타입과 넓은 타입(+잉여속성 검사)

  //좁은 타입과 넓은 타입
  //좁은 타입을 넓은 타입에 넣는 것(대입)은 가능
  //하지만, 넓은 타입을 좁은 타입에는 X 집합을 예시로 생각하면 된다.
  // ->직관적으로 헷갈리는 타입을 그냥 다른 타입으로 넣어봤을 때,
  // 오류가 안뜬다면 좁은 타입인거고 오류가 뜬다면 넓은 타입을 좁은 타입에 넣은 것.
  type A = string | number;
  type B = string;

  type C = string & number;

  //객체는 상세적(구체적)일수록 좁은 범위라고 생각하면 된다.
  type ObjA = { name: string };
  type ObjB = { age: number };
  type ObjAB = ObjA | ObjB;
  type ObjC = ObjA & ObjB;
  const ab: ObjAB = {
    age: 28,
  };

  //예외적인 case, 타입이 넓냐 좁냐 검사 외에도 '잉여속성 검사'를 하는데
  //객체 리터럴일 때 바로 집어넣어서 타입 사이즈 체크를 하면 헷갈릴 수 있다.
  //(좁은 타입을 넓은 타입에 넣은 건데 왜 안되지? 싶은 순간)
  //이럴 때는 중간에 다른 객체로 빼주기만 해도 에러가 사라진다.
  const objC: ObjC = {
    age: 28,
    name: "cong",
    // married: false, 여기서 객체 리터럴이라 바로 에러가 난다.(컨텐츠를 그대로 대입하는 방법)
  };
  //obj라는 객체로 따로 빼주어 대입하면 에러가 뜨지 않는다.
  const obj = { name: "cong", age: 28, married: true };
  const objC1: ObjC = obj;
💥타입스크립트에서는 "객체 리터럴"을 쓸 때, 잉여 속성 검사라는 추가 기능이 들어가는데 이럴 땐 객체리터럴로 직접 넣었던 컨텐츠를 변수로 빼 준 다음 넣어주면 에러가 사라진다. 

 

void 타입

: 리턴 값이 없는 것을 말한다. 

*대신 return undefined는 되고 null은 안된다. (함수에서 기본적으로 아무것도 return 해주지않을때 return undefined여서 그런가) 그래서 함수에 return; 으로 끝나거나 명시적으로 return undefined 혹은 return 키워드가 없는 함수는 void 타입이다. 

void는 크게 세 가지 경우로 생각해주면 되는데 

  1. 함수의 직접적인 리턴값이  void인 경우
  2. 메서드에서의 void
  3. 매개변수로 선언된 콜백함수의 void

함수 선언 시의 void와 메서드로서의 void는 역할이 다르다.

메서드와 콜백함수의 void의 경우 return 값이 있어도 된다.(오류 X)

💥why? 메서드와 콜백함수에서의 void의 의미는 "이 리턴값을 사용하지 않겠다"라는 의미이며 함수 선언 시의 리턴값의 타입을 void로 선언한 경우는 "이 함수는 리턴값이 없다" 라는 의미이기 때문이다. 
 //void 타입
  //따로 빠져있는 함수의 void + 매개변수로 선언된 함수(콜백)의 void
  function a(callback: () => void): void {
    return;
  }
  //메서드로서의 void
  interface Human {
    talk: () => void;
  }
  const human: Human = {
    talk() {
      return 123;
    },
  };

+ declare 키워드

  • 변수, 상수, 함수, 또는 클래스가 어딘가에 이미 선언되어 있음을 알린다. 
  • JS코드로는 컴파일되지 않고 TS컴파일러에게 타입 정보를 알리기만 하는 용도
  • 타입의 경우 어차피 JS코드로 컴파일되지 않으므로 declare 키워드를 사용하지 않아도 된다. 
  • 당연히 블록 스코프에서는 사용 불가능
  • 다른 스크립트에 해당 함수가 있는데 이 함수를 다른 스크립트에서 쓸 때 사용한다. declare의 의미는 임시적으로 다른 곳에 선언된  것을 보증할 때 타입을 선언만 해두는 용도로 쓰인다. 
    • 외부에서 만들어진 애들을 타입 선언하는 것
//이전에 함수도 body(구현부)없이 선언할 수 있다고 했는데
//대신 구현부와 선언부를 나눠놓은 것이기 떄문에 바로 밑에 구현부를 써줘야 한다.
declare function forEach(arr: number[], callback: (el: number) => void): void;
//구현부를 만들 기 싫을때는 선언부 앞에 declare를 붙여주면 된다.-> 실제 JS파일에서는 사라진다.
let target: number[] = [];
forEach([1, 2, 3], (el) => target.push(el));

unknown과 any ( 타입 대입가능표)

any - 타입 선언 포기(제일 피해야 함)

unknown - 당장은 타입을 정확히 모름 -> 이후에 as로 타입 명시하여 사용할 때 사용 (any보다는 괜찮지만 피해야 함)

-> 둘의 차이점: unknown은 사용할 때 직접 타입을 명시해줘야 하고 any는 그냥 타입쓰는 것을 포기한 것

//any는 타입선언을 포기해버리는 것
  //unknown은 "지금 당장은 타입을 정확히 모를 때" 사용하는 것, 직접 쓸 때 타입을 as로 정의해주면 된다.
  const b: unknown = a.talk();
  (b as A).talk(); // 쓸 때 타입을 지정해서 사용할 수 있다. as로 강제 형변환

대표적인 unknown 예시

-try ... catch...에서 catch의 error 파라미터

 try {
  } catch (error) {
    //대표적으로 error의 타입은 unknown이다.
    //Error는 TS에서 제공하는 기본 error타입, Axios error의 경우 AxiosError로 명시해줘야 한다.
    (error as Error).message;
  }

타입을 잘못만들어서 강제 형변환할 때,

interface A {
    talk: () => void;
  }

  const a: A = {
    talk() {
      return 3;
    },
  };
  const b = a.talk() as unknown as number; //talk가 void로 잘못선언되어있기 때문에 강제형변환해야함
  b.toString(); b.toString(); //만약에 위에 as unknown as number가 없어서 TS문법 오류가 나도 js 로 컴파일이 안되는 것은 아님

-> 이렇게 타입이 자꾸 꼬이기 때문에 처음부터 타입을 잘 만드는 것이 중요하다. 

 

+ 타입 대입표는 아래 all ref의 zero cho님의 깃허브 리포(repo)에 있습니다. 외울 필요없는 것이 IDE에서 친절하게 안내 메세지를 띄워준다. 

any가 떠도 문제이지만 never가 떠도 문제이다. never은 모든 타입에 다 넣을 수 없다.

대표적인 예시로 빈배열을 쓸 때 never가 뜨기때문에 잘 명시해줘야 한다.  

타입 대입 가능 표

  • 타입 대입 가능 표에서 파란색 체크표시가 아니라 초록색으로 된 체크표시가 있는데 strict mode에서 차이점을 갖는 애들이다 얘들도 그냥 X라고 생각하면 된다. 
  • 표 읽는 법: 화살표를 기준으로 any가 unkown에 대입가능하다 이렇게 읽으면 된다.
// void가 undefined에 대입가능
function a():void{
    return undefined;
  }

 

all ref: https://github.com/ZeroCho/ts-all-in-one

 

GitHub - ZeroCho/ts-all-in-one

Contribute to ZeroCho/ts-all-in-one development by creating an account on GitHub.

github.com

 

댓글