Getting Started with Vue.js 3: A Beginner's Guide

Anton Ioffe - December 21st 2023 - 9 minutes read

Embark on a journey through the dynamic landscape of Vue.js 3, where we'll navigate the intricacies of crafting your inaugural application from scratch to a refined digital masterpiece. This deep dive is tailored for seasoned developers seeking to harness the full potential of Vue's reactivity paradigms, the Composition API's organizational prowess, and the sophisticated communication channels within components. Prepare to leverage Single-File Components for seamlessly streamlined development, and to acquaint yourself with advanced tooling strategies that will propel your projects toward excellence. With practical examples and expert insights, each section of this guide is meticulously designed to elevate your Vue 3 expertise and inspire innovation in your web development endeavors.

Structuring Your First Vue 3 Application

When embarking on the journey of structuring your first Vue 3 application, initiating the process begins with the command vue create your-project-name. It is crucial to select the Vue 3 preset during the CLI prompts to ensure that you are working with the latest features and optimizations that Vue 3 offers. After making this selection and opting for "In dedicated config files" for configurations such as Babel, the CLI scaffolds a project for you. Remember to forgo saving these configurations as presets for future projects unless you are certain they will be reused.

The resulting structure of your Vue 3 application provides a robust starting point. The src directory is the heart of your application, containing the main.js file—responsible for initializing your Vue app and global libraries or components—and an App.vue root component which acts as the entry point of your UI. The components directory within src is designed for housing Vue components, fostering modularity and encouraging the reuse of UI elements. It is also advisable to familiarize yourself with the public directory for static assets and the root-level configuration files, as understanding their purpose is fundamental to managing dependencies and build processes.

Customization of the boilerplate code is an inevitable next step that adapts the scaffolded project to the needs of your unique application. This includes purging the default HelloWorld.vue component from src/components, wiping any references from App.vue, and removing template elements, such as the Vue logo image, that do not fit with your application's objectives. Within App.vue, your primary focus should be on crafting a minimalistic template that can serve as a canvas for your application's components.

The main.js file warrants special attention as it represents the entry point for Vue to mount the application. In a typical setup, you will import the createApp function from vue, the App.vue component, and apply global configurations, such as Vue plugins or additional global components. This file can be adjusted to set up global mixins, directive or provide/inject mechanisms that might be necessary for your application context.

Lastly, understanding the role of the components directory in your Vue application's architecture is fundamental. It's within this directory that you'll create and manage your Vue components, breaking down the user interface into manageable, reusable pieces. Efficient use of this directory involves establishing a naming convention that enhances readability and maintainability, often preferring clear and descriptive names over abbreviations or cryptic identifiers. As your application grows, you may find it beneficial to further organize this directory into subfolders that reflect different aspects of functionality or application domains.

Understanding and Implementing Reactivity in Vue 3

Vue 3's reactivity is a core feature that empowers developers to create dynamic user interfaces with ease. At the foundation of this reactivity system lie the ref and reactive functions from the Vue package. The ref function is straightforward: it takes a value and returns a reactive and mutable object that contains a property .value pointing to the underlying value. This simple mechanism is perfect for primitive values and can be used to ensure that any change to this .value property will trigger updates in the UI.

const count = ref(0);

function increment() {
    count.value++;
}

With count as a reactive ref, any component that references count.value will rerender when increment() is called, ensuring a seamless update cycle. Since ref wraps the value in an object, it's better suited for primitives to avoid unnecessary complications with destructuring or spreading operations.

On the other hand, the reactive function works with objects or arrays, returning a deeply reactive clone of the original. This is where we see Vue's reactivity shine in handling more complex state management scenarios. A reactive object can have multiple properties, and any mutations to its properties or nested properties will be tracked, triggering UI updates as needed.

const state = reactive({
    count: 0,
    message: 'Hello Vue 3'
});

function increment() {
    state.count++;
}

Here, mutations to state.count or state.message are both reactively handled, making reactive a powerful ally for more intricate component states. For array mutations or object additions, the reactivity system efficiently ensures updates without explicitly wrapping each property with ref.

When it comes to performance and memory usage, the reactive system in Vue 3 substantially benefits from JavaScript's Proxy feature, which allows for fine-grained tracking with minimal overhead. The Proxy-based reactivity provides better performance especially in cases where the data structure is complex, as it avoids the need for deep watchers that were more expensive in previous Vue versions.

