인프런 TIL : 타입스크립트 사용법 정리

te-ing·2022년 1월 6일
0
post-thumbnail

기본 타입

Boolean
Number
String
Object | let arr: number{} = { ... };
Array | let arr: number[] = [1,2,3];
Tuple | 배열의 길이, 요소 고정
Enum | 상수의 집합
Any
Void | 반환값 없음
Null
Undefined
Never | while(true)와 같이 절대 만들어지지 않을 값의 타입
Unknown | any와 같이 모든 타입의 값이 할당될 수 있지만 엄격한 검사


enum

enum Answer {
  Yes = 'Y',
  No = 'N',
}

function askQuestion(answer: Answer) {
  if (answer === Answer.Yes) {
    console.log('정답입니다');
  }
  if (answer === Answer.No) {
    console.log('오답입니다');
  }
}

상수의 집합인 enumobject와 비슷하지만 다음과 같은 차이점이 있다.

  • enum은 string과 number만 허용 가능하다.
  • 선언 이후 값을 변경할 수 없다.
  • 상수를 사용할 때보다 IDE의 적극적인 지원(단어추천)을 받을 수 있다.

unknown

  • any를 제외한 다른 타입으로 선언된 변수에 할당될 수 없음
  • 타입가드없이는 메소드 호출, 프로퍼티 접근, 인스턴스 생성이 불가능하다.
let variable: unknown
variable.foo.bar // Error: Object is of type 'unknown'.(2571)
variable[0] // Error
variable.trigger() // Error
variable() // Error
new variable() // Error

console.log(variable.length); // Error
if (typeof variable === "string") {
  console.log(variable.length); // 가능
}

any vs unkown

any 는 타입을 좁혀서 사용하지 않아도 되지만, unknown 은 타입을 좁혀서 사용해야만 한다.

unknown 타입은 지정된 타입을 먼저 확인한 후에 무언가를 할 수 있으므로 unknown 을 사용하는 것이 보다 안전하다. 따라서  unknown 으로 any 를 대체하여 보다 안전한 코딩이 가능하다.


타입정의

함수 타입정의

function log(a: number, b: number, c?: number): number {
	return a + b;
}
  • (a: number) 파라미터 타입정의
  • function log ( ...): number 반환값 타입정의
  • c?: string 옵셔널 파라미터

Interface 인터페이스

인터페이스 선언

interface User {
  age: number;
  name: string;
}
interface SumFunction {
  (a: number, b: number): number;
}

인터페이스 적용

const seho: User = {
  age: 23,
  name: "세호"
}

function getUser(user: User) {
  console.log(user);
}
getUser(seho);

extends 인터페이스 확장

interface Person {
  name: string;
  age: number;
}
interface Developer extends Person { // Person의 타입을 상속
  language: string;
}
	⬇⬇⬇⬇
interface Developer { // 위와 같은 의미
  name: string;
  age: number;
  language: string;
}

interface Developer {
  contact: number;
}
// 같은 interface 명으로 다시 만들 시 자동으로 확장됨

인터페이스를 통한 인덱싱, 딕셔너리 패턴

interface StringArray {
  [index: number]: string; // 인덱스를 통해 지정하는 것은 string으로만 지정할 수 있다.
}

const iArr: StringArray = ['a', 'b', 'c'];
// iArr[0] = 10; // 불가능
iArr[0] = 'd';

// 딕셔너리 패턴
interface StringRegexDictionary {
 [key: string]: RegExp // RegExp 정규표현식 타입으로만 지정해야 한다.
}

const iObj: StringRegexDictionary = {
  // cssFile: 'css' //불가능
  cssFile: /\.css$/,
  jsFile: /\.js$/,
}

인터페이스 vs 타입

타입은 extends 확장이 불가능하며, 링크 클릭(ctrl+click) 불가능하다.
type으로 지정된 객체에 hover시 지정된 타입을 볼 수 있다.

▶ 확장성 등의 이유로 대체로 interface 선언을 추천하고 있다.


타입스크립트 operator

유니온, 인터섹션

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeoneU(someone: Developer | Person) {
  someone.name; // 공통된 타입만 사용 가능
}
var arr = [1, 2, 3, true, true, 'abc', 'hello']; // string | boolean | number

function askSomeoneI(someone: Developer & Person) {
  someone.name; // 각각의 타입도 사용 가능
  someone.name
}
function logMessage(value: string | number) {
  if (typeof value === 'number') {  // 타입가드
    value.toString(); // 유니온타입으로 number 타입일 때의 단어추천 가능
  }
  if (typeof value === 'string') {
    value.trim(); // string 타입일 때의 단어 추천
  }
  else throw new TypeError('value must be string or number');
}

타입스크립트의 class

클래스 생성

class PersonT {
	constructor(age: number, name: string, contact: string) {
	    console.log('생성되었습니다');
	    this.name = name;
	    this.age = age;
	    this.contact = contact;
	  }
}
const sehoT = new PersonT(24, '세호', '010-2432-1214');
console.log(sehoT);

클래스 변수 접근 범위

