youngst
Young St_____
youngst
  • 분류 전체보기 (30)
    • Dev (28)
      • JS Family, HTML, CSS (14)
      • Git & Github (0)
      • Projects (7)
      • else (5)
      • TIL (2)
    • 일상 (2)
      • 그냥 글 (1)
      • 사과 농장에서 살아남기 (1)

인기 글

태그

  • TypeScript
  • 프론트엔드스쿨
  • vscode
  • jsdoc
  • Next.js
  • Vite
  • 옥소폴리틱스
  • CloneCoding
  • styled-components
  • react-router
  • 후기
  • 제로베이스
  • ReactRouter
  • 공부내용정리
  • Firebase
  • JSEvent
  • JavaScript
  • GitHub-Page
  • 제로베이스스쿨
  • React

티스토리

hELLO · Designed By 정상우.
youngst

Young St_____

Dev/JS Family, HTML, CSS

[TypeScript] 타입스크립트 입문 (4) - 제네릭, 고급 타입 다루기

2022. 9. 14. 16:52

타입스크립트 심화 기능

제네릭

  • 유니온 타입 등을 남발하다보면 결국 any와 다름없어지는 상황이 발생한다.
  • C# 및 Java와 같은 언어에서 널리 사용되는 문법
  • 대규모 소프트웨어를 구축할 수 있는 가장 유연한 기능을 제공
  • 와 같은 꺽쇠 괄호를 사용한다.
  • 다형성을 구현 가능하게 한다!
/**
 * 제네릭
 */

//이럴 바엔 any가 낫겠어...
//function getInfo(msg: string | number | number[] | string[] ) {

function getInfo(msg: any ) {
  return msg
}

console.log(getInfo('Word'))
console.log(getInfo(123))
console.log(getInfo([1, 2]))
console.log(getInfo(['C', 'T']))

제네릭 사용

  • Type을 변수처럼 받을 수 있다
/**
 * 제네릭 사용
 */
function getInfoUnion(msg: string | number | string[] | number[]): string | number | string[] | number[] {
  return msg
}

function getInfoAny(msg: any): any {
  return msg
}

// 타입을 변수처럼 받을 수 있다
// function 함수명<타입 변수 지정>(msg: 타입 인자): 타입 반환 {
function getInfo<T>(msg: T): T {
  return msg
}

console.log(getInfo<string>('Word'))
console.log(getInfo<number>(123))
console.log(getInfo<number[]>([1, 2]))
console.log(getInfo<string[]>(['C', 'T']))

함수에서의 제네릭

/**
 * 함수에서의 제네릭
 */
type TypeGetInfoFn = <T>(msg: T) => T;

interface InterfaceGetInfoFn {
  <T>(msg: T): T;
}

// function 함수명<타입 변수 지정>(msg: 타입 인자): 타입 반환 {
function getInfo<T>(msg: T): T {
  return msg;
}

const TypeAliases:TypeGetInfoFn = getInfo;
const TypeIntercae:InterfaceGetInfoFn = getInfo;

//튜플이랑 비슷!
function getInfoMultiple<T1, T2>(msg: [T1, T2]): [T1, T2] {
  return msg
}

getInfoMultiple<string, number>(['Str', 123])

클래스에서의 제네릭

/**
 * 클래스에서의 제네릭
 */
class Animal<T, U> {
  name: T
  genderType: U

  constructor(name: T, genderType: U) {
    this.name = name
    this.genderType = genderType
  }
}

type GenderType = 'M' | 'F'
const dog = new Animal<string, GenderType>('강아지', 'M')
const cat = new Animal<GenderType, string>('F', '고양이')

제네릭 타입 좁히기

  • 제네릭 타입은 자칫 잘못하면 any와 다를게 없어질 수 있다
  • 이럴 경우 extends 키워드를 이용하여 범위를 좁혀줄 수 있다
/**
 * 제네릭 타입 좁히기 (extends)
 */
function printMessage<T extends string | number>(msg: T) {
  return msg
}

function printArr<T extends string[] | number[]>(msg: T) {
  return msg.toString()
}

printMessage('Hello')
printMessage(123)
// Error!
// printMessage<string[]>(['H', 'E', 'L', 'L', 'O'])

printArr(['H', 'E', 'L', 'L', 'O'])
printArr([1, 2, 3, 4])

// Error!
// printArr<boolean[]>([true, false])

고급타입 다루기

인덱스 타입 다루기

  • 객체의 index에도 타입을 지정할 수 있다
  • 꼬일 수 있는 상황이 많기 때문에 주의해서 사용해야한다!
/**
 * Index 타입 다루기
 */
interface Todo {
  id: number
  content: string
}

// Indexed Access Type
type TodoId = Todo['id']

interface StrInterface {
  [index: number]: string
  name: string
  age: number
  // [index: string]: number
}

const Person: StrInterface = {
  99: '구구',
  name: '장',
  age: 99,
  22: '댜',
  5345: 'ㄹㅈㄷ'
  // genderType: 'M'
}

조건부 타입 다루기

  • 타입에 삼항 연산자 형식으로 조건을 걸 수 있다
