lobo_tuerto's notes
Home
Blog
Notes
About

Vue 3 with TypeScript cheat sheet

How to properly use types when writing Vue components.

📅Published29 July 2022Last updatedJul 2022
🏷️
frontendtypescriptvue

Just a quick cheat sheet on how to define and do basic stuff when using the composition API with TypeScript.

Table of contents

Prerequisites

For this, you’ll need a Vue 3 + TypeScript (+ Tailwind CSS) project.
You can set up one following the instructions here:
Build a Vue 3 + TypeScript dev environment with Vite

Basic types

  • If you want a type meaning “any value”, you probably want unknown instead.
  • If you want a type meaning “any object”, you probably want Record<string, unknown> instead.
  • If you want a type meaning “empty object”, you probably want Record<string, never> instead.

Props

<script setup lang="ts">
const props = defineProps<{
  ppi: number | null
  mapConfig: MapConfig
}>()
</script>

Emits

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'frame:height', value: number): void
  (e: 'frame:width', value: number): void
  (e: 'layer:toggle', value: LayerSpecification): void
  (e: 'map:add-text', value: string): void
  (e: 'map:download'): void
  (e: 'text:remove', value: [AddedText, number]): void
}>()
</script>

Refs

Typing refs:

<script setup lang="ts">
import { ref } from 'vue'

const activeMenuIndex = ref<number | null>(null)

// later...
activeMenuIndex.value = 5
activeMenuIndex.value = null
</script>

Get a DOM reference:

<script setup lang="ts">
import { ref } from 'vue'

const textToAdd = ref('')
const textToAddInput = ref<HTMLInputElement | null>(null)

onMounted(() => {
  console.log(textToAddInput.value)
})

function focusInput() {
  textToAddInput.value?.focus()
}
</script>

<template>
  <input
    ref="textToAddInput"
    v-model.trim="textToAdd"
    type="text"
  />
</template>

Events

When working with input events, the event handler will receive an Event type, then you’ll have to assert the currentTarget type as the one you need.

<script setup lang="ts">
function handleHeightResize(ev: Event) {
  const value = (ev.currentTarget as HTMLInputElement).value

  if (value !== '') {
    const inPixels = toPixels(parseInt(value))
    emit('frame:height', inPixels)
  }
}
</script>

<template>
  <input
    type="number"
    min="0"
    step="1"
    :value="frameHeightInUnits"
    @input="handleHeightResize"
  />
</template>

Provide / inject

The way I see it, provide and inject are basically localized global props.

// TheParent.vue
const ppi = ref<number | null>(null)
provide('ppi', ppi)

// AGrandGrandGrandChild.ts
const ppi = inject<Ref<number | null>>('ppi')

Watch

Watching some values that in turn will update another value that depends on a DOM element that changes based on the watched values (need to wait for next tick!):

<script setup lang="ts">
const boundingRect = ref<DOMRect | undefined>(undefined)
const height = computed(() => `${props.height}px`)
const width = computed(() => `${props.width}px`)

watch([height, width], () => {
  nextTick(() => {
    boundingRect.value = frameRef.value?.getBoundingClientRect()
  })
})

defineExpose({
  boundingRect
})
</script>

<template>
  <div
    ref="frameRef"
    class="frame"
  ></div>
</template>

<style scoped>
.frame {
  height: v-bind(height);
  width: v-bind(width);
}
</style>

Somewhere in the parent:

const frameRef = ref<{ boundingRect: DOMRect } | null>(null)

// later...
function handleMapDownload() {
  const boundingRect: DOMRect | undefined = frameRef.value?.boundingRect

Misc

What about setInterval?

type MaybeInterval = ReturnType<typeof setInterval> | null

const interval = ref<MaybeInterval>(null)

onMounted(() => {
  interval.value = setInterval(() => console.log("I'm called every 3 seconds..."), 3000)
})

onBeforeUnmount(() => {
  if (interval.value) {
    clearInterval(interval.value)
  }
})

References


Got comments or feedback?
Follow me on
v-529da0c