class PersonT {
  public age: number; // 변수 접근 범위: 모든 클래스
  protected name: string; // 변수 접근 범위: 해당 클래스와 자식 클래스
  private contact: string; // 변수 접근 범위: 해당 클래스
  readonly log: string; // 변수 접근 범위: 변경불가, 읽기만 가능
}

generic

function logText<T>(text: T): T { // 받는타입: T 인자타입: T 반환타입: T
  console.log(text);
  return text;
}
logText<number>(23);

제네릭의 타입제한

다음 상황에서 타입스크립트는 아직 talking이 string인지 모르기 때문에 error가 발생한다.
const talking = logText<string>('abc'); talking.split('');
아래는 위와 같은 상황을 해결하기 위한 타입제한 방법이다.

제네릭의 타입제한 - 1. 힌트주기

function logTextLength<T>(text: T[]): T[] { // text에 힌트를 주는 식
  console.log(text.length); 
  text.forEach(function (text) {
    console.log(text)
  })
  return text;
}
logTextLength<string>(['hi', 'abc']);

제네릭의 타입제한 - 2.정의된 타입 이용

interface LegnthType {
  length: number;
}
function logTextLength<T extends LegnthType>(text: T): T {
  text.length;
  console.log(text.length);
  return text;
}
logTextLength('abc');
logTextLength({length: 10});

제네릭의 타입제한 3 - keyof

interface ShoppingItem {
  name: string;
  price: number;
  stock: number;
}
// ShoppingItem의 옵션 중 하나가 제네릭이 된다는 정의
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
  return itemOption
}
getShoppingItemOption(10);
getShoppingItemOption<string>('a');
getShoppingItemOption("name")

타입단언 assertion

var c;
c = 20;
c = 'c';
var d = c as string // string으로 타입지정, as 사용 안할 시 any타입으로 설정됨

 

타입스크립트 추론이 아닌, 개발자가 직접 타입을 지정하는 방법


타입단언 예시 - DOM API 조작

var div = document.querySelector('div');
div.innerText; // error

위와 같이 DOM API를 조작하는 상황에서 divnull일 수 있기 때문에 if (div)를 통해 제한을 두어야 한다.
이때 var div = document.querySelector('div') as HTMLDivElement; 타입단언으로 null의 가능성을 없애면 바로 접근할 수 있다.


타입단언 예시 - 인수가 필요하지 않은 상황에서의 에러 대처

또한 위처럼 인수가 필요하지 않음에도 에러가 날 수 있는데, ({} as ...)와 같이 임시로 에러를 없앨 수 있다.


is타입을 이용한 타입가드 패턴

interface Developer {
  name: string;
  skill: string;
}
interface Person {
  name: string;
  age: number;
}

function isDeveloper(target: Developer | Person): target is Developer 
  return (target as Developer).skill !== undefined; // Developer면 true 반환
}

if (isDeveloper(tony)) { // 타입가드를 이용한 패턴
  console.log(tony.skill);
} else {
  console.log(tony.age);
}

if (isDeveloper(tony)): target is Developer 에서 targetDevelopertrue를 반환하도록 하는 패턴


인터페이스 확장

interface User {
  id: number;
  name: string;
  age: number;
  address: string;
  createdAt?: string;
  updatedAt?: string;
}

Partial

Partial: Optional 설정, 모든 필드가 있어도 되고 없어도 됨.

위와 같은 상황에서 다음과 같이 사용가능 const parial: Partial<User> = { createdAt: "2", };


Required

Required: 모든 필드 Required , 모든 필드가 있어야만 한다.

위와 같은 상황에서 다음처럼 모든 필드를 기입해야만 사용 가능하다.

const required: Required<User> = {
  id: 1,
  name: "Lee",
  age: 0,
  address: "Seoul",
  createdAt: "필수",
  updatedAt: "필수",
};

Pick, Omit

Pick: 특정 필드만 사용

const pick: Pick<User, "name" | "age"> = { name: "", age: 3, };

Omit: 특정 필드만 제외하고 사용

const omit: Omit<User, "id" | "createdAt"> = { name: "", age: 3, address: "", };


Mixed

const mixed: Omit<User, "id" | "age" | "address"> & Pick<Partial<User>, "age"> =
  {
    name: "",
    age: 3,
  };

Mixed: Interface 여러개 동시 사용


extends

interface Time {
  hour: number;
  minute: number;
}

interface DateTime extends Time {
  month: number;
  day: number;
}

interface TimeFormat extends Pick<Time, 'hour'> {
  ampm: number;
}

extends: 인터페이스 상속


Mapped Type

map()을 타입에 적용한 것과 같은 효과

type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
  Hulk: 54,
  Thor: 1000,
  Capt: 33,
}
	⬇⬇⬇⬇
type HeroProfiles = {
  Hulk: number;
  Thor: number;
  Capt: number;
}

keyof를 이용한 Mapped Type

type Subset<T> = {
  [K in keyof T]?: T[K];
}


참고사이트: 삼바의 성장 블로그, 타입스크립트 핸드북

profile
병아리 프론트엔드 개발자🐣

0개의 댓글