We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
        
  
    
    2022 / 07 / 29
  
  
    
  
  
    
    2023 / 03 / 31
  
        Vue 3 with TypeScript cheat sheet
      
      How to properly use types when writing Vue components.
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, Records
- 
If you want a type meaning “any value”, you probably want unknowninstead.
- 
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.
Adding properties to the window object
Add a src/index.d.ts file with this content:
export {}
declare global {
  interface Window {
    someVariable: string
    otherThing: number
    // any other variables you need here...
  }
}With that you’ll avoid the error:
Property ‘someVariable’ does not exist on type ‘Window & typeof globalThis’.
Vue components
If you are passing a Vue component as a property or assigning to a variable:
import { defineComponent } from 'vue'
export interface MenuItem {
  label: string
  icon?: ReturnType<typeof defineComponent>
  children: MenuItem[]
}Props
<script setup lang="ts">
const props = defineProps<{
  ppi: number | null
  mapConfig: MapConfig
}>()
</script>Props with defaults
<script setup lang="ts">
const props = withDefaults(
  defineProps<{
    buttonStyle?: 'primary' | 'secondary'
  }>(),
  { buttonStyle: 'primary' }
)
</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>DOM refs
Get a DOM reference to an HTML input element:
<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>Component refs
Get a DOM reference to a Vue component:
<script setup lang="ts">
import { ref } from 'vue'
import BaseButton from '@/components/buttons/BaseButton.vue'
const buttonRef = ref<InstanceType<typeof BaseButton> | null>(null)
onMounted(() => {
  console.log(buttonRef.value)
})
</script>
<template>
  <BaseButton ref="buttonRef">Click me</BaseButton>
</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?.boundingRectsetTimeout
type MaybeTimeout = ReturnType<typeof setTimeout> | undefined
let timeoutId: MaybeTimeout = undefined
function frequentlyCalled() {
  clearTimeout(timeoutId)
  // Do stuff...
  timeoutId = setTimeout(() => {
    // Do some other stuff on time out
  }, 500)
}setInterval
What about setInterval?
type MaybeInterval = ReturnType<typeof setInterval> | undefined
let intervalId: MaybeInterval = undefined
onMounted(() => {
  intervalId = setInterval(() => console.log("I'm called every 3 seconds..."), 3000)
})
onBeforeUnmount(() => {
  if (intervalId) {
    clearInterval(intervalId)
  }
})Silence an error
If you need to quickly —and hopefully temporarily— silence a TypeScript error you can do so with:
// @ts-expect-error whatever reason here
const algo: anyRecursive types
Useful when defining tree-like structures.  
Seems they can only be used when defining the types of properties.
interface SomeTree {
  [x: string]: boolean | SomeTree
}Functions
What about a function that can receive a function as an argument or nothing at all?
function toggleModal(hideMenu: (() => void) | void)Misc
Infer the type of array elements
You might not need this if you are doing proper typing.  
But, sometimes it happens that you might need to.  
Read on to find out how to do it:
const someArray = [{a: 1}, {a: 2}, {a: 3}]
function algo(o: (typeof someArray)[number]) {
  // Here `o` is of type { a: number }
  // even though we didn't type it explicitly
  // ...
}If you create an ad-hoc data structures like this:
import Type1Icon from '@/components/icons/Type1Icon.vue'
import Type2Icon from '@/components/icons/Type2Icon.vue'
import Type3Icon from '@/components/icons/Type3Icon.vue'
const menuItems = [
  {
    id: 'item1',
    label: 'Label 1',
    description: 'Desc 1',
    icon: Type1Icon
  },
  {
    id: 'item2',
    label: 'Label 2',
    description: 'Desc 2',
    icon: Type2Icon
  },
  {
    id: 'item3',
    label: 'Label 3',
    description: 'Desc 3',
    icon: Type3Icon
  }
]Later, you can infer the type of the array elements like this:
const currentUserItem = computed(() => {
  return find(menuItems, { id: route.params.menuItemId }) as (typeof menuItems)[number] | undefined
})