Skip to content

Vue

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@3

npm install -D vue-class-component@8.0.0-rc.1

Modify tsconfig.json and add: "experimentalDecorators": true,


Or classical

  • Install VUE cli (cli.vuejs.org) > npm install -g @vue/cli
  • Create new project with > vue create <projectname>
  • Select typescript, babel, vuex, router, pwa,….
  • Change to project directory and lauch VS Code
  • Install Vue plugins for VS Code (Vetur)
  • Launch dev website > npm run serve

Vue CLI


Vue.js chrome devtools extension


  • Install vuejs-devtools Chrome plugin

Formatting issues in .eslintrc.js

Vue.js eslint config formatting issues


Fix common formatting issues in .eslintrc.js.

VS Code configuration

Vue.js Vetur config

Set VS Code vue support (Vetur) to use the same indentation – 4


Vue.js editor config

And in .editorconfig.

Install design libs

  • Install packages for css and html
    • > npm i jquery popper.js bootstrap font-awesome
    • > npm i --save-dev @types/jquery @types/bootstrap

Include design items in main.ts

Structure overview

3 main parts - Template, Script, Style. Vue2 – only single root element in template. Vue3 – allows multiple.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
    <div class="hello">Hello, {{ name }}!</div>
</template>

<script lang="ts">
    import { Options, Vue } from "vue-class-component";
    @Options({
        props: {
            name: String,
        },
    })
    export default class TestThing extends Vue {
        name!: string;
    }
</script>

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

Using components

Define used components in @Options.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
    <TestThing name="general Kenobi!" />
</template>

<script lang="ts">
    import { Options, Vue } from "vue-class-component";
    import TestThing from "./components/TestThing.vue";
    @Options({
        components: {
            TestThing,
        },
    })
    export default class App extends Vue {}
</script>

<style>
    #app {
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

App entry

  • main.ts
  • Include design libs.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import 'jquery';
import 'popper.js';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'font-awesome/css/font-awesome.min.css';

createApp(App)
    .use(store)
    .use(router)
    .mount('#app')

App.vue component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
    <div id="app">
        <div id="nav">
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link> 
        </div>
        <router-view/>
    </div>
</template>

<style>
</style>

Child components

Using child components

Note

Don’t forget to register imported components!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<template>
    <div class="home">
        <HelloWorld msg="Welcome" />
    </div>
</template>

<script lang="ts">
    import { Options, Vue } from "vue-class-component";
    // @ is an alias to /src
    import HelloWorld from "@/components/HelloWorld.vue";
    @Options({
        components: {
            HelloWorld,
        },
    })
    export default class Home extends Vue {}
</script>

Props

Sending data down to sub-component

1
2
3
<template>
    <div class="home">
        <HelloWorld msg="Welcome to Your Vue.js App" />

Receiving data in subcomponent

1
2
3
4
5
6
7
@Options({
    props: {
        msg: String,
    },
})
export default class HelloWorld extends Vue {
    msg!: string;

Class properties and methods

  • Declare your properties as regular class properties.
  • Declare methods as class methods. Don’t use => syntax when you need to reference this.
    • Arrow functions are bound to the parent context, and therefore this will not be the Vue instance (it will be Window instance)
1
2
3
4
5
6
7
export default class HelloWorld extends Vue {
    private alertMsg: string = "Alert ";

    onClick(): void {
        window.alert(this.alertMsg + this.msg);
    }
}

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">

Vue Lifecycle

Vue lifecycle

Vue lifecycle methods

Lifecycle methods, add as regular class component methods.

 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
28
29
30
31
// ============ Lifecycle methods ==========
beforeCreate(): void {
    console.log("beforeCreate");
}
created(): void {
    console.log("created");
}
beforeMount(): void {
    console.log("beforeMount");
}
mounted(): void {
    console.log("mounted");
}
beforeUpdate(): void {
    console.log("beforeUpdate");
}
updated(): void {
    console.log("updated");
}
activated(): void {
    console.log("activated");
}
deactivated(): void {
    console.log("deactivated");
}
beforeUnmount(): void {
    console.log("beforeUnmount");
}
unmounted(): void {
    console.log("unmounted");
}

Simple state management

  • Simple approach
  • Props to send data down
  • Events to send data up


  • In child component this.$emit('update:title', newTitle)
  • list emitted events in Options
    1
    2
    3
    4
    5
    6
    7
    8
    @Options({
        components: {
        },
        props: {
            todoItem: Object
        },
        emits:['doneClicked','deleteItem']
    })
    
  • In parent component <text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event"></text-document>

Router component