/**
 * 조건부 타입 다루기
 * 
 * SomeType extends OtherType ? TrueType : FalseType;
 */
interface Person {
  name: string
}

interface Me extends Person{
  age: number
}
// 조건을 걸 수 있다
type Ex1 = Me extends Person ? string : number


class Galaxy {
  type = 'AOS'
}

class IPhone {
  type = 'IOS'
}
// Generic과 함께 사용하면 편리함
type MyDevice<T> = T extends 'IPhone' ?  IPhone : Galaxy

let myPhone: MyDevice<'Galaxy'>
let myPhone2: MyDevice<'IPhone'>
let myPhone3: MyDevice<'Xiaomi'>

type IsNumberType = 123 extends number ? true : false
type IsNumberType2 = number extends 123 ? true : false

const isNumberType: IsNumberType = true
const isNumberType2: IsNumberType2 = false

맵드 타입 다루기

  • 기존 타입에서 새로운 다른 타입으로 타입을 정의할 수 있다
  • 인덱스 서명이나 조건부 타입과 함께 사용할 수 있다
  • in 키워드를 사용한다
/**
 * (Mapped) 맵드 타입 다루기
 * 
 * { T in V: T }
 */

// 예시
const newNum = [1, 2, 3].map((num) => num ** 2)

type Person = {
  name: string
  age: number
  gender: string
}

type Dict<T> = {
  [key: string]: T
}

const newPerson: Dict<Person> = {}
//새로운 type 생성
newPerson.me

type NewRecord = {
  [key in 'you' | 'we']: Person
}

function printNewPerson(newPerson: NewRecord) {
  newPerson.we
  newPerson.you
  newPerson.me //정의되지 않은 타입
}

선언 병합

  • 타입스크립트의 특이한 개념
  • 컴파일러가 동일한 이름으로 선언된 두 개의 다른 선언을 하나의 정의로 병합한다.
  • - 원래의 기능이 모두 포함되며, 여러 선언을 병합할 수 있다.
  • TypeScript에서 선언은 네임스페이스, 타입, 또는 값의 세 그룹 중 적어도 하나의 엔터티를 만든다.
  • 네임스페이스는 점으로 구분된 표기법을 사용하여 엑세스하는 이름을 포함하는 영역을 생성한다.
선언 타입 네임스페이스 타입 값
네임스페이스 X   X
클래스   X X
열거형   X X
인터페이스   X  
타입별칭   X  
변수     X
함수     X
/**
 * 선언 병합
 */
interface Cat {
  name: string
}

interface Cat {
  age: number
}

//위의 선언이 병합되었다
const animal: Cat = {
  name: 'cat',
  age: 99
}

interface Person {
  name: string
  age: number
}

const Person = {
  name: 'Jang',
  age: 99
}

//인터페이스까지 포함되어 있다
export {
  Person
}

class Car {
  static createCar(): Car {
    return {
      name: 'Car'
    }
  }
}

namespace Car {
  function createCar(): Car {
    return Car.createCar()
  }
}

interface Car {
  name: string
  brandType?: string
}

//위의 3개가 다 합쳐진다 - 조심!
export {
  Car
}

 

타입 추론

  • 명시적으로 타이핑(Typing - 타입 선언) 하지 않았을 때 TypeScript가 해당 코드의 타입을 해석해 나가는 방식
  • const -> 리터럴 타입으로 추론 / let -> 기본 타입으로 추론

일반적인 타입 추론(Best common type)

  • 여러 식에서 타입을 유추해 가장 일반적인 타입을 계산
  • 각 후보 타입을 고려하고 다른 모든 후보 타입과 호횐되는 타입을 선택하는 알고리즘

문맥상 타이핑(Contextual Typing)

  • 간혹 타입 추론이 반대로 동작하는 경우가 있다 -> 이것을 문맥상 타이핑이라고 부른다.
  • 문맥상 타입 지정은 표현식 타입이 위치에 의해 암시적인 경우 발생한다.
/**
 * 타입 추론
 */
let str = 'string'
const str2 = 'string'

type Obj = {
  name: string
}

interface IObj {
  name: string
}

const div = document.querySelector('div')

div?.addEventListener('click', function(event: MouseEvent) {
  event.mouse
})

 

타입 단언

  • TypeScript가 알 수 없는(추론할 수 없는) 타입에 대한 정보가 있을 때 타입 단언을 사용하여 보다 구체적인 타입을 지정할 수 있다.
  • 하지만 컴파일 단계에서 제거되기 때문에 런타임 검사가 생략 된다 -> 타입 단언이 잘못되어도 null이나 예외를 발생시키지 않음
  • as 키워드 혹은 <>(JSX가 아닌 경우) 구문으로 사용 가능
  • 주로 DOM 요소나 event에 사용
/**
 * 타입 단언
 * 
 * 남용하면 any와 마찬가지가 된다
 */
const div = document.querySelector('.ts-wrapper') as HTMLDivElement
// const div2 = <HTMLDivElement>document.querySelector('.ts-wrapper')


const str = 'Hello' as unknown as number

 

