31 August 2024

Single File Components

Single File Components (SFCs) in Vue.js are a cornerstone of Vue's development ecosystem, providing a concise and organised way to encapsulate component logic, template, and styles. SFCs allow you to keep all related code in a single file, making it easier to manage and maintain. SFCs are a classic trio of Logic, Template and Styles.

You can keep the template, logic, and styles in one place to enhance readability and maintainability of the Vue component. This is especially useful while working on larger applications. SFCs can help encapsulate component functionality, organising code and reducing the cognitive load.

Enhanced DX: Modern code editors and IDEs provide excellent support for SFCs to enhance productivity with features like improved tooling and error detection. From built-in linting to type checking can help catch errors early on. Using SFCs improves an overall development experience with features like syntax highlighting, linting, and code completion. This is useful for:

Scoped and Modular CSS: SFCs support scoped CSS, allowing you to apply styles only to the current component, preventing style leakage. This is particularly useful when want to apply component-specific styles to ensure styles are only applied to the relevant component to avoid further conflicts with other classes with similar names. This modularity of css promotes the reusability of the components without worrying about global style conflicts.

Template Preprocessing: SFCs are written in a file with .vue extension. So, they require a build step to convert these .vue files into JavaScript that browsers can understand. That's why SFCs allow the use of template preprocessors, providing flexibility in how you write templates. This is useful for creating more concise, readable and reusable templates that allows greater customisation options on individual project basis.

Easy Testing & Documentation: SFCs facilitate easier testing and documentation of components. You can isolate and test each component effortlessly through unit testing. You can also automatically generate documentation from component metadata and comments.

They should be used to encapsulate component logic, template, and styles, enhancing the development experience, promoting modular CSS, facilitating template preprocessing, and supporting easy testing and documentation. By leveraging SFCs, you can build more structured, maintainable, and scalable applications.

Single File Components Example

As we learn about various types of components in Vue and when to use them, we will see how the same component - Numeric Input component in this case - is built using different techniques.

Reactive Input as an SFC
quantity: ref(0)

Since Single File Components encapsulate component logic, template, and styles, you can see how reactive property, quantity, events like increment & decrement and watchers are all contained in a single .vue file. Along with it, we also have styles and template as well.

<script>
export default {
    setup() {
        const quantity = ref(0);
        const increment = () => {};
        const decrement = () => {};
        watch(() => quantity.value, (newValue) => {})
        return {};
    },
};
</script>

See full example of Numeric Input Single File Component here

There are two main ways to define the script section of the Vue components: 1) using an explicit setup() function, or 2) using <script setup>. Both of these can be written in JavaScript and TypeScript as well.

Write SFCs using `setup()`

1. JS without script setup

setup() function below serves as an entry point to use Composition API in your Vue component. You need to make sure to explicitly return the properties to access them in the template section.

// myComponent.vue
<script> 
    export default {
    props: {  min: Number, max: Number },
    setup(props, { slots, emits, extend, attrs }) {
      const quantity = ref(0)
      return { quantity }
    }
  }
</script>
<template> </template>

2. TS without script setup

You can write a Vue component using defineComponent() to let TypeScript properly infer types inside the Vue component. defineComponent() also supports inferring the props passed to setup() function.

// myComponent.vue
<script lang="ts">
  import { defineComponent } from 'vue'
  export default defineComponent({
    props: { min: Number },
    setup(props) {
      props.min // type: number | undefined
    }
  })
</script>
<template> </template>

Write SFCs with `script setup`

3. JS with script setup

As per the Vue docs, <script setup> is a compile-time syntactic sugar for using Composition API inside SFCs.

Vue components written using <script setup> have less boilerplate code because you do not need to return properties - i.e. top-level bindings, reactive properties, events & imports - explicitly to access them in the template section. They are exposed automatically.

Also, when other Vue components are imported in your SFC, you do not need to declare them like we did with Options API.

Finally, you can declare your props and emits (emitted event) using pure TypeScript as well.

// myComponent.vue
<script setup>
  import { ref } from 'vue'
  const emit = defineEmits(['change', 'delete'])
  const props = defineProps({ min: Number, max: Number })
</script>

<template> </template>

4. TS with script setup

Sometimes you may need to use type casting in Vue templates. In those instances, you can add a lang attribute to the <script setup> to leverage TypeScript in binding expressions.

// myComponent.vue
<script setup lang="ts">
  // TypeScript enabled
  let x: string | number = 1
</script>

<template> 
  {{ (x as number).toFixed(2) }}
</template>

Encapsulation of logic, template and styles may work very well in most instances. But when we want to reuse the logic multiple times, while the presentation, i.e. template section, changes more often, that's when we must learn the concept of Composition API that was introduced in Vue 3.

Composable Example

As we learn about various types of components in Vue and when to use them, we will see the same Numeric Input component built using different Vue components design patterns.

With Vue 3, now we can separate the logical aspect of the SFC into a separate file while using the Composition API to encapsulate and reuse stateful logic. This way of writing component logic allows us flexibility for code organisation, better typescript support and reusability while still taking advantage of the Vue’s reactivity system.

Instead of writing all the logic withi a single SFC, here’s the reusable composable (useNumericInput) function to create the same Numeric input component that we created as an SFC.

Reactive Input Component with Composable
quantity: ref(0)

In the Numeric Input component above, we took out all the reactive parts, such as reactive property, quantity, events like increment & decrement and watchers, and added them into a reusable function, known as composables in Vue.

Now our .vue file will use the reactive properties and methods from the composable.

// components/v2/ReactiveInputWithComposable.vue
const { increment, 
        decrement, 
        validation, 
        number: quantity } = useNumericInput();

See full example of Numeric Input component using composable here and how the logical parts are extracted into its own composable: useNumericInput.