In terms of best practices, it's crucial to consider the shape and complexity of the data when selecting between ref and reactive. Use ref for individual primitive values and when you need a consistent reference, such as in template refs or reactive primitives. Lean on reactive for complex state objects and when you wish to maintain reactivity across a deeply nested structure. Embracing this separation of concerns can lead to more readable and maintainable code, ensuring that components stay performant and responsive to state changes.

However, excessive use of reactive properties can introduce unnecessary reactivity and overhead, particularly if large datasets are involved. Striking the balance between reactivity and performance is key. Only make data reactive if it directly influences the view or requires being tracked for changes. Non-reactive state or ephemeral state, which doesn't influence the UI, can and should remain as simple JavaScript variables. This conserves memory and reduces the burden on Vue's reactivity system, resulting in a leaner, more efficient application.

Developing with the Composition API

The Composition API in Vue 3 ushers in a functional programming paradigm, allowing for a more logical structuring of components. Utilizing ref, developers can effortlessly manage reactive primitives that react to changes, thus streamlining template reactivity and local state management.

import { ref, computed } from 'vue';

export default {
    setup() {
        const count = ref(0);
        const doubleCount = computed(() => count.value * 2);

        function increment() {
            count.value++;
        }

        // Exposing to template
        return { count, doubleCount, increment };
    }
}

With reactive, we can create a reactive state for complex objects, ideal for collating related data and making nested objects reactive. Coupling this with computed properties, we establish autonomous logic responding to shifts in the reactive state, recalculating as necessary.

import { reactive, computed } from 'vue';

export default {
    setup() {
        const state = reactive({
            count: 0
        });

        const doubleCount = computed(() => state.count * 2);

        function increment() {
            state.count++;
        }

        return { state, doubleCount, increment };
    }
}

When leveraging the Composition API, vigilance is key to maintaining reactivity. Developers must be mindful of accessing .value when working with refs and consider the implications of mutating reactive state on other dependent observers.

import { ref, watch } from 'vue';

export default {
    setup() {
        const count = ref(0);

        watch(count, (newCount, oldCount) => {
            // Actions in response to count changes
        });

        return { count };
    }
}

The Composition API distinguishes itself by enabling a more modular and scalable code organization. By transitioning to this API, developers can construct flexible components that are easier to maintain and test, provided that they fully grasp Vue's reactivity concepts.

Embracing the Composition API can lead to a marked improvement in both modularity and testability of your Vue 3 codebase. The flexibility and clarity it affords developers solidify it as a preferred approach for those looking to fully exploit the advanced features of Vue 3.

Component Communication and Advanced Features

When building Vue 3 applications, components need efficient mechanisms for communicating with each other to share data and events. Props provide a straightforward way to pass data from parent to child components, enabling a unidirectional data flow that simplifies state management. To illustrate:

// ChildComponent.vue
<template>
  <div>
    <p>{{ item.title }}</p>
  </div>
</template>

<script>
export default {
  props: {
    item: Object
  }
}
</script>

While props serve for data "downstreaming," events allow for "upstream" communication. Child components can emit events that parents listen to, forming a responsive chain of interaction:

// ChildComponent.vue
<template>
  <button @click="onClick">Click me</button>
</template>

<script>
export default {
  methods: {
    onClick() {
      this.$emit('childClicked');
    }
  }
}
</script>

However, excessive use of props can lead to "prop drilling," where data is passed through several layers of components, resulting in complicated code. To achieve more isolated and re-usable components, Vue 3's provide/inject API can be adopted. This pattern allows an ancestor component to serve as a dependency injector for its descendants, abstracting away the layers:

// AncestorComponent.vue
<script>
import { provide } from 'vue';

export default {
  setup() {
    provide('message', 'Hello from Ancestor');
  }
}
</script>

Vue's slot system presents another powerful method for content distribution, enabling child components to compose layouts with injected content, thus increasing flexibility and reusability:

// ParentComponent.vue
<template>
  <ChildComponent>
    <template #default>
      <p>Injected content</p>
    </template>
  </ChildComponent>
</template>