never

  • 일부 함수의 반환이 없는 경우를 명시
  • void와는 조금 다르다!
  • 예외를 던지거나 실행이 종료될 때 사용
/**
 * never
 */
function someError(message: string): never {
    throw new Error(message)
}

function someFunc(param: string | number) {
    if (typeof param === 'string') {

    } else if (typeof param === 'number') {

    } else {
        // some never
    }

}

// 유니언 타입에서 제거가 됨
type StrUnion = never | string

// 교차 타입에서는 덮어씀
type NeverInter = never & string

 

keyof

  • 객체 타입의 키를 (문자열 or 숫자) 유니언 타입으로 생성
  • 리터럴 집합으로 만들어진 유니언 타입을 만들 때 유용하다.
  • 맵드 타입과 사용시에 더욱 유연하게 활용 가능

 

/**
 * keyof 타입 연산자
 */
interface Size {
    width: number
    height: number
}

type S = keyof Size // = 'width' | 'height'

function getSize(size: S) {
    return size
}

getSize(1111)
getSize('width22')
getSize('width')
getSize('height')

const SizeType = {
    width: 'Px1',
    height: 'Px2'
} as const

type SizeTypeValue = typeof SizeType[keyof typeof SizeType]

 

빈 값 관리하기

  • Non-null Assertion Operator
  • (!.) 으로 사용한다.
  • 값이 null이 아님을 개발자가 강제로 보장함 -> 에러 발생 가능!
/**
 * Non-null Assertion Operator (!.)
 * and
 * Optional chaining (?.)
 */
let nil = null
let un

function func():void {}

function func2():undefined {
    return undefined
}

function func3():null {
    return null
}

console.log(func())
console.log(func2())
console.log(func3())

// ---cut---

interface Home {
    foods?: string[]
    books?: string[]
}

const MyHome:Home = {}

// 값이 있음을 TS에 개발자가 알린다 - 에러 발생 가능!
MyHome.foods!.push('banana')

MyHome.books?.push('Clean Code')

 

Type Aliases vs Interfaces

  • 각자 다른 개념이지만 비슷한 점도 많고 다른 점도 있어 알아둘 것이 많다.
  • 둘 다 많이 사용되는 키워드(취향의 차이), 서로 비슷하게 대체도 가능함
  • 정답이 있는 것이 아니므로, 팀 & 개인의 규칙이나 선호도에 따라 다른 방식으로 사용 가능!
  • 비교표 수시로 찾아서 확인해보자.
  • 항상 최신 자료를 확인해야한다!!

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces

/**
 * Type Aliases vs Interfaces
 */
interface IAnimal {
    name: string
}

interface IBear extends IAnimal {
    honey: boolean
}

type TAnimal = {
    name: string
}

// interface는 Type Aliases를 확장할 수 있다.
interface IBear2 extends TAnimal {
    honey: boolean
}

// Error - extends는 interface만 받을 수 있다
// type TBear extends IAnimal = {}
// type TBear extends TAnimal = {}

type TBear = TAnimal & {
    honey: boolean
}

// 위의 중복선언 인터페이스와 선언 병합됨
interface IAnimal {
    age: number
}

const AnimalImpl: IAnimal =  {
    name: 'AnimalImpl',
    age: 99
}

// type alias는 중복 선언이 불가능하다 (선언 병합 지원 X)
// type TAnimal = {
//     age: number
// }

class Dog implements IAnimal {
    name = 'Dog'
    age = 99
}

class Dog2 implements TAnimal {
    name = 'Dog'
    age = 99
}

type CatType = 'City' | 'Forest' & {
    meow(): string
}

//기본 타입도 취급 가능
type S = string
type N = number
type B = boolean
// type CatType = 'City' | 'Forest'

// 인터페이스는 객체 위주
// interface CatType {
//     'City' | 'Forest' & {
//         meow(): string
//     }
// }

class Cats implements CatType {
    
}

 

 

 

 

저작자표시 비영리 변경금지 (새창열림)

'Dev > JS Family, HTML, CSS' 카테고리의 다른 글

[TypeScript] 타입스크립트 입문(5) - 타입 시스템  (0) 2022.09.17
[React] 리액트 심화 - SPA, React Router, 비동기 프로그래밍  (0) 2022.09.16
[TypeScript] 타입스크립트 입문(3) - 인터페이스, 타입 가드, 열거형, 유니온 타입  (0) 2022.09.12
[TypeScript] 타입스크립트 입문 (2) - 기본 타입, 클래스  (0) 2022.09.09
[TypeScript] 타입스크립트 입문(1)  (2) 2022.09.04
    'Dev/JS Family, HTML, CSS' 카테고리의 다른 글
    • [TypeScript] 타입스크립트 입문(5) - 타입 시스템
    • [React] 리액트 심화 - SPA, React Router, 비동기 프로그래밍
    • [TypeScript] 타입스크립트 입문(3) - 인터페이스, 타입 가드, 열거형, 유니온 타입
    • [TypeScript] 타입스크립트 입문 (2) - 기본 타입, 클래스
    youngst
    youngst
    좋은 프론트엔드 개발자가 되고 싶습니다

    티스토리툴바