DEV Community

Cover image for Vue 3 TypeScript code snippets and tips
Giannis Koutsaftakis
Giannis Koutsaftakis

Posted on • Updated on

Vue 3 TypeScript code snippets and tips

In this article, I've curated a collection of useful code snippets and tips that I frequently find myself searching for in the Vue docs or digging up from my existing codebase during development. Having all these snippets in one place should make it easier to reference and use them as quick copy-paste examples, ultimately saving valuable time when developing with Vue 3 and TypeScript.

Let's dive in!

Define a component

Here's an example of a component definition using the <script setup> syntax. The defineProps and defineEmits macros are used to declare the component's props and emitted events, respectively. The withDefaults helper allows us to set default values for the props. We can define this as a custom code snippet in our code editor to serve as a starting point when we're creating a new component.

<template>
  <div class="mb-4 flex items-center rounded bg-blue-50 p-4 text-blue-800">
    <div class="text-sm font-medium">
      {{ text }}
    </div>
    <button
      v-if="closeable"
      type="button"
      class="-m-1.5 ms-auto inline-flex size-8 items-center justify-center rounded p-1.5 hover:bg-blue-100"
      @click="close"
    ></button>
  </div>
</template>

<script setup lang="ts">
defineOptions({
  name: 'SimpleComponent'
})

type Props = {
  text?: string
  closeable?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  text: '',
  closeable: false
})

const emit = defineEmits<{
  'click:close': [value: string]
}>()

const close = () => {
  emit('click:close', props.text)
}
</script>
Enter fullscreen mode Exit fullscreen mode

Props validation

To validate props, we have to use the defineProps macro along with the PropType utility. This allows us to specify the type and a validator function that checks the prop's value. Here, the text prop is validated to ensure it starts with the letter 'T'.

import { type PropType } from 'vue'

defineProps({
  text: {
    type: String as PropType<string>,
    default: 'Test',
    validator(value: string) {
      return value.startsWith('T')
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Define a template ref

Sometimes, we need a reference to a DOM element within our template. We can achieve this using ref.

<template>
  <div ref="header" class="text-4xl">Header text</div>
</template>

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

const header = ref<HTMLElement | null>(null)
</script>
Enter fullscreen mode Exit fullscreen mode

Define a component ref (get the type of a component)

Similarly, like DOM element refs, there are times when we require a reference to a component within our template. For that, we can use the InstanceType utility along with typeof. This ensures that our component references are correctly typed.

<template>
  <SimpleComponent ref="simpleComponentRef" :text="'Hello world!'" />
</template>

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

type SimpleComponentType = InstanceType<typeof SimpleComponent>

const simpleComponentRef = ref<SimpleComponentType | null>(null)
</script>
Enter fullscreen mode Exit fullscreen mode

In cases where the exact type of the component isn't available or isn't important, we can use the generic ComponentPublicInstance that Vue exposes for us, i.e:

import { type ComponentPublicInstance } from 'vue'

const simpleComponentRef = ref<ComponentPublicInstance | null>(null)
Enter fullscreen mode Exit fullscreen mode

Generic component type

In scenarios where we need to store and render components dynamically, we can use the generic Component type. Such a scenario would be for example to store components in an object or an array so that we can render them dynamically in the template. By using shallowRef, we ensure that Vue doesn't complain with [Vue warn]: Vue received a Component that was made a reactive object..

<template>
  <Component :is="components.settings" />
</template>

<script setup lang="ts">
import { type Component, shallowRef } from 'vue'
import IconSettings from './IconSettings'
import IconHome from './IconHome'

const components = ref<Record<string, Component>>({})

// Later in our code
components.value.settings= IconSettings
components.value.home = IconHome
</script>
Enter fullscreen mode Exit fullscreen mode

We can also use shallowRef for a single component reference.

const settings = shallowRef(IconSettings)
Enter fullscreen mode Exit fullscreen mode

Easy wrapper components with defineModel

I've previously written about how to create an input wrapper in Vue 3 by binding events manually. However, starting from Vue 3.4, there's now a simpler way to achieve this using defineModel.

<template>
  <div>
    <label class="block">
      {{ label }}
    </label>
    <input v-model="model" type="text" class="rounded border p-2" />
  </div>
</template>

<script setup lang="ts">
defineOptions({
  name: 'InputWrapper'
})

withDefaults(defineProps<{ label?: string }>(), {
  label: ''
})

const model = defineModel<string>({ default: '' })
</script>
Enter fullscreen mode Exit fullscreen mode

Working with disabled attribute inheritance

In Vue 3, event listeners, as well as style and class, are now part of all attributes. When we're working with inheritAttrs: false, we often want to distribute different attributes across various parts of our components. In the following examples, we'll see how we can manage this.

Get listeners like in Vue 2

We can still extract event listeners specifically by using useAttrs and a computed property.

<template>
  <div>
    <button type="button" v-bind="listeners"></button>
  </div>
</template>

<script setup lang="ts">
import { computed, useAttrs } from 'vue'

defineOptions({
  inheritAttrs: false
})

const allAttrs = useAttrs()

const listeners = computed(() => {
  return Object.keys(allAttrs).reduce((acc, curr) => {
    if (curr.startsWith('on')) {
      return { ...acc, [curr]: allAttrs[curr] }
    } else {
      return acc
    }
  }, {})
})
</script>
Enter fullscreen mode Exit fullscreen mode

Get all attrs without style and classes

Sometimes, it's useful to separate style and classes from the rest of the attributes. We can easily do this by using the spread operator to extract them from all attributes.

import { computed, useAttrs } from 'vue'

const allAttrs = useAttrs()

const attrs = computed(() => {
  const { class: classes, style, ...rest } = allAttrs

  return rest
})
Enter fullscreen mode Exit fullscreen mode

Get only style from attrs

Similarly, we can extract any part we want from all attributes. Here's an example showing how we can extract style.

import { type StyleValue, computed, useAttrs } from 'vue'

const allAttrs = useAttrs()

const style = computed(() => {
  return allAttrs.style as StyleValue
})
Enter fullscreen mode Exit fullscreen mode

That was it!
I hope these snippets and tips help streamline your development process with Vue 3 and TypeScript. If you have your own most-used snippets or find yourself frequently forgetting how to do certain things in Vue, feel free to share them in the comments.

Thanks for reading!

Top comments (1)

Collapse
 
alexanderop profile image
Alexander Opalic

Than you for the awesome blog post

Having really awesome code snippets is essential. I think every developer should define snippets for tasks they frequently perform. It helps so much while coding, especially for me, since I often can't remember the exact syntax. I also have many snippets that I use for writing tests.