[Vue.js] Vue3 + TypeScript #6 마지막 커스텀 타입 정의하기(Type Assertion, data, props, computed, methods)

하수도키

·

2021. 3. 14. 01:10

728x90
반응형
SMALL

 

2021.03.05 - [개발일기/Vue.js] - [Vue.js] Vue3+Typescript #1 - Vue, TypeScript 사용하는 이유(Why Vue & TypeScript)

2021.03.11 - [개발일기/Vue.js] - [Vue.js] Vue 3 + TypeScript #5 - 커스텀 타입 정의(Defining Custom Types)

지난 포스팅에서 커스텀 타입을 정의하는 방법을 알아봤다.

 

이제는 커스텀 타입을 적용해 보자.

우선 데이터(data) 옵션을 살펴보자.

 

event 객체를 참조하는데 만약 event.title 대신 event.description로 수정하면 코드를 실행하기 전까지 description의 값이 있는지 없는지 확인할 방법이 없다.

VS Code를 사용하면 확장 프로그램인 VueDX를 살펴보자.

 

VueDX 소개(Introducing VueDX)

marketplace.visualstudio.com/items?itemName=znck.vue-language-features&ssr=false

타입스크립트 확장에 의해. vue 파일들에 대해서 타입 체킹 등 여러 가지 유용한 기능들을 제공한다.

(This extension provides features like type checking, completion, renaming and refactoring for. vue files by extending TypeScript extension.)

<template>
  <div v-if="event">
    <h1>{{ event.title }}</h1>
    <p>{{ event.time }} on {{ event.date }} @ {{ event.location }}</p>
    <p>{{ event.description }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import EventService from '../services/EventService'
export default defineComponent({
  props: ['id'],
  data() {
    return {
      event: null
    }
  },

event의 값이 null인데 event가 객체인 줄 알고 속성 값에 접근하게 되면 실행 전까지 알 수가 없지만

VueDX를 설치하면 아래와 같이 경고 밑줄로 알려준다.

vueDX 에러 메서지

이제 저 에러 메시지들을 없애보자

Event 커스텀 타입 정의하기(Defining the Event Custom Type)

event에 null이 할당되어 있으므로 title, time, 기타 등등 속성이 없는 게 당연하다.

우선 event가 어디서 가지고 오는지부터 추적해보자.

보통 API 통신으로 request 하고 response 받은 data로 event에 할당한다.

이 과정은 지금 포스팅하고 거리가 머니까 생략한다.

response data는 아래와 같다.

[
  {
    "id": 123,
    "category": "animal welfare",
    "title": "Cat Adoption Day",
    "description": "Find your new feline friend at this event.",
    "location": "Meow Town",
    "date": "January 28, 2022",
    "time": "12:00",
    "organizer": "Kat Laydee"
  }
]

event type을 정의하기 위해 interface EventItem을 만들 예정이다.

이 interface 위치는 /src/types.ts에 정의하겠다.

그 이유는, types.ts에서 모든 타입, 인터페이스를 관리해서 유지보수를 편하게 하기 위함이다.

// types.ts
export interface EventItem {
  id: number
  category: string
  title: string
  description: string
  location: string
  date: string
  time: string
  organizer: string
}

더 명확하게 작성을 할 수 있는데 우선 여기서는 간단히 위와 같이 작성한다.

이제 위에서 정의한 EventItem을 import 하고 event에 정의해주면 된다.

import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  name: 'EventList',
  data() {
    return {
      event: null
    }
  },
})

근데 정의는 어떻게 할까?

정의하는 방법을 알아보기 전에 새로운 속성 type assertions을 배워보자.

 

type assertions이란?(What are type assertions?)

type assetions은 에디터에서 추론 타입을 다시 재 정의할 수 있게 한다.

다시 말하면 컴파일러에게 나 이 타입에 대해 더 많이 알고 있어 라고 이야기해주는 것 과같다. 예제로 살펴보자.

 

interface TodoItem {
  label: string
  complete: boolean
}

const futureTodoItem = {}

futureTodoItem.label = 'Install VueDX extension'
futureTodoItem.complete = false

futureTodoItem 빈 객체에 label, complete를 할당하려고 한다. 

futureTodoItem type error

위와 같이 type이 존재하지 않는다고 에러를 내뿜는다.

하지만 우리는 TodoItem이라는 타입이 되어야 한다고 알고 있다. 이걸 as 키워드를 사용해서 정의해보자.

interface TodoItem {
  label: string
  complete: boolean
}

const futureTodoItem = {} as TodoItem

futureTodoItem.label = 'Install VueDX extension'
futureTodoItem.complete = false

TodoItem type 적용

이제 에러가 사라졌다.

 

event data에 Type Assertion 사용(Use Type Assertion to Define Types in Data)

import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  name: 'EventDetails',
  data() {
    return {
      event: null // 1
      event: {} // 2
      event: {} as EventItem // 3
    }
  },
})

