Learn TypeScript With Me in 14 Days Day 12

Posted on 12 October 2024 Reading time: 9 min read
Learn TypeScript With Me in 14 Days Day 12

Day 12: TypeScript with Vue.js

In this part of the series, we will focus on integrating TypeScript with Vue.js, one of the most popular frameworks for building dynamic user interfaces. TypeScript adds static typing and better tooling to Vue, improving code maintainability and preventing runtime errors. By the end of this post, we will have learnt how to set up and use TypeScript with Vue 3, and how it enhances the development experience for our Task Manager project.

Why Use TypeScript with Vue.js?

Vue.js, especially in its third version, fully supports TypeScript out of the box. By integrating TypeScript, we get the following advantages:

  1. Type Safety: TypeScript helps catch errors during development by ensuring that variables, props, and components adhere to the correct types.
  2. Better Developer Experience: With TypeScript, we get better autocompletion, documentation, and error checking, which speeds up development.
  3. Maintainability: Well-typed code is easier to understand and maintain, especially in larger applications where multiple components interact.

Step 1: Setting Up TypeScript with Vue.js

Vue 3 makes TypeScript integration seamless. Follow these steps to create a Vue project with TypeScript support.

Install the Vue CLI

npm install -g @vue/cli

Creating a New Vue App with TypeScript:

To set up a new Vue project with TypeScript, you can use Vue CLI:

vue create task-manager-vue

Select “Manually select features”. For the purpose of this walkthrough, we will select Babel and TypeScript and select Vue version 3.

Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS
? Choose a version of Vue.js that you want to start the project with 3.x
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)?
 Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

After the setup completes, you should see the following output:

Successfully created project task-manager-vue-app.
👉  Get started with the following commands:

 $ cd task-manager-vue-app
 $ npm run serve

Project Structure Overview After Creating a Vue App

After you run vue create task-manager-vue with TypeScript selected, the following structure is generated:

bash

task-manager-vue/

├── node_modules/        # Installed dependencies
├── public/              # Public assets (index.html, etc.)
│   └── index.html       # The main HTML file for your app

├── src/                 # Your source code lives here
│   ├── assets/          # Static assets like images and icons
│   ├── components/      # Vue components you will create
│   │   └── HelloWorld.vue  # Example component
│   ├── App.vue          # Root component of the app
│   ├── main.ts          # Entry point of the application
│   └── shims-vue.d.ts   # TypeScript declaration for Vue files

├── tests/               # Unit or end-to-end tests (if selected during setup)
├── .gitignore           # Files and folders to be ignored by git
├── package.json         # Project metadata and dependencies
├── tsconfig.json        # TypeScript configuration
├── babel.config.js      # Babel configuration (if needed)
└── README.md            # Project documentation

Key Files and Folders

1. public/index.html:

  • This file serves as the main HTML entry point for our application.
  • Vue CLI injects the compiled app into this file during the build process.

2. src/main.ts:

  • This is the entry point of our Vue app. It initializes the Vue instance and mounts our app to a DOM element.
  • Since we’re using TypeScript, the file extension is .ts instead of .js.

Here’s an example of what it might look like:

import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

3. src/App.vue:

  • This is the root component of our application. It serves as the base for all other components in your app.
  • Typically, this component contains a for handling routing if we are using Vue Router but in this case we are not using it.
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from './components/HelloWorld.vue';

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld,
  },
});
</script>

<style>
#app {
  text-align: center;
  color: #2c3e50;
}
</style>

