defineComponent vs <script setup>
Tags: vue, webdev, javascript
Published: 2022-09-18 22:46:41

## Introduction

With the creation of Vue 3 the community has been introduced to a new, more function oriented way of organizing component structure dubbed the Composition API.

The previous way being named the Options API was more akin to an object oriented approach, heavily relying on the **this** keyword.

## Options API

Even though both ways are fully capable of covering common use cases, the options API had its use but in the bigger picture it severely limited the developer as it abstracted away the reactivity details and enforced code organization via option groups.

```javascript
<template>
  <button @click="increment">
    Count is: {{ count }}
  </button>
</template>

<script>
export default {
  // Properties returned from data() become reactive state and will be exposed on this.
  data() {
    return {
      count: 0
    }
  },
  // Methods are functions that mutate state and trigger updates.
  // They can be bound as event listeners in templates.
  methods: {
    increment() {
      this.count++
    }
  },
  // Lifecycle hooks are called at different stages of a component's lifecycle.
  // This will be called when the component is mounted.
  mounted() {
    console.log(`The initial count is ${count.value}.`)
  }
}
</script>
```

## Composition API

The Composition API is centered around declaring reactive state variables directly in a function scope, and composing state from multiple functions together to handle complexity.

It is more free-form, and requires understanding of how reactivity works in Vue to be used effectively.

In return, its flexibility enables more powerful patterns for organizing and reusing logic.

```javascript
<template>
  <button @click="increment">
    Count is: {{ count }}
  </button>
</template>

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

export default defineComponent({
  setup() {
    // Reactive state
    const count = ref(0);

    // Functions that mutate state and trigger updates
    function increment() {
      count.value++;
    }

    // Lifecycle hooks
    onMounted(() => {
      console.log(`The initial count is ${count.value}.`);
    });

    return {
      count,
      increment
    };
  }
});
</script>
```

Data that needs to be used on the template must be manually exposed through the return values of the defineComponent function which is very verbose, but it allows for clearer insight into what is being sent up.

## &lt;script setup&gt;

Additionally, another option was added to the Composition API in the form of syntactic sugar - the **setup** directive.

It allows the developer to write top level statements without the additional boilerplate of the defineComponent function.

```javascript
<template>
  <button @click="increment">
    Count is: {{ count }}
  </button>
</template>

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

const count = ref(0)

function increment() {
  count.value++
}

onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>
```

Unlike the previous entry, everything is exposed to the template which might make it difficult to manage the things that were actually meant to be on the template, but with the amount of reduced boilerplate this syntax makes up for that.

## Props and emits

The defineComponent approach is verbose and very explicit in the way it functions, such as the props property where the properties of the component are defined.

```javascript
<template>
  <span>
    {{ title }}
  </span>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    title: {
      type: String,
      default: "",
      required: false
    }
  },
  setup(props) {
    return {};
  }
});
</script>
```

The title prop is immediately available to the template and if it needs to be used in the script it is exposed through the parameters of the setup function.

The same can be done with the setup directive, using the defineProps function.

```javascript
<template>
  <span>
    {{ title }}
  </span>
</template>

<script setup>
const props = defineProps({
  title: {
    type: String,
    default: "",
    required: false
  }
});
</script>
```

Notice how we do not need to explicitly write props.title in the template.

If the title needs to be used in the script, the defineProps function returns a reactive object with the aforementioned prop.

The **defineProps** macro is available by default if using the setup directive.

The same principle is applied to the emission of events:

```javascript
<script>
import { defineComponent } from "vue";

export default defineComponent({
  emits: ["update:value"],
  setup(props, { emit }) {
    emit("update:value");
    return {};
  }
});
</script>
```

When not using the setup directive, the emit function must be exposed through the setup function parameters.

```javascript
<script setup>
  const emit = defineEmits(["update:value"]); emit("update:value");
</script>
```

In this case, however, only the **defineEmits** macro is neccessary. The return value is a function that can be used to emit events.

## In comparison

```javascript
<template>
  <button @click="increment">
    Count is: {{ count }}
  </button>
</template>

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

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

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

    onMounted(() => {
      console.log(`The initial count is ${count.value}.`);
    });

    return {
      count,
      increment
    };
  }
});
</script>
```

```javascript
<template>
  <button @click="increment">
    Count is: {{ count }}
  </button>
</template>

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

const count = ref(0)

function increment() {
  count.value++
}

onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>
```

Comparing the previous blocks of code side by side, we can see a slight decrease in boilerplate. To be exact 29 lines vs 20 lines, a 30ish% decrease!

## Conclusion

The defineComponent approach and the setup macro approach are one in the same in terms of functionality but differ in regards to the way things are set up.

One is verbose and the other is not, if the need for more explicit code arises defineComponent is the way to go.

When using setup though, a lot of boilerplate is removed and top level code becomes the norm.

By personal choice, I go with the setup syntactic sugar.