우리는 event 각 객체인걸 아니까 null 대신 {}(빈객체)로 변경해준다.(2)

그다음 빈객체에 우리가 정의한 EventItem 타입을 as 키워드를 사용해 재정의 해준다.(3)

 

Props에 Type 적용하기(Props with Types)

방금 data에 type을 적용하는 법을 알아봤고, 이번에는 props에 type을 적용하는 법을 살펴보자.

 

기본 Prop Types 정의(Default Prop Types)

Vue에서 보통 props을 정의하는 방법 2가지이다.

props 배열로 값만 작성하거나

props 객체를 만들어서 각각 여러 가지 속성을 작성하는 방법이 있다.

먼저 props 배열로 값만 작성하는 방법을 살펴보자.

import { defineComponent } from 'vue'

export default defineComponent({
  props: ['id']
})

다음은 props 객체 형태로 id에 추가적인 정보를 작성해보자.

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    id: {
      type: Number,
      required: true
    }
  }
})

props id 객체에 type, reuiqred 속성을 작성했다.

Number 타입으로 정의했고, id 값이 필수라고 작성했다.

위와 같이 사용하면 타입 정의가 충분하다.

하지만, Object 타입이 들어왔을 때 어떤 Object인지, 안에 어떤 속성을 가지고 있는 구체적으로 적어주기 위해서는 별도의 작업이 필요하다.

 

Props에 커스텀 타입 적용하기(Applying a Custom Type to Props)

props에 커스텀 타입을 적용하기 위해 위에서 data에 적용한 2가지 방법을 사용하면 될 것 같지만, 다른 방법을 사용해야 한다.

우선 2가지 방법을 사용할 경우, 어떤 결과가 나오는지 확인해보자.

import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  props: {
    event: {
      type: EventItem,
    }
  }
})

우선 EventItem을 type에 그대로 위에 코드처럼 작성한 경우 아래 이미지처럼 에러가 발생한다.

props type error

EventItem은 타입스크립트의 타입이므로 자바스크립트에서는 제대로 평가되지 않는다.

 

그럼 2번째 as 키워드를 활용해보자.

import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  props: {
    event: {
      type: {} as EventItem,
    }
  }
})

역시 아래 이미지처럼 에러가 발생한다.

props type error

Vue props에서는 타입스크립트의 타입을 체크하는 별도의 기능을 제공해주기 때문에 위와 같이 에러가 발생한다.

Vue에서 제공하는 기능을 살펴보기 전에 TypeScript Generics에 대해 알아보자.

 

TypeScript Generics

타입스크립트를 사용해 기본 함수에 타입을 지정하면 아래 코드와 같다.

function createList(item: number): number[] {
    const newList: number[] = []
  
    newList.push(item)
  
    return newList
}

const numberList = createList(123)

타입을 쉽게 작성할 수 있지만, 함수로써의 역할은 제한적이다.

만약 이 함수의 이름을 재정의한다면, addNumberToNumberList라고 작성하면 된다.

그러나 이 함수의 item이 number만 받을 수 있게 타입이 지정되어 있으니 재사용할 수가 없다.

 

타입스크립트에서 이러한 불편함을 해결하기 위해 제네릭(Generics)을 제공한다.

추후 높은 수준에서 함수 안에서 동적인 타입을 정의해야 하는 경우도 있다.

제네릭은 자바스크립트에 사용된 {} 중괄호 대신 <> 브라켓을 사용한다.

function createList<CustomType>(item: CustomType): CustomType[] {
    const newList: CustomType[] = []
  
    newList.push(item)
  
    return newList
}

const numberList = createList<number>(123)

