Single-File Component (SFC)

A Vue SFC encapsulates the component’s logic (JavaScript), template (HTML), and style (CSS) in a single file. Sample:

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Count is: 8</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

API Styles

Options API

With options API, we define a component’s logic using an object of options such as data, methods, mounted. Properties defined by options are exposed on this inside functions.

Composition API

With composition API, we define a component’s logic using imported functions. For example:

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

// 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}.`)
})
</script>

<template>
  <button @click="increment">Count is: 8</button>
</template>

‘script’ vs ‘script setup’

If <script></script> is used to declare some variables and functions, they need to be exported by setup() function inside. But if <script setup><script> is used, then manual exporting becomes unnecessary. This feature is used in SFCs.

Attribute Binding

In Vue, mustaches are only used for text interpolation. To bind an attribute to a dynamic value, we use v-bind directive. A directive is a special attribute that starts with the v- prefix. Directive values are Javascript expressions that have access to the component’s state.

The part after the colon (:id) is the “argument” of the directive. Because v-bind is used so frequently, it has a dedicated shorthand syntax:

<div :id="dynamicId"></div>

Event Listening

We can listen to DOM event using v-on directive:

<button v-on:click="increment">8</button>

Due to its frequent use, v-on also has a shorthand syntax:

<button @click="increment">8</button>

Here, increment is referencing a function declared in <script setup>. Inline handlers is also supported:

<button @click="count++">8</button>

Event Handler Parameter

The event handler automatically receives the native DOM event object, which triggers it.

Event Modifiers

It’s common to call event.preventDefault() or event.setPropagation() inside event handlers. Vue can set this when binding event handlers, using event modifiers, which are postfixs to v-on directives. Here are some of them:

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive For example: <a @click.stop="doThis"> </a>. And they can be chained one after another.

Key Modifiers

Vue allows adding key modifiers to v-on or @ when listening for key events.

<input @keyup.enter="submitThis" />

Vue provides some common key alias:

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right And modifier keys:
  • .ctrl
  • .alt
  • .shift
  • .meta (This is WIN key on Windows)

For example:

<input @keyup.alt.enter="clear" />

.exact modifier allows control of exact combination of system modifiers needed to trigger an event.

<button @click.ctrl.exact="onlyCtrlPressedFunc"></button>

Mouse Modifiers

Some:

  • .left
  • .right
  • .middle

These modifiers restrict the handler to events triggered by a specific mouse button.

Form Bindings

Using v-bind and v-on together, we can create two-way bindings on form input elements:

<input :value="text" @input="onInput">

function onInput(e) {
  // A v-on handler receives the native DOM event as the argument.
  text.value = e.target.value;
}

To simplify two-way bindings, Vue provides a directive, v-model, which is essentially a syntax suger for the above:

<input v-model="text">

v-model automatically syncs the <input>’s value with the bounded state, so we no longer need to use an event handler for that.

Some Modifiers to v-model

  • .number
  • .trim
  • .lazy

Computed Value

Introducing computed(). We can create a computed ref that computes its .value based on other reactive data sources.

computed() expects to be passed a getter function, whose returned value bacomes a computed ref. Computed refs are also auto-unwrapped in templates, so you can reference them without .value in template expressions.

computed refs are cached, and only update when their dependents change.

Life Cycle Hook

To run code after mount, we can use the onMount() function:

import { onMounted } from 'vue';

onMounted(() => {
  // Component is now mounted.
})

This is called a lifecycle hook. The onMounted() will be called after the component finishes the initial rendering and creates the DOM nodes.

Some lifecycle hooks:

  • onMounted
  • onUpdated
  • onUnmounted

Watchers

Sometimes we need to perform some “side effects” reactively, for example when some value changed, to perform an action.

watch(variable, calledFunction);

Components

Typically, Vue components nest other components. To use a child component, we first import it:

import ChildComp from './ChildComp.vue'

Then, we use it in the template as:

<ChildComp />

Props

A child component can accept input from parent via props.

First, child component needs to declare the props it accepts:

<script setup>
const props = defineProps({
  msg: String
})
</script>

or

<script setup>
defineProps(['title']);

// Access
console.log(props.title);
<script>

Here defineProps() is a compile time macro. And now we can pass the props to child like attributes:

<ChildComp :msg="myVariable" />

or

<ChildComp msg="constant string" />

Emits

In addition to receiving props, a child component can also emit events to the parent:

// Declare emitted events.
const emit = defineEmits(['response']);

// emit with arguments
emit('response', 'hello');

The first argument to emit() is the event name. Any additional arguments are passed on to the event listener.

The parent can listen to child-emitted events using v-on, and the handler receives the extra argument from the child emit call.

A component can emit custom events directly in template expressions, using the built-in $emit method:

<button @click="$emit('someEvent')">click me</button>

But $emit method is not accessible within the <script setup>.

The parent listens to it using v-on:

<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />

Event names are automatically transformed to kebab-case, which is recommended.

ref Unwrapping in Templates

Only top-level ref properties will be unwrapped automatically in template, where a .value is unnecessary.

With an exception: when a ref value is the final expression in text interpolation(inside `` tag), ref unwrapping can also take place.

ref Unwrapping in Reactive Objects

When a ref is accessed or mutated as a property of a reactive object, it is also automatically unwrapped so it behaves like a normal property.

HTML Class Binding

Vue has special rules when binding class attribute.

Binding to Objects

An object can be bound to class, where the key is the classname and value is true or false which determines whether the key is present in the class attribute. For example:

<div :class="{ active: isAction }"> </div>

:class directive can co-exist with plain class attribute.

Style Binding

:style directive binds an object to HTML style attribute. For example:

const activeColor = ref('red');
const fontSize = ref(30);

<div :style="{ color: activeColor, fontSize: fontSize + 'px'}"> </div>

or

<div :style="{ 'font-size': fontSize + 'px' }"> </div>

camelCase keys are recommended.

Binding to Arrays

:style can be bound to an array of style objects, and they will be merged by Vue.

<div :style="[firstStyleObj, secondStyleObj]"> </div>

Multiple Values

Multiple values can be provided to a style property as an array, and the last valid one (to browser) will be used.

Conditional Rendering

Related directives are: v-if, v-else-if, v-else. They can apply to normal html elements, and also to <template> element, which serves as an invisible wrapper.

v-if vs v-show

When using v-if, the element is created/destroyed when truthiness changes. But when using v-show, only CSS display property is toggled, and the element is always rendered into DOM.

So, if some element show/hide frequently, v-show is better considering performance. And if some element is show/hide and is unlikely to change at runtime, v-if is a good option.