ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Composition API] Typescript 적용하기 (script setup)
    Frontend/Vue3 2023. 3. 12. 16:34

    Typing Component Props

    <script setup lang="ts">
    import { defineProps } from 'vue';
    
    interface Props {
      name: string
      count?: number
    }
    
    const props = defineProps<Props>()
    </script>
    

    Props는 상위 컴포넌트(부모)에서 하위 컴포넌트(자식)로 데이터를 넘겨줄 때 사용합니다.

    Props Default Values

    <script setup lang="ts">
    import { defineProps, withDefaults } from 'vue';
    
    interface Props {
      msg?: string
      labels?: string[]
    }
    
    const props = withDefaults(defineProps<Props>(), {
      msg: 'hello',
      labels: () => ['one', 'two']
    })
    </script>
    

    default props값을 지정하는 방법입니다.

    Typing Component Emits

    <script setup lang="ts">
    // runtime
    const emit = defineEmits(['change', 'update'])
    
    // type-based
    const emit = defineEmits<{
      (e: 'change', id: number): void
      (e: 'update', value: string): void
    }>()
    </script>
    

    Emits는 하위 컴포넌트(자식)에서 상위 컴포넌트(부모)로 이벤트, 데이터를 넘겨줄 때 사용합니다.

    Typing ref()

    ref는 초기값으로 타입을 추론하는 기능이 기본적으로 내장되어 있습니다.

    import { ref } from 'vue'
    
    // inferred type: Ref<number>
    const year = ref(2023)
    
    // => TS Error: Type 'string' is not assignable to type 'number'.
    year.value = '2023'
    

    명시적인 타입 선언은 다음과 같습니다.

    //1. type Ref 선언해 적용하는 방식
    import { ref } from 'vue'
    import type { Ref } from 'vue'
    
    const year: Ref<string | number> = ref('2023')
    
    year.value = 2023 // ok!
    
    //2. ref() 에 override 하는 방식
    // resulting type: Ref<string | number>
    const year = ref<string | number>('2023')
    
    year.value = 2023 // ok!
    
    ---------------------------------------------
    // 초기값을 지정하지 않으면 type에 undefined가 자동으로 포함
    const n = ref<number>()
    

    Typing reactive()

    reactive도 초기값으로 타입을 추론하는 기능이 내장되어 있습니다.

    import { reactive } from 'vue'
    
    // inferred type: { title: string }
    const book = reactive({ title: 'Vue 3 Guide' })
    

    명시적인 타입 선언은 다음과 같습니다.

    import { reactive } from 'vue'
    
    interface Book {
      title: string
      year?: number
    }
    
    const book: Book = reactive({ title: 'Vue 3 Guide' })
    

    Typing computed()

    computed는 getter의 리턴값을 기준으로 타입을 추론하는 기능이 내장되어 있습니다.

    import { ref, computed } from 'vue'
    
    const count = ref(0)
    
    // inferred type: ComputedRef<number>
    const double = computed(() => count.value * 2)
    
    // => TS Error: Property 'split' does not exist on type 'number'
    const result = double.value.split('')
    

    명시적인 타입 선언은 다음과 같습니다.

    const double = computed<number>(() => {
      // type error if this doesn't return a number
    })
    

    Typing Event Handlers

    type을 명시하지 않으면 event의 인수는 any 타입을 갖습니다.

    <script setup lang="ts">
    function handleChange(event) {
      // `event` implicitly has `any` type
      console.log(event.target.value)
    }
    </script>
    
    <template>
      <input type="text" @change="handleChange" />
    </template>
    

    하지만 tsconfig.json에서 “strict”: true 또는 “noImplicitAny”: true가 사용되는 경우에는 타입 오류가 발생합니다.

    따라서 다음과 같이 명시적으로 타입을 선언해줍시다.

    function handleChange(event: Event) {
      console.log((event.target as HTMLInputElement).value)
    }
    

    Typing Provide, Inject

    Provide 와 Inject는 일반적으로 독립적인 컴포넌트로부터 수행됩니다.

    Inject된 값을 올바르게 입력하기 위해 Vue는 Symbol을 확장하는 interface 유형인 InjectionKey interface를 제공합니다. 이는 Inject된 값과 provider 간에 타입을 동기화 하는데 사용할 수 있습니다.

    import { provide, inject } from 'vue'
    import type { InjectionKey } from 'vue'
    
    const key = Symbol() as InjectionKey<string>
    
    provide(key, 'foo') // providing non-string value will result in error
    
    const foo = inject(key) // type of foo: string | undefined
    

    여러 컴포넌트에서 사용할 수 있도록 Injection key를 별도의 파일에 배치하는 것이 좋습니다.

    string Injection key를 사용하는 경우, Inject 값 유형은 unknown이 되기 때문에 generic type을 통해 명시적으로 선언해야 합니다.

    const foo = inject<string>('foo') // type: string | undefined
    

    Inject 값은 아직 undefined가 될 수 있습니다. 왜냐하면 provider가 런타임에 undefined를 제공하지 않는다는 보장이 없기 때문입니다.

    다음과 같이 두번째 인자로 default value를 입력하면 undefined를 제거할 수 있습니다.

    const foo = inject<string>('foo', 'bar') // type: string
    

    값이 항상 제공된다고 확신하는 경우 값을 강제로 캐스팅할 수도 있습니다.

    const foo = inject('foo') as string
    

    Typing Template Refs

    Template refs는 명시적인 generic 타입과 함께 초기값 null로 만들어야 합니다.

    <script setup lang="ts">
    import { ref, onMounted } from 'vue'
    
    const el = ref<HTMLInputElement | null>(null)
    
    onMounted(() => {
      el.value?.focus()
    })
    </script>
    
    <template>
      <input ref="el" />
    </template>
    

    엄격한 타입 체크를 위해 el.value에 접근할 때 optional chaining 또는 type guard를 사용해야 합니다.

    컴포넌트가 마운트되기 전까지는 초기 ref 값이 null이고, 참조된 element가 v-if에 의해 마운트 해제된 경우에도 null로 설정될 수 있기 때문입니다.

    Typing Component Template Refs

    자식 컴포넌트에 포함된 public method를 사용하기 위해 template ref를 선언해야 하는 경우가 있습니다. 예를 들어, 아래와 같이 모달을 여는 메서드가 있는 MyModal 자식 컴포넌트가 있습니다.

    <!-- MyModal.vue -->
    <script setup lang="ts">
    import { ref } from 'vue'
    
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)
    
    defineExpose({
      open
    })
    </script>
    

    MyModal의 인스턴스 type을 가져오려면 먼저 typeof를 통해 type을 가져온 다음 TS의 내장 InstanceType 유틸리티를 사용해 인스턴스 type을 추출해야 합니다.

    <!-- App.vue -->
    <script setup lang="ts">
    import MyModal from './MyModal.vue'
    
    const modal = ref<InstanceType<typeof MyModal> | null>(null)
    
    const openModal = () => {
      modal.value?.open()
    }
    </script>
    

     

    https://vuejs.org/guide/typescript/composition-api.html#typing-component-props

     

    TypeScript with Composition API | Vue.js

     

    vuejs.org

     

    댓글

Designed by Tistory.