위와 같이 코드를 작성하면 제네릭 사용이 끝난다.

하지만, 저런 식으로 작성하게 되면 새로운 개발자는 혼란스럽고 코드 읽기도 어렵다.

그래서 타입 스크립트 커뮤니티에서 T 문자로 제네릭에서 커스텀 타입을 정의한다.

 

function createList<T>(item: T): T[] {
    const newList: T[] = []
  
    newList.push(item)
  
    return newList
}

const stringList = createList<T>(123)

 

이제 vue에서 적용해보자.

 

PropType 헬퍼 메서드(PropType Helper Method)

Vue3에서 커스텀 타입을 props 정의할 때는 헬퍼 메서드인 PropsTypes을 import 해야 한다.

import { PropTypes } from 'vue'

PropsTypes를 사용하려면 아래 코드처럼 사용하면 된다.

코드를 보면 왜 위에서 제네릭을 먼저 살펴봤는지 이해할 수 있다.

as 키워드와 제네릭처럼 PropType 사용해 event에 타입을 정의했다.

import { defineComponent, PropType } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  props: {
    event: {
      type: Object as PropType<EventItem>,
      required: true
    }
  },
})

Computed, Methods에서 커스텀 타입 적용하기(Computed & Methods with Custom Types)

이제 슬슬 마무리할 시간이 다가왔다.

Computed, Methods에 타입 적용하는 법을 끝으로 이 시리즈는 끝내도록 하겠다.

 

Computed 프로퍼티에 커스텀 타입 적용하기(Custom Types with Computed Properties)

<template>
  <div v-if="currentEvent">
    <h1>{{ currentEvent.title }}</h1>
    <p>
      {{ currentEvent.time }} on {{ currentEvent.date }} @
      {{ currentEvent.location }}
    </p>
    <p>{{ currentEvent.description }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
// eslint-disable-next-line no-unused-vars
import { EventItem } from '../types'

export default defineComponent({
  name: 'EventDetails',
  data() {
    return {
      event: [] as EventItem[]
    }
  },
  created() {
    this.event = [
      {
        id: 0,
        category: 'string0',
        title: 'string0',
        description: 'string0',
        location: 'string0',
        date: 'string0',
        time: 'string0',
        organizer: 'string0'
      },
      {
        id: 1,
        category: 'string1',
        title: 'string1',
        description: 'string1',
        location: 'string1',
        date: 'string1',
        time: 'string1',
        organizer: 'string1'
      }
    ]
  },
  computed: {
    currentEvent(): EventItem {
      return this.event[0]
    }
  }
})
</script>

위 예제 코드는 정상적으로 computed에 currentEvent에 EventItem 타입을 정의했다.

data에 event: [] as EventItem[] 코드는 EventItem을 타입으로 가진 배열이라고 이해하면 된다.

computed에서 currentEvent(): EventItem으로 정의하면 함수에 적용했던 것과 같이 return값의 타입을 정할 수 있다.

 

Methods에 커스텀 타입 적용하기(Custom Types with Methods)

<script lang="ts">
import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  data() {
    return {
      events: [] as EventItem[]
    }
  },
  methods: {
    addEvent(newEvent) {
      this.events.push(newEvent)
    }
  }
})
</script>

addEvent 메서드를 만들었고 여기에 type을 정의하고자 한다.

메서드에서 타입을 정의할 때는 2곳을 해야 한다. parameter랑 return 값이다.

addEvent(newEvent: EventItem): EventItem {
  this.events.push(newEvent)
  return this.ervents[1]
}

함수랑 비슷하게 매개변수 :(세미콜론) 뒤에 타입을 작성하고, () 괄호 뒤 :(세미콜론)에 리턴 값의 타입을 정하면 된다.

 

결론

Vue3 + TypeScript에 대한 기초적인 부분들을 살펴봤다.

진짜 기초적인 내용이므로 실무에 사용하면서 더 공부하고 좋은 예제들을 살펴봐야 한다.

구글링을 어마어마해야 될 것 같다.

타입 스크립트를 자체를 더 깊이 있게 사용하고 , vue3에서 타입스크립트를 더 깊이 있게 사용하고, 마지막으로 composition API에서 타입스크립트를 쓰는 법도 더 알아봐야 한다!

 

 

728x90
반응형
LIST