Skip to content

Vue 1

Why Vue? I figured, what if I could just extract the part that I really liked about React and build something really lightweight without all the extra concepts involved? I was also curious as to how its internal implementation worked. I started this experiment just trying to replicate this minimal feature set, like declarative data binding. That was basically how Vue started.

-- Evan You

Like React.js - but simplified. Why Vue? Table comparison between Vue.js and React.js

Usage

Vite based (10-100x faster dev server than Webpack, but has some issues)

npm init vue@latest

1
2
3
4
5
6
7
8
✔ Project name: … vue-test01
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

and then

1
2
3
cd vue-test01
npm install
npm run dev

MS VS Code Vue3 support

  • Search for extensions under 'volar'
  • Vue Language Features (Volar)
  • TypeScript Vue Plugin (Volar)
  • Vue Volar extension Pack

First App

Using composition api

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<script setup lang="ts">
import { ref, onMounted } from 'vue';

// reactive state
const count = ref(0);

// functions that mutate state and trigger updates
const increment = () => {
    count.value++;
}

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

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

Composition API

  • Reactivity API, e.g. ref() and reactive(), that allows us to directly create reactive state, computed state, and watchers.
  • Lifecycle Hooks, e.g. onMounted() and onUnmounted(), that allow us to programmatically hook into the component lifecycle.
  • Dependency Injection, i.e. provide() and inject(), that allow us to leverage Vue's dependency injection system while using Reactivity APIs.

NB!
Composition API is NOT functional programming. Composition API is based on Vue's mutable, fine-grained reactivity paradigm, whereas functional programming emphasizes immutability.

The Composition API provides a functional and reusable way of organizing your code, while the Options API provides a traditional object-oriented approach.

Component Structure Overview

3 main parts - Template, Script, Style. Vue3 – allows multiple root elements in template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script setup lang="ts">
    const name = 'Dark Lord';
</script>
<template>
    <div class="hello">Hello, {{ name }}!</div>
</template>
<style scoped>
    .hello {
        color: #f00;
    }
</style>

Root Component

1
2
3
4
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Every app requires a "root component" that can contain other components as its children.

#app - "container" argument, which can either be an actual DOM element or a selector string (located in index.html).

Using component

Main style is SFC - single file component. Template and Script and Style in on file.

ButtonCounter.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<script setup lang="ts">
import { ref, onMounted } from 'vue';

// reactive state
const count = ref(0);

// functions that mutate state and trigger updates
const increment = () => {
    count.value++;
}

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

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

App.Vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import ButtonCounter from './components/ButtonCounter.vue'

const name = 'Dark Lord';
</script>

<template>
    <div class="hello">Hello, {{ name }}!</div>
    <br/>
    <ButtonCounter/>
</template>

<style scoped>
.hello {
    color: #f00;
}
</style>

Properties in components

1
2
3
4
5
6
7
8
<script setup lang="ts">
interface Props {
    foo: string
    bar?: number
}

const props = defineProps<Props>()
</script>

NB! this interface must be defined in the same file as component (cannot be imported)!
(The interface or object literal type can contain references to types imported from other files.)

This is because Vue components are compiled in isolation and the compiler currently does not crawl imported files in order to analyze the source type. This limitation could be removed in a future release.

Default values for properties

1
2
3
4
5
6
7
8
9
export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script setup lang="ts">
import { ref, onMounted } from 'vue';
export interface Props {
  counter: number
}

const props = withDefaults(defineProps<Props>(), {
    counter: -5
})

// reactive state
const count = ref(props.counter);

// functions that mutate state and trigger updates
const increment = () => {
    count.value++;
}

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

<template>
    <button @click="increment">Count is: {{ count }}</button>
</template>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import ButtonCounter from './components/ButtonCounter.vue'

const name = 'Dark Lord';
</script>

<template>
    <div class="hello">Hello, {{ name }}!</div>
    <br/>
    <ButtonCounter :counter=10 />
</template>

<style scoped>
.hello {
    color: #f00;
}
</style>

Directives in templates

  • Directives are using v- prefix. v-for, v-bind, v-on, v-if, etc.
  • Some directives take an argument, using colon v-on:click="methodName"
  • Shorthands v-bind - <a :href="url"> ... </a> v-on - <a @click="doSomething"> ... </a>

Bindings in template

  • Text interpolation with {{variableName}} <span>Message: {{ msg }}</span>
  • One-time interpolation with v-once <span v-once>This will never change: {{ msg }}</span>
  • Raw-html binding with v-html="variableName" <p>Using v-html directive: <span v-html="rawHtml"></span></p>
  • Binding in html attributes with v-bind="variableName" <div v-bind:id="dynamicId"></div>
  • Vue supports JavaScript expressions inside all data bindings <div v-bind:id="list- + id"></div>

Conditional rendering – v-if, v-else, v-else-if

Hidding element – v-show only toggles the display CSS property of the element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div v-if="type === 'A'">
    A
</div> 
<div v-else-if="type === 'B'">
    B
</div> 
<div v-else-if="type === 'C'"> 
    C 
</div> 
<div v-else>
    Not A/B/C 
</div>

Rendering a list

  • v-for directive to render a list of items based on an array. The v-for directive requires a special syntax in the form of item in items, where items is the source data array and item is an alias for the array element being iterated on. Use (item, index) to get counter also.
  • Also use v-for to iterate through the properties of an object.
  • v-for can also take an integer. In this case it will repeat the template that many times.
1
2
3
4
5
<ul id="example-2">
    <li v-for="(item, index) in items">
        {{ parentMessage }} - {{ index }} - {{ item.message }}
    </li> 
</ul>

Form bindings

Use the v-model directive to create two-way data bindings on form input, textarea, and select elements. It automatically picks the correct way to update the element based on the input type.

<input v-model="message" placeholder="edit me">

Component Events - emitting and listening

A component can emit custom events directly in template expressions (e.g. in a v-on handler) using the built-in $emit method:

1
<button @click="$emit('someEvent')">click me</button>
1
<MyComponent @some-event="callback" />

Event arguments

1
2
3
<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>
1
<MyButton @increase-by="(n) => count += n" />

Declaring emit events

$emit is not usable in script section!

defineEmits() returns an equivalent function that can be used instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<script setup lang="ts">
// runtime
const emit = defineEmits(['change', 'update'])

// type-based
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

Component v-model

By default, v-model on a component uses modelValue as the prop and update:modelValue as the event. We can modify these names passing an argument to v-model:

1
<MyComponent v-model:title="bookTitle" />

and inside component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Component content - slot

1
2
3
<FancyButton>
    Click me! <!-- slot content -->
</FancyButton>
1
2
3
4
5
<button class="fancy-btn">
    <slot>
        Submit <!-- fallback content -->
    </slot> <!-- slot outlet -->
</button>

NB! Slot content has access to the data scope of the parent component, because it is defined in the parent. Does not have access to child scope!

Vue Lifecycle

Vue lifecycle

Vue lifecycle hooks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// ============ Lifecycle function ==========
function onMounted(callback: () => void): void
function onUpdated(callback: () => void): void
function onUnmounted(callback: () => void): void

function onBeforeMount(callback: () => void): void
function onBeforeUpdate(callback: () => void): void
function onBeforeUnmount(callback: () => void): void

function onErrorCaptured(callback: ErrorCapturedHook): void

type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) => boolean | void


function onActivated(callback: () => void): void
function onDeactivated(callback: () => void): void