  • https://router.vuejs.org
  • Main navigation between different sections in app are provided in Router component - src/router/index.ts
  • Selected component is rendered to <RouterView />

Router

src/router/index.ts

 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
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import OwnersEdit from '../views/owners/Edit.vue'

const routes: Array<RouteConfig> = [
    {
    path: '/', name: 'Home', component: Home
    },
    // ==================== Owners ====================
    {
    path: '/owners', name: 'OwnersIndex', 
    // lazy loading chunk
    component: () => import("../views/owners/Index.vue")
    },
    {
    path: '/owners/edit/:id', name: 'OwnersEdit', component: OwnersEdit, props: true
    },
]

const router = createRouter({
    // history: createWebHistory(process.env.BASE_URL),
    history: createWebHistory(import.meta.env.BASE_URL),
    routes
})

export default router;

Routing

1
2
3
4
{
    path: '/test/:id’, name: 'Test,
    component: Test, props: true
},
  • Specify url paceholders with propertyName.
  • Include props: true to map from url placeholders to component props.

  • Catchall with *

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Options({
    components: {
    },
    props: {
        id: String
    }
})
export default class Test extends Vue {
    id!: string;
}

Router - navigation

  • Declarative

    • <RouterLink :to="…">
    • <RouterLink :to="{name:'personsedit', params: {id: id}}">Edit</RouterLink>
  • Programmatic

    • router.push(…)
  • Argument can be string path or a location descriptor object
    • router.push(‘animals’)
    • router.push({name: ‘animals’, params: {id: ‘5’}, query: {foo: ‘bar’}})
    • router.go(n) – navigate n steps back or forward

Inside vue instance – access router via this.$router

Pinia state management (VUEX 5)

  • https://pinia.vuejs.org/

Create the root store, and pass it to app

1
2
3
4
5
6
import { createPinia } from 'pinia'

app.use(createPinia());

app.use(router);
app.mount("#app");

Pinia - STORE

A Store is an entity holding state and business logic that isn't bound to your Components.
Pinia store has three concepts: the state, getters and actions.

A store should contain data that can be accessed throughout your application. This includes data that is used in many places, e.g. User information that is displayed in the navbar, as well as data that needs to be preserved through pages, e.g. a very complicated multi-step form.

Pinia - Define store

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { defineStore } from "pinia";

export const useCounterStore = defineStore({
  id: "counter",
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
  actions: {
    increment() {
      this.counter++;
    },
  },
});

Pinia - Using store

Import and use store in your component.

1
2
3
4
5
6
7
8
9
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { RouterLink, RouterView } from "vue-router";
import { useCounterStore } from "@/stores/counter";

export default class App extends Vue {
  counterStore = useCounterStore();
}
</script>

Reference and use store as any other reactive property.

1
2
3
4
5
<template>
    Home {{ counterStore.doubleCount }} - {{ counterStore.counter }}
    <button @click="counterStore.increment()">Add</button>
    <button @click="counterStore.counter = counterStore.counter + 5">Add 5</button>
</template>

VUEX state management

  • State management pattern + library
  • It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.
  • It is Flux Architecture (like Redux, etc.)

VUEX

VUEX properties

  • State: A State is simply the application data the components interacts with. The state object is a single object and it contains all application level Getters: Computed properties for stores. A getter result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
  • Mutators: The only way of modifying a state of an application is by committing mutations. Mutators is similar to events in sense that it has it own string and handler component for committing mutations. Mutations are synchronous. Use store.commit method.
  • Actions: Actions are similar to mutation only difference being action commits mutations and actions can contain arbitrary asynchronous operations. Actions are triggered with the store.dispatch method.
  • Modules: Building a large scale application could amount to a large amount of data in the store. Modules comes in as a rescue here serving as a container for all stores.

VUEX store

  • Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes.
  • You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations.

Example

  • State: initial state object
  • Getters: computed properties, cached (bug in 3.0). Will receive state and other getters as params
  • Mutations: sync state modifiers. State as first param. Payload as second param.
  • Actions: commit mutations, can contain arbitrary async operations. Context as first parameter (store like object). Payload object second.
  • Modules – when store grows too big.
  • Strict: monitor direct state access.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
state: {
    count: 0
},
getters: {
    message: (state, getters) 
        => 'Count is ' + state.count.toString()
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
},
actions: {
    increment: (context) 
        => context.commit('increment')
},
modules: {
}
})

vuex, actions, mutations

  • You cannot directly call a mutation handler
  • To invoke a mutation handler, you need to call store.commit with its type store.commit('increment’, {amount: 10; direction: "down"});

Using VUEX in components

  • Computed properties via get
  • store.commit – call actions
  • store.dispatch – call mutations
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import store from "../store";

export default class HelloWorld extends Vue {

    get count(): number {
        return store.state.count;
    }
    onClick(): void {
        store.commit('increment');
        store.dispatch('increment');
    }

}

Vue's reactive problems

  • Vue has no possibility to monitor some data changes
  • When adding new properties to an Object, you should either: Use Vue.set(obj, 'newProp', 123), or Replace that Object with a fresh one. For example, using the object spread syntax we can write it like this: state.obj = { ...state.obj, newProp: 123 }
  • Array change detection: push(), pop(), shift(), unshift(), splice(), sort(), reverse()
    • Bad: vm.items[indexOfItem] = newValue
    • Use instead: Vue.set(vm.items, indexOfItem, newValue), vm.items.splice(indexOfItem, 1, newValue)