An introduction to Vue 3 and Typescript - Getting started
Tags: vue, typescript
Published: 2022-04-12 12:37:49

In March 2022, I built my first serious project with Vue 3 + Typescript. And I was flabbergasted - these two play together delightfully. It felt like there are **worlds** regarding developer experience between Vue 2 and Vue 3.

Among the benefits I noticed were:

*   Excellent VSCode code completion
*   Clever import suggestions for components and composable functions
*   Convenient code abstractions

This article series is for you if you're:

*   looking to make your Vue apps more robust
*   are curious about how Vue 3 differs from previous versions
*   use Typescript in your Vue apps

Already hooked? Great. Let's jump in!

Setting up the app with Vite
----------------------------

Vite allows for quick boilerplating. Open a terminal in a project folder of your choice. Then, type:

```bash
yarn create vite

# for NPM
npm create vite@latest
```

1.  Select **vue** as your framework

![](https://blog.q-bit.me/content/images/2022/03/image-21.png)

2\. Choose **vue-ts** as your project variant

![](https://blog.q-bit.me/content/images/2022/03/image-22.png)

3\. Run `yarn` & `yarn dev` to start the development server

![](https://blog.q-bit.me/content/images/2022/03/image-23.png)

The project's file structure
----------------------------

We receive the following boilerplate. There are at least two files that differ from a Javascript boilerplate. Let's have a look at them.

![](https://blog.q-bit.me/content/images/2022/03/image-24.png)

### The `env.d.ts` file

Despite its tiny size, this file is a powerhouse. It globally maps the `DefineComponent` type to all `.vue` files. That means Typescript Intellisense for all of your Vue components!

```js
/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}
```

### The `tsconfig.json` file

Holds configuration of how the TS compiler interacts with your project. You don't need to make any changes here. Check out [the official docs](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) to learn more.

```js
{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

The HelloWorld.vue file
-----------------------

Let's have a look at the boilerplate component. If you haven't worked with Vue 3 yet - this template uses [Vue's Composition API](https://vuejs.org/api/composition-api-setup.html). I'll cover the details in another article. First, let's try to understand what's going on by comparing three different component setups.

*   The common `options` API approach with a single, default component export
*   The common `composition` API approach with a setup method
*   The newer `composition` API approach with a setup 'attribute'

### HelloWorld - Typescript + Options API

In a traditional Vue app, you declare a single default export. This is your component. It provides data and logic to your HTML template.

```js
<script lang="ts">
export default {
  data: () => ({
    count: 0,
  }),
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  methods: {
    increment() {
      this.count++;
    },
  }
};
</script>

<template>
  <h1>{{ msg }}</h1>

  <button type="button" @click="increment">count is: {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>
```

While this is a perfectly valid approach, try and imagine you have a single Vue file that exceeds one thousand lines of code. It will have all its states aggregated within a single object. Methods or computed properties are likely located further below. Jumping back and forth between state and logic quickly gets tedious.

### HelloWorld - Typescript + Setup method

Behold the setup method. It essentially allows you to decouple data and logic and is the gateway to Vue's composition API.

It comes with a significant difference from the options API: The setup method is evaluated **before** the component is created. As a consequence, inside of `setup`, you have no access to the Vue instance using `this`.

> Instead of using this.(...), you can access props and the component's context by two arguments of the setup function

The following code is the equivalent to the Options - API approach above.

*   It uses `ref` to make the variable `count` reactive
*   It uses setup to provide `count` and `increment` to the template

```js
<script lang="ts">
import { ref } from 'vue';

export default {
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup(props, { attrs, emit, slots }) {
    const count = ref(0);
    const increment = () => count.value++;
    return { count, increment };
  },
};
</script>

<template>
  <h1>{{ msg }}</h1>

  <button type="button" @click="increment">count is: {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>
```

Instead of having dedicated `data` and `method` sections, I declared both inside the setup method.

### HelloWorld - Typescript + Setup syntactic sugar

Till now, we didn't really make a lot of Typescript usage. Even the props are declared using Object syntax. It's time to change this.

Before Vue 3's beta phase ended, [this RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md) provided syntactic sugar for the composition API. It also paved the way for the modern Vue+TS syntax I found so powerful.

> With `<script setup>...</script>`\> There is no need to return any components, variables or functions to the template. They're simply available to the template.

Let's have a look at what this means in the code.

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

defineProps<{ msg: string }>();

const count = ref(0);
const increment = () => count.value++;
</script>

<template>
  <h1>{{ msg }}</h1>

  <button type="button" @click="increment">count is: {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>
```

**That's just four lines of Typescript!** As a bonus, types are automatically propagated into the parent component.

Try and assign the `msg` property in the parent component.

![](https://blog.q-bit.me/content/images/2022/03/image-25.png)

### Where's the rest?

What happened to props, emits, attributes and slots?

*   Props and emits were turned into `compiler macros`, one of which is `defineProps`
*   Default props can be declared using `withDefaults`
*   Attributes and slots must be imported separately.  
    They're still available in the template using `$attrs` & `$slots`

I'll describe these in a separate article in detail. If you're after a quick overview, have a look here:

```js
<script setup lang="ts">
import { ref, withDefaults, useSlots, useAttrs } from 'vue';

const props = withDefaults(
  defineProps<{
    msg: string;
  }>(),
  { msg: 'Hello World!' }
);

const emit = defineEmits<{
  (event: 'click', count: number): void;
}>();

const slots = useSlots();
const attributes = useAttrs()

const count = ref(0);
const increment = () => count.value++;
</script>

<template>
  <h1>{{ msg }}</h1>

  <button type="button" @click="increment">count is: {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>
```

Wrapup
------

So far we have:

*   Created a Vue 3 - Typescript project
*   Got a brief overview of TS - project-specific files
*   Covered the difference between Vue's options and composition API
*   Outlined how Typescript Intellisense helps us during development

In the follow-up articles, we'll dive even deeper into these topics and explore what else Vue's latest version has up its sleeves for us.