5 Essential Vue 3 Composition API Methods Every Frontend Developer Must Know in 2024

Understanding these functions deepens your grasp of Vue 3's Composition API and enables you to build more robust and maintainable applications. This guide provides practical examples you can immediately implement in your projects.

Overview of Composition API Macros

Vue 3 provides these macro functions specifically for defining component properties, events, exposed methods, options, and slots within the script setup syntax.

Function Purpose Basic Syntax Notes
defineProps Declares component props const props = defineProps({...}); Enables type-safe prop validation
defineEmits Declares component events const emit = defineEmits([...]); Type-safe event emission
defineExpose Exposes component internals to parent defineExpose({...}); Essential for template refs
defineOptions Sets component options in Composition API defineOptions({...}); Alternative to script setup attributes
defineSlots Defines and accesses slot content const slots = defineSlots(); Useful for dynamic slot handling

Practical Examples and Analysis

1. defineProps: Declaring Component Properties

The defineProps function enables you to declare and validate component props, making data flow between parent and child components more preditcable and secure. This approach works exceptionally well with TypeScript, providing automatic type inference and runtime validation.

Best suited for: Components requiring clear prop contracts, especially in TypeScript projects where type safety is paramount. While it adds clarity and validation, it may feel verbose for simple props that don't need strict validation.

Here's a counter component accepting an initial value:

<!-- Counter.vue -->
<template>
  <div>
    <p>{{ currentValue }}</p>
    <button @click="increase">Increase</button>
  </div>
</template>

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

interface Props {
  startValue: number;
}

const props = defineProps<Props>();

const currentValue = ref(props.startValue);

function increase() {
  currentValue.value++;
}
</script>

Parent component usage:

<!-- App.vue -->
<template>
  <div>
    <Counter :start-value="initialCount" />
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import Counter from './Counter.vue';

const initialCount = ref<number>(100);
</script>

2. defineEmits: Declaring Component Events

The defineEmits function provides a type-safe mechanism for declaring events that a component can emit, replacing the traditional $emit approach. This creates a clear contract between components about what events they can communicate.

Best suited for: Complex component communication scenarios where clear event contracts improve maintainability. Extending our counter example:

<!-- Counter.vue -->
<template>
  <div>
    <p>{{ currentValue }}</p>
    <button @click="increase">Increase</button>
  </div>
</template>

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

interface Props {
  startValue: number;
}

const props = defineProps<Props>();
const emit = defineEmits<{
  (e: 'value-changed', value: number): void;
}>();

const currentValue = ref(props.startValue);

function increase() {
  currentValue.value++;
  emit('value-changed', currentValue.value);
}
</script>

Parent component handling the event:

<!-- App.vue -->
<template>
  <div>
    <Counter :start-value="initialCount" @value-changed="onValueChanged" />
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import Counter from './Counter.vue';

const initialCount = ref<number>(100);

function onValueChanged(newValue: number) {
  console.log('Counter value:', newValue);
}
</script>

defineProps and defineEmits form the foundation of Vue 3 component communication and are used together frequently in production applications.

3. defineExpose: Exposing Component Methods to Parents

The defineExpose function lets you explicitly control which internal methods or properties are accessible to parent components via template refs. By default, components using <script setup> keep their state private.

Best suited for: Building reusable component libraries or creating components that need controlled public APIs. This improves encapsulation but requires careful consideration of what gets exposed.

Let's add a reset method to our counter:

<!-- Counter.vue -->
<template>
  <div>
    <p>{{ currentValue }}</p>
    <button @click="increase">Increase</button>
  </div>
</template>

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

interface Props {
  startValue: number;
}

const props = defineProps<Props>();
const emit = defineEmits<{
  (e: 'value-changed', value: number): void;
}>();

const currentValue = ref(props.startValue);

function increase() {
  currentValue.value++;
  emit('value-changed', currentValue.value);
}

function resetToInitial() {
  currentValue.value = props.startValue;
}

defineExpose({
  reset: resetToInitial
});
</script>

Parent accessing the exposed method:

<!-- App.vue -->
<template>
  <Counter ref="counterRef" :start-value="initialCount" @value-changed="onValueChanged" />
  <button @click="resetCounter">Reset</button>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import Counter from './Counter.vue';