**4. **src/components/****:

  • This folder holds all of our custom Vue components.
  • By default, Vue CLI generates an example component called HelloWorld.vue. You’ll create additional components here as your project grows.

5. src/shims-vue.d.ts:

  • This file helps TypeScript understand .vue files, which contain both HTML and JavaScript/TypeScript code.
  • It’s crucial for enabling TypeScript’s type checking inside Vue’s single-file components (SFCs).
declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

6. tsconfig.json:

  • This file configures how TypeScript is used in our project. It specifies options such as module resolution, strict mode, and type checking.
  • You can customize the settings here to adjust TypeScript’s behavior in your project.

Running the Project

Once your Vue app is created, you can start the development server by running:

npm run serve

This will compile your application and start a local development server at http://localhost:8080/.

Summary of the Project Structure

  • public/: Contains the static assets and the main index.html file.
  • src/: The source directory that holds all your components, assets, and the entry point (main.ts).
  • App.vue: The root component that serves as the foundation of your app.
  • components/: Contains reusable Vue components, like the example HelloWorld.vue.
  • shims-vue.d.ts: Ensures that TypeScript understands .vue files.
  • tsconfig.json: Configures TypeScript’s behavior within the project.

This structure provides a clean and organized starting point for building our Vue.js app with TypeScript.

Writing Vue Components with TypeScript

Let’s take a look at how to write Vue components using TypeScript. We’ll start with a basic component example and then explore more advanced features.

Basic Vue Component with TypeScript:

<template>
  <div>
    <h3>{{ title }}</h3>
    <p>{{ completed ? 'Completed' : 'Not Completed' }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    title: {
      type: String,
      required: true
    },
    completed: {
      type: Boolean,
      required: true
    }
  }
});
</script>

Explanation:

  • lang=“ts” : This directive tells Vue to treat the script block as TypeScript.
  • Typed Props: We use TypeScript to ensure that the title is a string and completed is a boolean.

Using the Composition API with TypeScript

Vue 3 introduced the Composition API, which makes working with TypeScript even more flexible and powerful. Let’s see how to handle reactive data using the Composition API and TypeScript.

Example of a Task Component with Composition API:

<template>
  <div>
    <h3>{{ title }}</h3>
    <p>{{ isCompleted ? 'Completed' : 'Not Completed' }}</p>
    <button @click="toggleCompletion">Toggle Completion</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  props: {
    title: String,
    completed: Boolean
  },
  setup(props) {
    const isCompleted = ref(props.completed);

    const toggleCompletion = () => {
      isCompleted.value = !isCompleted.value;
    };

    return {
      isCompleted,
      toggleCompletion
    };
  }
});
</script>

Explanation:

  • ref(props.completed): We define isCompleted as a reactive reference using the ref function. TypeScript infers the type from the prop, ensuring that it’s always a boolean.
  • Composition API: The setup function allows you to manage component logic, making it easier to work with TypeScript in Vue.

Applying TypeScript to the Task Manager

Now that you’re familiar with how TypeScript works with Vue.js, let’s apply these concepts to the Task Manager project.

Task Manager with TypeScript and Composition API:

// Task.vue
<template>
      <div>
    <h2>Task Manager</h2>
    <input v-model="newTaskTitle" placeholder="Enter task title" />
    <button @click="addTask(newTaskTitle)">Add Task</button>
    <p v-if="errorMessage" class="error">{{ errorMessage }}</p>
    <ul>
      <li v-for="task in tasks" :key="task.id">
        {{ task.title }} - {{ task.completed ? 'Completed' : 'Not Completed' }}
        <button @click="toggleCompletion(task.id)">Toggle Completion</button>
      </li>
    </ul>
  </div>
  </template>

  <script lang="ts">
  import { defineComponent, ref } from 'vue';
  
  interface Task {
    id: number;
    title: string;
    completed: boolean;
  }
  
  export default defineComponent({
  setup() {
    const tasks = ref<Task[]>([]);
    const newTaskTitle = ref<string>('');
    const errorMessage = ref<string>('');

    // Add a task only if the title is unique
    const addTask = (title: string) => {
      if (title.trim() === '') {
        errorMessage.value = 'Task title cannot be empty!';
        return;
      }

      const isDuplicate = tasks.value.some(task => task.title.toLowerCase() === title.toLowerCase());

      if (isDuplicate) {
        errorMessage.value = `Task with the title "${title}" already exists!`;
        return;
      }

      errorMessage.value = '';  // Clear any previous error messages

      tasks.value.push({
        id: tasks.value.length + 1,
        title: title.trim(),
        completed: false
      });

      newTaskTitle.value = '';  // Clear the input field after adding the task
    };

    const toggleCompletion = (id: number) => {
      const task = tasks.value.find(task => task.id === id);
      if (task) {
        task.completed = !task.completed;
      }
    };

    return {
      tasks,
      newTaskTitle,
      errorMessage,
      addTask,
      toggleCompletion
    };
  }
});
</script>

<style>
.error {
  color: red;
}
</style>

Explanation:

  • Typed State with ref: The tasks array is defined with a TypeScript interface, ensuring that each task has the expected shape (id, title, and completed).
  • Reactivity: tasks is reactive, meaning that any change to the tasks array will automatically update the DOM.
  • Function Types: Both the addTask and toggleCompletion functions are explicitly typed, ensuring that the data remains consistent.

Best Practices When Using TypeScript with Vue.js

Here are some best practices for using TypeScript in your Vue.js project:

1. Define Clear Interfaces for Props and State:

Always define clear interfaces for your components’ props, state, and any other data. This improves readability and ensures that your components behave as expected.

2. Use the Composition API for Complex Components:

The Composition API works well with TypeScript, especially for larger components where you have multiple reactive states or methods. It provides better type inference and organization.

3. Use TypeScript’s Utility Types:

Leverage TypeScript’s utility types, such as Partial, Omit, and Pick, to handle complex data structures efficiently.

4. Enable TypeScript’s Strict Mode:

For better type safety, enable TypeScript’s strict mode by adding this to your tsconfig.json:

{
  "compilerOptions": {
    "strict": true
  }
}

5. Type Assertion Sparingly:

Although TypeScript allows you to cast or assert types, avoid using type assertions (as) unless absolutely necessary. Instead, rely on TypeScript’s type inference wherever possible.

What’s Next?

Today, we explored how to integrate TypeScript with Vue.js, learned how TypeScript improves your development experience in Vue, and applied these techniques to your Task Manager project. Tomorrow, we’ll dive into advanced TypeScript with Asynchronous Code, exploring how TypeScript handles promises, async/await, and error handling to ensure type safety in asynchronous workflows.

Resources for Further Reading

TypeScript Handbook

Vue 3 TypeScript Support Guide