Engineering/Coding Standards/TypeScript/Vue/Vue Composition Api/

Vue 3 Composition API Standards · VUE-02

The Composition API in Vue is a powerful and flexible way to write components, offering an alternative to the Options API used in Vue 2.

It provides a more granular and function-based approach to managing component logic.

Use ref over reactive for State Management · VUE-02.01 · MUST

Providing explicit reactivity control through ref improves performance with more granular reactivity. With Typescript, these provide strong type inference.

The same reactivity is exposed via reactive, but it has many limitations that ref does not. Furthermore, reactive causes maintainability issues.

Links supporting why we use ref over reactive:

Key Points 🔑

  • Vue.js recommend using ref as the primary API for declaring reactive state. In 99% of cases, you will want to use ref for reactivity.
  • reactive only takes objects, and not JS primitives (string, boolean, number, etc.)
  • ref works on any type, primitive or complex.
  • ref is calling reactive behind the scenes, so they both work with objects.
  • ref has a .value property for reassignging, reactive does not and therefore cannot be reassigned.
  • Due to the above reasons, in most cases you should use ref for reactivity.

Use ref for Reactive State · VUE-02.01.01 · MUST

Use ref when:

  • Creating a reactive state of a primitive.
  • Creating a reactive state for an object you need to later reassign (such as an array).
  • Directly binding values to the component template.

ref Example

<script setup>
import { ref } from 'vue';

// Use ref for a single reactive variable
const count = ref(0);

function increment() {
  count.value++;
}
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

Only use reactive for Reactive State of Immutable Objects · VUE-02.01.02 · MUST

Use reactive when:

  • Creating a reactive state of a complex object or array.
  • Working with an object you don’t need to reassign.

reactive Example

<script setup>
import { reactive } from 'vue';

// Use reactive for an object with multiple properties
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
});

function updateAge() {
  user.age++;
}
</script>

<template>
  <div>
    <p>{{ user.firstName }} {{ user.lastName }} - Age: {{ user.age }}</p>
    <button @click="updateAge">Increase Age</button>
  </div>
</template>

Further limitations of reactive

Using reactive has some limitations due to being an unwrapped JS Proxy.

The value type you assign to reactive cannot be reassigned, only the properties may be manipulated.

It also does not support destructuring, and any destructured properties will lose reactivity. This can lead to unmaintainable code.

Use Lifecycle Hooks to manage behaviour at different component stages · VUE-02.02 · MUST

Use onMounted() to Encapsulate Logic that Needs to Run After the Component is Added to the DOM · VUE-02.02.01 · MUST

Ensure consistent and efficient use of the onBeforeMount, onMounted, onUpdated, onErrorCaptured lifecycle hook in Vue components for executing code after the component is mounted.

onMounted() Example (✅)

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

const message = ref('');

onMounted(() => {
  message.value = 'Component is mounted!';
  initializeComponent();
});

function initializeComponent() {
  // Additional initialization logic
}
</script>

Use onBeforeMount() to Encapsulate Logic that Needs to Run Before the Component is Added to the DOM · VUE-02.02.01 · MUST

<script setup>
import { onBeforeMount, onUpdated } from 'vue';

onBeforeMount(() => {
  console.log('Component is about to be mounted.');
});

</script>

Use onBeforeUpdate() & onUpdated() to Encapsulate Logic that Needs to Run When the Component Updated on the DOM · VUE-02.02.02 · MUST

Reactive State Example

<script setup>
import { onBeforeMount, onUpdated } from 'vue';

const count = ref(0)
const val = ref(0)

onBeforeUpdate(() => {
  count.value++
  console.log('beforeUpdate')
})

onUpdated(() => {
  console.log('updated() val: ' + val.value)
})
</script>

Use onBeforeUnmount() & onUnmounted() to Encapsulate Logic that Needs to Run When the Component is Being Destroyed · VUE-02.02.03 · MUST

Cleanup Example