interface CounterRef {
  reset: () => void;
}

const counterRef = ref<CounterRef | null>(null);
const initialCount = ref<number>(10);

function onValueChanged(value: number) {
  console.log('Current value:', value);
}

function resetCounter() {
  counterRef.value?.reset();
}
</script>

Important: Methods and properties exposed via defineExpose must be accessed through a template ref on the parent component.

4. defineOptions: Setting Component Options

The defineOptions function enables you to configure component options like name, components, and directives while using the Composition API. This provides a way to access options-based configuration without switching between APIs.

Best suited for: Complex components requiring detailed configuration. It bridges the gap between Composition and Options APIs, though it may be unnecessary for simpler components.

defineOptions({
  name: 'DataDisplay',
  components: { ChildComponent },
  directives: {
    focus: {
      mounted(element: HTMLElement) {
        element.focus();
      }
    }
  }
});

Full example with custom component and directive:

<!-- Dashboard.vue -->
<template>
  <div v-highlight>
    <p>{{ greeting }}</p>
    <InfoPanel />
  </div>
</template>

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

const greeting = ref('Welcome to the dashboard');

defineOptions({
  name: 'Dashboard',
  components: {
    InfoPanel
  },
  directives: {
    highlight: {
      mounted(el: HTMLElement) {
        el.style.backgroundColor = '#f0f0f0';
        el.style.padding = '16px';
      }
    }
  }
});
</script>

5. defineSlots: Handling Component Slots

The defineSlots function provides programmatic access to slot content, enabling dynamic slot handling and inspection. While less commonly used then other macros, it proves valuable for components with complex layout requirements.

Best suited for: Components requiring dynamic content manipulation or flexible layouts. The API simplifies slot processing but may be overkill for straightforward slot usage.

<!-- LayoutCard.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header"></slot>
    </div>
    <div class="card-body">
      <slot></slot>
    </div>
    <div class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

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

const slots = defineSlots();

console.log('Header exists:', !!slots.header);
console.log('Default exists:', !!slots.default);
console.log('Footer exists:', !!slots.footer);
</script>

Parenet using the slot component:

<!-- App.vue -->
<template>
  <LayoutCard>
    <template #header>
      <h2>Card Title</h2>
    </template>
    <template #footer>
      <p>Additional information</p>
    </template>
  </LayoutCard>
</template>

<script setup>
import LayoutCard from './LayoutCard.vue';
</script>

When to Use Each Function

Scenario Recommended Functions
Basic component communication defineProps + defineEmits
Building reusable component APIs defineExpose
Complex component configuration defineOptions
Dynamic content rendering defineSlots

Additional Configuration

ESLint Configuration for Vue 3 Macros

When using ESLint with Vue 3, configure it to recognize Composition API macros as global variables:

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es2021: true
  },
  parser: 'vue-eslint-parser',
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-strongly-recommended'
  ],
  parserOptions: {
    ecmaVersion: 12,
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: [
    'vue',
    '@typescript-eslint'
  ],
  rules: {
    quotes: [1, 'single'],
    'vue/script-setup-uses-vars': 0,
    'no-unused-vars': 0
  },
  globals: {
    withDefaults: true,
    defineExpose: true,
    defineEmits: true,
    defineProps: true,
    defineOptions: true,
    defineSlots: true
  }
};

withDefaults Helper for Type-Safe Props

The withDefaults helper provides default values for props while maintaining TypeScript type inference:

// Using runtime declaration with defaults
const props = withDefaults(defineProps({
  title: String,
  quantity: { type: Number, default: 0 },
}), {
  title: 'Default Title',
  quantity: 0
});

// Using TypeScript generics with defaults
interface Props {
  title: string;
  quantity: number;
}

const props = withDefaults(defineProps<Props>(), {
  title: 'Default Title',
  quantity: 0
});

withDefaults is highly recommended for TypeScript projects as it enables proper type inference and reduces repetitive default value assignments.

Tags: vue3 vue-composition-api javascript TypeScript frontend-development

Posted on Tue, 21 Jul 2026 17:11:51 +0000 by FMB