Mentioned in this video
Frameworks and Libraries
Programming Languages
Development Tools
Key Concepts
Modern Vue.js Crash Course | with TypeScript + script setup + Composition API
Summary
Embarking on a journey with Vue.js is a truly rewarding experience for any developer looking to build dynamic and responsive front-end applications. The framework consistently earns praise for its approachable syntax and well-structured ecosystem, making it a joy to work with, whether you are a seasoned developer or just starting your adventure in web development. In a recent demonstration by CJ, a comprehensive crash course illuminated the core principles of building an interactive task management application using Vue.js 3, TypeScript, and the powerful Composition API.
Charting the Course: The Vue.js Task Manager
The central aim of this tutorial is to construct a fully functional task manager. This application showcases a wide array of fundamental Vue.js features, including the dynamic addition and removal of tasks, marking tasks as complete, and filtering tasks by their status. Beyond core functionality, the tutorial delves into elegant UI enhancements such as subtle animations for task transitions and conditional rendering for an intuitive user experience. The robust nature of Vue.js allows for breaking down the application into manageable, reusable components, facilitating data flow between them, and ensuring a maintainable codebase. This practical approach demystifies complex interactions, proving Vue.js's capability to make front-end development both easy and enjoyable.
Foundations for Success: Essential Prerequisites
Before diving into the intricacies of Vue.js, a solid grasp of foundational web technologies is essential. Developers should be comfortable with HTML for structuring content, CSS for styling, and JavaScript for fundamental programming logic. An understanding of modern JavaScript features, particularly ES6+, will greatly assist in comprehending Vue's reactivity system and script setup syntax. Given the project's use of TypeScript, familiarity with basic TypeScript concepts such as types, interfaces, and generics will also be beneficial for leveraging Vue's excellent type inference and ensuring robust code quality.
The Developer's Toolkit: Key Libraries and Tools
The Vue.js ecosystem is rich with tools designed to streamline development, and this tutorial leverages several key players:
- Vue.js: The progressive JavaScript framework itself, forming the bedrock of the application. It provides the core reactivity system, component model, and directives. Vue 3, with its Composition API and
script setupsyntax, significantly simplifies component logic and state management. - Vite: A next-generation frontend tooling that provides an extremely fast development server and build tool. Vite is praised for its quick cold start times and instant hot module replacement (HMR), greatly accelerating the development feedback loop.
- Nuxt.js: Although not directly used in the task manager application, Nuxt.js is mentioned as Vue's equivalent to React's Next.js. It is a powerful meta-framework for building server-rendered Vue applications, offering features like file-based routing, data loading, and API routes.
- Vue Router: The official routing library for Vue.js, providing robust, configurable routing for single-page applications. In the Vue ecosystem, its prevalence means developers rarely debate alternative routing solutions.
- Pinia: The official state management library for Vue.js. Pinia is lightweight, type-safe, and offers an intuitive API for managing global application state, serving as a streamlined alternative to Vuex.
- Pico CSS: A minimalist, classless CSS framework. Pico CSS simplifies styling by providing sensible defaults for standard HTML elements, allowing developers to focus on functionality without extensive CSS boilerplate. It is ideal for rapid prototyping or simple demonstrations.
- Vue Dev Tools: An indispensable browser extension for debugging Vue.js applications. It offers insights into component hierarchies, reactivity, events, and state, enabling developers to inspect and even modify component data in real-time.
- TypeScript: A superset of JavaScript that adds static typing. TypeScript significantly enhances code maintainability, readability, and helps catch errors early in the development cycle, particularly in larger applications.
Constructing the Application: A Code Walkthrough
Let us begin by dissecting the construction of our Vue.js task management application, piece by piece.
Initializing the Project with Vite and TypeScript
Starting a new Vue.js project is effortless with Vite. The process involves a few simple commands, setting up a minimal yet powerful development environment ready for TypeScript.
pnpm create vite@latest tasks-app
When prompted, one selects Vue as the framework and TypeScript as the variant. After navigating into the tasks-app directory and installing dependencies with pnpm install, the project is ready to open in a code editor like VS Code.
Setting the Stage: index.html and Base Styles
The index.html file serves as the entry point, containing the root div where the Vue application will mount. CJ demonstrates enhancing this file by updating the title and linking Pico CSS for quick, default styling, avoiding the need for extensive custom CSS.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Tasks App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
The Core Component: App.vue Structure and Scoped Styles
Vue applications are built using Single File Components (SFCs), typically .vue files, which encapsulate HTML (<template>), JavaScript (<script>), and CSS (<style>) for a specific part of the UI. CJ cleans up the default App.vue to create a simple Hello World example, introducing a main element to act as a container with centered styling. The scoped attribute on the <style> tag is highlighted, explaining how it ensures that component-specific styles do not "leak" out and affect other components, promoting modularity.
<script setup lang="ts"></script>
<template>
<main>
<h1>Hello World</h1>
</main>
</template>
<style scoped>
/* Styles defined here only apply to this component */
main {
max-width: 800px;
margin: 1rem auto;
}
</style>
Introducing Reactive State with ref
Vue's reactivity system is a cornerstone of its appeal. ref is a fundamental primitive for defining reactive state variables. CJ demonstrates replacing the hardcoded text with a message ref and interpolating its value into the template using {{ message.value }} (though in templates, .value is often automatically unwrapped).
<script setup lang="ts">
import { ref } from 'vue';
const message = ref('Tasks App');
</script>
<template>
<main>
<h1>{{ message }}</h1>
</main>
</template>
Observing this with Vue Dev Tools reveals how easy it is to inspect and even modify the message state directly, with changes instantly reflected in the browser.
Building the Task Form: v-model and DOM Events
The next step involves creating a form for adding new tasks. The v-model directive is introduced for two-way data binding, seamlessly connecting an input element's value to a reactive state variable (newTask). Form submission is handled using the @submit directive combined with the .prevent modifier, which automatically calls event.preventDefault() to stop the browser's default form submission behavior.
<script setup lang="ts">
import { ref } from 'vue';
const newTask = ref('');
function formSubmitted() {
console.log(newTask.value);
// Logic to add task
}
</script>
<template>
<form @submit.prevent="formSubmitted">
<label for="new-task">New Task</label>
<input type="text" id="new-task" name="new-task" v-model="newTask" />
<div class="button-container">
<button type="submit">Add</button>
</div>
</form>
</template>
<style>
/* Global styles for button container */
.button-container {
display: flex;
justify-content: flex-end;
}
</style>
Componentization: Extracting TaskForm.vue
To promote modularity and reusability, the task input form is extracted into its own TaskForm.vue component. This involves moving the template, script, and associated styles into a new file. The App.vue then imports and renders this new component.
<!-- src/components/TaskForm.vue -->
<script setup lang="ts">
import { ref } from 'vue';
const newTask = ref('');
const error = ref('');
// Placeholder for emit function
const emit = defineEmits<{ (e: 'addTask', task: string): void }>();
function formSubmitted() {
if (!newTask.value.trim()) {
error.value = 'Task cannot be empty';
return;
}
emit('addTask', newTask.value.trim());
newTask.value = '';
error.value = '';
}
function clearError() {
error.value = '';
}
</script>
<template>
<form @submit.prevent="formSubmitted">
<label for="new-task">New Task</label>
<input
type="text"
id="new-task"
name="new-task"
v-model="newTask"
:aria-invalid="!!error"
@input="clearError"
/>
<small class="invalid-helper" v-if="error">{{ error }}</small>
<div class="button-container">
<button type="submit">Add</button>
</div>
</form>
</template>
<style scoped>
/* Scoped styles specific to TaskForm */
.button-container {
display: flex;
justify-content: flex-end;
}
</style>
Inter-Component Communication: defineEmits
For the TaskForm (child) to communicate new task data back to App.vue (parent), the defineEmits compiler macro is used. This explicitly declares the events a component can emit, along with their payloads, enabling type-safe event handling. The parent component then listens for these custom events using the @ syntax, just like native DOM events.
<!-- src/App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import TaskForm from './components/TaskForm.vue';
import type { Task } from './types'; // Assuming types.ts defines Task interface
const message = ref('Tasks App');
const tasks = ref<Task[]>([]);
function addTask(newTaskTitle: string) {
tasks.value.push({
id: crypto.randomUUID(),
title: newTaskTitle,
done: false,
});
}
</script>
<template>
<main>
<h1>{{ message }}</h1>
<TaskForm @add-task="addTask" />
<!-- Task list will go here -->
</main>
</template>
Dynamic Task Management: Reactive Arrays and Validation
The tasks array, defined as a ref of Task[], stores the application's core data. New tasks are added directly to tasks.value array. Vue's reactivity system automatically detects changes to the array and triggers UI updates. Basic form validation is also added to prevent empty tasks, using v-if for conditional display of an error message and :aria-invalid for dynamic styling of the input field. The clearError function, triggered by the @input event, ensures the error message disappears once the user starts typing again.
Rendering Lists with v-for and key
To display the list of tasks, the v-for directive iterates over the tasks array. Each item is rendered as an <article> element, providing a distinct card-like appearance with Pico CSS. Crucially, a :key binding (:key="task.id") is included. This key attribute is vital for Vue's efficient virtual DOM reconciliation, allowing it to track individual nodes in a list, especially during additions, removals, or reordering.
<!-- src/App.vue (or TaskList.vue after refactoring) -->
<template>
<div class="task-list">
<article v-for="task in filteredTasks" :key="task.id" class="task">
<!-- Task content goes here -->
</article>
</div>
</template>
Prop-Driven Components: TaskList.vue and defineProps
Similar to the TaskForm, the task list display is refactored into a TaskList.vue component. Data, specifically the tasks array, is passed down from the parent (App.vue) to the child (TaskList.vue) using props. The defineProps compiler macro declares the expected props, providing type safety and clear component interfaces. A shorthand (:tasks) is demonstrated when the prop name matches the variable name.
<!-- src/components/TaskList.vue -->
<script setup lang="ts">
import type { Task } from '../types';
const props = defineProps<{ tasks: Task[] }>();
// Emits for toggle done and remove task events
const emit = defineEmits<{ (e: 'toggleDone', id: string): void; (e: 'removeTask', id: string): void }>();
function handleToggleDone(id: string) {
emit('toggleDone', id);
}
function handleRemoveTask(id: string) {
emit('removeTask', id);
}
</script>
<template>
<TransitionGroup name="task-list" tag="div" class="task-list">
<article v-for="task in props.tasks" :key="task.id" class="task">
<label>
<input type="checkbox" :checked="task.done" @change="handleToggleDone(task.id)" />
<span :class="{ done: task.done }">{{ task.title }}</span>
</label>
<button class="outline" @click="handleRemoveTask(task.id)">Remove</button>
</article>
</TransitionGroup>
</template>
<style scoped>
.task-list {
margin-top: 1rem;
}
.task {
display: flex;
justify-content: space-between;
align-items: center;
}
.done {
text-decoration: line-through;
}
/* Transition styles */
.task-list-enter-active, .task-list-leave-active {
transition: all 0.5s ease;
}
.task-list-enter-from, .task-list-leave-to {
opacity: 0;
transform: translateX(300px);
}
.task-list-move {
transition: transform 0.8s ease;
}
</style>
Elegant Conditional Rendering with v-if and v-else
Vue offers intuitive directives for conditional rendering. The v-if directive conditionally renders an element based on a truthy or falsy expression. The v-else directive, used immediately after v-if or v-else-if, provides an alternative content block when the preceding condition is false. This is demonstrated for displaying either a
Mentioned in this video
Frameworks and Libraries
Programming Languages
Development Tools
Key Concepts