<script setup>
import { onBeforeUnmount, onUnmounted } from 'vue';

onBeforeUnmount(() => {
  console.log('Component is about to be unmounted.');
});

onUnmounted(() => {
  console.log('Component has been unmounted.');
});
</script>

Use onErrorCaptured() to Encapsulate Logic that Runs for Errors In Child Components · VUE-02.02.03 · MUST

Error Handling Example

<script setup>
import { onErrorCaptured } from 'vue';

onErrorCaptured((error, instance, info) => {
  console.error('Error captured:', error, info);
  return false; // Prevent further propagation
});
</script>

Use Watchers watchEffect & watch to Encapsulate Logic that Runs on Reactive Effects · VUE-02.02.03 · MUST

Watchers Example

<script setup>
import { ref, watch, watchEffect } from 'vue';

const count = ref(0);

watch(count, (newVal) => {
  console.log('Count changed:', newVal);
});

watchEffect(() => {
  console.log('Count is:', count.value);
});
</script>

Use Slots for Flexible Component Composition · VUE-02.3 · MUST

Slots provide a way to compose components and pass content into them. They allow for flexible and reusable component design by letting parent components pass content into child components.

Slots in Vue are a way to pass content from a parent component into a child component. They allow you to define placeholders in a component’s template where dynamic content can be inserted.

This makes components highly flexible and reusable, as they enable injection of customizable content based on their usage context.

Use Default Slots for Singular Data Transmission · VUE-02.3.1 · MUST

Use default slots to pass content into a component when no specific slot name is provided.

Default Slots Example (✅)

Default Slots ParentComponent.vue
<template>
  <ChildComponent>
    <p>This is default slot content.</p>
  </ChildComponent>
</template>
Default Slots ChildComponent.vue
<template>
  <div>
    <slot></slot> <!-- Placeholder for default slot content -->
  </div>
</template>

Use Named Slots when Placing Multiple Outlets in a Single Component · VUE-02.3.2 · MUST

Named Slots allow for more control over where the content is placed within the child component’s template.

Named Slots Example (✅)

Named Slots ParentComponent.vue
<template>
  <ChildComponent>
    <template #header>
      <h1>Header Content</h1>
    </template>
    <template #footer>
      <p>Footer Content</p>
    </template>
  </ChildComponent>
</template>
Named Slots ChildComponent.vue
<template>
  <div>
    <header>
      <slot name='header'></slot>
    </header>
    <main>
      <p>Main Content</p>
    </main>
    <footer>
      <slot name='footer'></slot>
    </footer>
  </div>
</template>

Use Scoped Slots to Share Reusable Logic or Structure Across Different Components with Varying Data or Content · VUE-02.3.3 · MUST

Scoped Slots allows a parent component to access the state in a child component, enabling more flexible and reusable component design.

Scoped Slots Example (✅)

Scoped Slots ParentComponent.vue
<template>
  <ItemList :items="items">
    <template #default="{ item }">
      <div>{{ item.name }} - {{ item.description }}</div>
    </template>
  </ItemList>
</template>

<script setup>
import { ref } from 'vue';
import ItemList from './ItemList.vue';

const items = ref([
  { name: 'Item 1', description: 'Description 1' },
  { name: 'Item 2', description: 'Description 2' }
]);
</script>
Slots ChildComponent.vue
<template>
  <div>
    <div v-for="item in items" :key="item.name">
      <slot :item="item"></slot>
    </div>
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  items: {
    type: Array,
    required: true
  }
});
</script>

Use setup Function for Component Initialization · VUE-02.4 · MUST

The setup() hook centralises all component initialisation and initialises reactive state. Making it easier to understand a component’s dependencies, and the logic it encapsulates.

Setup Example

<template>
  <div>{{ message }}</div>
</template>

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

export default {
  setup() {
    const message = ref('Hello, Composition API!');

    return {
      message,
    };
  },
};
</script>