DEV Community

Tianya School
Tianya School

Posted on

Vue 3 Composition API and Hooks Pattern

The Composition API in Vue 3 introduces the concept of Hook functions, offering a more modular and reusable approach to state management and logic organization.

Custom Hooks

First, we create a custom Hook, such as useCounter, to encapsulate counter logic:

// useCounter.js
import { ref } from 'vue';

export function useCounter() {
  const count = ref(0);

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

  function decrement() {
    count.value--;
  }

  return {
    count,
    increment,
    decrement
  };
}
Enter fullscreen mode Exit fullscreen mode

In the code above, ref creates reactive data, and the increment and decrement methods increase or decrease the counter’s value, respectively. The return statement exposes the data and methods for direct use in components.

Then, in a component, we can import and use this Hook:

<template>
  <div>
    <h1>Count: {{ count }}</h1>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
import { defineComponent } from 'vue';
import { useCounter } from './useCounter';

export default defineComponent({
  setup() {
    const { count, increment, decrement } = useCounter();

    return {
      count,
      increment,
      decrement
    };
  }
});
</script>
Enter fullscreen mode Exit fullscreen mode

In the component’s setup function, we call the useCounter Hook and destructure its returned object into local variables. These variables are reactive and can be used directly in the template.

Benefits of this pattern:

  1. Logic Separation: Hooks encapsulate specific business logic, making component code clearer and easier to maintain.
  2. Reusability: Hooks can be shared across multiple components, reducing code duplication.
  3. Type Safety: In TypeScript, custom Hooks can include type definitions, improving code readability and predictability.
  4. Reactive System: Reactive data created with ref or reactive ensures automatic view updates when the state changes.

The Composition API combined with the Hooks pattern enables developers to organize and reuse code more flexibly, enhancing maintainability and development efficiency.

Built-in Hooks

In addition to custom Hooks, Vue 3’s Composition API provides several built-in Hooks, such as watch, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, toRefs, computed, and watchEffect.

1. onMounted: Executes after the component is mounted

import { onMounted } from 'vue';

export default {
  setup() {
    let element;

    onMounted(() => {
      element = document.querySelector('#someElement');
      // Perform DOM-dependent initialization here
    });

    return {};
  }
};
Enter fullscreen mode Exit fullscreen mode

2. computed: Computed properties, similar to Vue 2’s computed properties

import { ref, computed } from 'vue';

export default {
  setup() {
    const base = ref(10);
    const multiplier = ref(2);

    const result = computed(() => {
      return base.value * multiplier.value;
    });

    return {
      base,
      multiplier,
      result
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

3. watch: Watches for changes in reactive data

import { ref, watch } from 'vue';

export default {
  setup() {
    const base = ref(10);
    const multiplier = ref(2);

    watch(base, (newBase, oldBase) => {
      console.log(`Base changed from ${oldBase} to ${newBase}`);
    });

    return {
      base,
      multiplier
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

4. watchEffect: Runs a side-effect function whenever any dependency changes

import { ref, watchEffect } from 'vue';

export default {
  setup() {
    const base = ref(10);
    const multiplier = ref(2);

    watchEffect(() => {
      console.log(`Result: ${base.value * multiplier.value}`);
    });

    return {
      base,
      multiplier
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

5. toRefs: Converts object properties to reactive references for direct use in templates

import { ref, toRefs } from 'vue';

export default {
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  setup(props) {
    const { name, age } = toRefs(props.user);

    return {
      name,
      age
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

6. reactive: Creates a reactive object, enabling deep monitoring of property changes

import { reactive } from 'vue';

export default {
  setup() {
    const state = reactive({
      count: 0,
      user: {
        name: 'John Doe',
        age: 30
      }
    });

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

    return {
      state,
      increment
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

7. provide/inject: Provides and injects dependencies, similar to Vue 2’s non-reactive version, but supports reactive data in Vue 3

// Parent component
import { provide } from 'vue';

export default {
  setup() {
    const theme = ref('light');

    provide('theme', theme);

    return {};
  }
};
Enter fullscreen mode Exit fullscreen mode
// Child component
import { inject } from 'vue';

export default {
  setup() {
    const theme = inject('theme');

    return {
      theme
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

8. watch with Options: Provides fine-grained control, such as deep watching, async handling, and triggering on initial value changes

import { ref, watch } from 'vue';

export default {
  setup() {
    const base = ref(10);
    const multiplier = ref(2);

    watch(
      [base, multiplier],
      ([newBase, newMultiplier], [oldBase, oldMultiplier]) => {
        if (newBase !== oldBase || newMultiplier !== oldMultiplier) {
          console.log(`Result: ${newBase * newMultiplier}`);
        }
      },
      {
        deep: true, // Deep watching
        immediate: true // Trigger callback immediately on initial value change
      }
    );

    return {
      base,
      multiplier
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

9. effectScope: Runs side effects within a specific scope for easier cleanup and management

import { effectScope, ref } from 'vue';

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

    const stopEffect = scope.run(() => {
      console.log('Effect running');
      scope.watch(count, () => {
        console.log(`Count is now: ${count.value}`);
      });
    });

    return {
      count,
      stopEffect
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)