Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

A Comprehensive Guide to Vue 3's script-setup Syntax

Tech 2

Vue 3.0 introduced the Composition API, which, while powerful, initially felt more verbose for developers accustomed to the Options API. The Vue team addressed this feedback within the Single File Component (SFC) context by introducing the <script setup> syntax, a compile-time syntactic sugar designed to streamline the developer experience.

Prerequisite: This guide assumes familiarity with Vue 3's Composition API and SFC structure. Ensure your project uses vue and @vue/compiler-sfc at version 3.1.4 or higher for stable support.

Introduction to script-setup

The <script setup> feature, which moved out of experimental status in Vue 3.2.0-beta.1, offers a more concise way to write component logic. By adding the setup attribute to a <script> tag, the entire block is treated as the setup() function. Top-level variables and functions are automatically available in the template, eliminating the need for explicit returns.

Standard Composition API Syntax:

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

export default defineComponent({
  setup() {
    const message = ref('Hello')
    return { message }
  }
})
</script>

script-setup Syntax:

<script setup>
import { ref } from 'vue'
const message = ref('Hello')
</script>

The compiler transforms this syntax back into a standard component during build. This feature is intended for use within .vue files in a build-tool-enabled project (e.g., Vite, Vue CLI).

Global Compiler Macros

In <script setup>, several compiler macros are available globally without import. To avoid ESLint warnings, you can configure them as globals.

.eslintrc.js Configuration:

module.exports = {
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly',
  },
}

Streamlined Template Usage

Variables Are Automatically Exposed

In the standard setup(), data must be explicitly returned for template access. With <script setup>, top-level declarations are automatically available.

Standard Syntax:

<template>
  <p>{{ text }}</p>
</template>

<script>
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    const text = 'Auto-exposed'
    return { text }
  }
})
</script>

script-setup Syntax:

<template>
  <p>{{ text }}</p>
</template>

<script setup>
const text = 'Auto-exposed'
</script>

Automatic Component Registration

Imported components are automatically registered for use in the template.

Standard Syntax:

<template>
  <CustomButton />
</template>

<script>
import { defineComponent } from 'vue'
import CustomButton from './CustomButton.vue'
export default defineComponent({
  components: { CustomButton },
  setup() { /* ... */ }
})
</script>

script-setup Syntax:

<template>
  <CustomButton />
</template>

<script setup>
import CustomButton from './CustomButton.vue'
</script>

Working with Props

Since there is no explicit setup() function signature, props are declared using the defineProps macro.

Basic Array Syntax

<script setup>
defineProps(['title', 'count', 'metadata'])
</script>

To access props within the script, assign the result too a variable.

<script setup>
const componentProps = defineProps(['title', 'count'])
console.log(componentProps.title)
</script>

Runtime Type Checking with Constructors

You can define prop types using JavaScript constructors, similar to the Options API.

<script setup>
defineProps({
  title: String,
  count: {
    type: Number,
    required: false,
    default: 0
  },
  metadata: Object
})
</script>

Type-Based Declarations with TypeScript

Using TypeScript, you can define prop types via a type annotation.

<script setup lang="ts">
interface UserData {
  id: number
  name: string
}

defineProps<{
  title: string
  count?: number // Optional prop
  userData: UserData
}>()
</script>

Setting Default Values with withDefaults

For TypeScript users, the withDefaults helper provides default values for optional props.

<script setup lang="ts">
interface Settings {
  itemsPerPage?: number
  labels?: string[]
}

const props = withDefaults(defineProps<Settings>(), {
  itemsPerPage: 10,
  labels: () => ['default']
})
console.log(props.itemsPerPage) // 10
</script>

Note: Constructor-based and type-annotation-based prop definitions are mutually exclusive.

Defining Custom Events with defineEmits

Custom events are declared using the defineEmits macro.

Basic Usage

<script setup>
const emitEvent = defineEmits(['updateTitle', 'deleteItem'])

function triggerUpdate() {
  emitEvent('updateTitle', 'New Title')
}
</script>

Full event validation syntax is also supported.

Accessing Attributes and Slots

Accessing Non-Prop Attributes with useAttrs

Attributes not declared as props are accessible via the useAttrs composition function (Vue >= 3.1.4).

<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
console.log(attrs.class)
console.log(attrs['data-test'])
</script>

Accessing Slots with useSlots

Slot content can be accessed programmatically using useSlots (Vue >= 3.1.4), particularly useful in render functions/JSX.

Parent Component:

<template>
  <ChildComponent>
    <p>Default slot content</p>
    <template #footer>
      <p>Footer slot content</p>
    </template>
  </ChildComponent>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
</script>

Child Component (using JSX):

// ChildComponent.jsx
import { defineComponent, useSlots } from 'vue'

export default defineComponent({
  setup() {
    const slots = useSlots()
    return () => (
      <div>
        <div>{slots.default ? slots.default() : null}</div>
        <div>{slots.footer ? slots.footer() : null}</div>
      </div>
    )
  }
})

Exposing Component Data with defineExpose

By default, a component's internal state in <script setup> is private. To expose specific data or methods to a parent component via a template ref, use defineExpose.

Child Component:

<script setup>
import { ref } from 'vue'
const internalState = ref('secret')
const publicMethod = () => { console.log('called') }

defineExpose({
  internalState,
  publicMethod
})
</script>

Parent Component:

<template>
  <ChildComponent ref="childRef" />
</template>

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

const childRef = ref(null)

onMounted(() => {
  console.log(childRef.value.internalState) // 'secret'
  childRef.value.publicMethod() // 'called'
})
</script>

Using Top-Level await

<script setup> allows the use of await at the top level. The component's setup context is automatically made asynchronous.

<script setup>
const apiResponse = await fetch('/api/data').then(r => r.json())
</script>

This is equivalent to an async setup() function in the standard syntax.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.