In advanced scenarios, Vue's asynchronous components are a game-changer, allowing for lazy loading of components only when needed, optimizing resource usage and improving application performance. On the other hand, Vue 3's Teleport feature revolutionizes the placement of component templates, often utilized for modals or tooltips, by transporting them to different locations in the DOM:

// ModalComponent.vue
<template>
  <teleport to="body">
    <div class="modal">
      <!-- Modal content -->
    </div>
  </teleport>
</template>

However, it's critical to handle components asynchronously with care to prevent potential issues related to timing and state management.

In summary, Vue 3 gives developers an array of tools for component communication that, when used judiciously, can significantly enhance code modularity and application performance. Yet, it's essential to avoid common pitfalls, such as overusing props or neglecting proper handling of asynchronous components, which can lead to inefficiencies and bugs. As you integrate these communication techniques, consider reflecting on how you might refine data flow to prevent prop drilling, or when Teleport could simplify your DOM structure, to further hone your Vue 3 expertise.

Building Single-File Components (SFC) and Leveraging Vue 3 Tooling

Single-File Components (SFCs) in Vue 3 offer a nuanced variant of the separation of concerns, co-locating HTML, JavaScript, and CSS within a single file while maintaining their distinct conceptual boundaries. By confining all of a component's logic, presentation, and styles to one file, SFCs provide developers with an ergonomic and cohesive development experience. The template block is dedicated to the declarative UI, the script block encapsulates the component's reactivity and lifecycles, and the style block, optionally scoped, dictates visual styling.

<template>
  <div>
    <button @click="increment">Clicked {{ count }} times</button>
  </div>
</template>

<script>
  import { defineComponent, ref } from 'vue';

  export default defineComponent({
    setup() {
      const count = ref(0);
      const increment = () => {
        count.value++;
      };
      return { count, increment };
    },
  });
</script>

<style scoped>
  button {
    background-color: var(--button-bg, #42b983);
    color: var(--button-color, white);
    border: none;
    padding: 0.5rem 1rem;
    font-size: 1rem;
  }
</style>

While SFCs generally promote maintainability, caution is warranted against excessive modularity. Overpartitioning can result in a fragmented codebase where the overhead of managing numerous small components overshadows the agility typically afforded by Vue's modular architecture. Coupled with Vite's powerful features, such as Module Graph Optimizations and Hot Module Replacement, developers are equipped to navigate the trade-offs of application state handling and developer ramp-up time during rapid iteration periods.

Scoped styles within SFCs empower developers to style components independently, though global styling remains advantageous for foundational or thematically consistent visual design across an application. Leveraging CSS variables and careful planning for style scope ensures that the codebase retains its maintainability without the pitfalls of excessive specificity or accidental overrides.

<style>
  :root {
    --button-bg: #42b983;
    --button-color: white;
  }
</style>

<style scoped>
  /* Scoped button styles */
  button {
    /* Scoped styles access global CSS variables */
    background-color: var(--button-bg);
    color: var(--button-color);
  }
</style>

The integration of Vue 3's reactivity system and Vite tooling goes hand-in-hand with sound asset management in SFCs, where direct asset imports enhance coherency between resources and components. Leveraging aliases and ensuring correct asset references guard against deployment snags and facilitate efficient loading in production environments. A balance between resources and application performance remains crucial, particularly when dealing with substantially sized assets.

In building SFCs, the pursuit of clarity in component structure and purity of component responsibilities should remain paramount. Manageable methods, along with computed properties and watchers, finetune state management. Continual refactoring and subdivision of cumbersome components encourage a DRY codebase; enhancing component composability and reuse fortify an application's foundations against the throes of complexity and scale.

Summary

In this article about getting started with Vue.js 3, senior-level developers will learn about structuring a Vue 3 application, understanding and implementing reactivity, developing with the Composition API, component communication and advanced features, as well as building single-file components and leveraging Vue 3 tooling. Key takeaways include the importance of selecting the Vue 3 preset during the project setup, using the ref and reactive functions for reactivity, embracing the Composition API for modular and scalable code, and utilizing props, events, provide/inject, slots, and teleportation for component communication. The article concludes by highlighting the benefits and considerations of single-file components and the integration of Vue 3's reactivity system with Vite tooling. A challenging task for the reader is to refactor a complex Vue component into smaller, reusable components to enhance code modularity and maintainability.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers