Skip to content

Vue 2

Pinia state management

  • 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
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'


export const useNumberStore = defineStore('number', () => {
  const count = ref(0)

  const doubleNumber = computed(() => count.value * 2)

  const increment = () => count.value++

  const incrementBy = (val: number) => count.value += val

  return { count, doubleNumber, increment, incrementBy }
})

OR

 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
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">
import { useNumberStore } from './stores/counter';

const store = useNumberStore();

store.$subscribe((mutation, state) => {
    console.log(mutation, state);
    console.log(state.count);
});

const changeState = () => {
    store.count = -1;
}
</script>

<template>
    <div>{{ store.count }}</div>
    <button @click="store.increment()">+1</button>
    <button @click="store.incrementBy(5)">+5</button>
    <button @click="changeState()">set to -1</button>
</template>

<style scoped>
</style>

Vue router

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

import './assets/main.css'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

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 } 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")
    },
    // When props is set to true, use defineProps macro in component
    {
    path: '/owners/edit/:id', name: 'OwnersEdit', component: OwnersEdit, props: true
    },
]

const router = createRouter({
    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 *

Receiving propertiess

1
2
3
4
5
<script setup lang="ts">
const props = defineProps(['foo'])

console.log(props.foo)
</script>

or using object syntax

1
2
3
4
5
6
<script setup lang="ts">
const props = defineProps({
  title: String,
  likes: Number
})
</script>

RouterView

1
2
3
  <!-- route outlet -->
  <!-- component matched by the route will render here -->
  <router-view></router-view>

Router - navigation

  • Declarative

    • <RouterLink :to="'viewpath'">
    • <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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts">
import { RouterLink, useRouter, useRoute } from 'vue-router';
const router = useRouter()
const route = useRoute()

const go = () => {
    router.push({
        name: 'second',
        params: { id: '5' },
        query: { foo: 'bar' }
    });
}
</script>

<template>
    Home

    <RouterLink :to="'second'">To Second View</RouterLink>
    <button @click="go()">second</button>
</template>

<style scoped></style>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

const props = defineProps({
    id: String,
})

</script>

<template>
    Second: "{{ id }}" - "{{ route.query.foo }}"
</template>

<style scoped></style>

Vue HTTP requests

Use whatever - axios, fetch api...

Vue reactivity

Reactive state

Create a reactive object or array with the reactive()

1
2
3
import { reactive } from 'vue'

const state = reactive({ count: 0 })

State is deeply reactive by default.
It only works for object types (objects, arrays, and collection types).
You must keep the same reference to reactive object.

Create a reactive value with the ref()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { ref } from 'vue'

const state = ref(0)
const objectRef = ref({ count: 0 })

// this works reactively
objectRef.value = { count: 1 }

// the function receives a ref
// it needs to access the value via .value but it
// will retain the reactivity connection
callSomeFunction(objectRef)

ref() allows us to create a "reference" to any value and pass it around without losing reactivity.

Computed properties

1
2
3
4
// a computed ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})

NB! Computed properties are cached based on their reactive dependencies.

Watchers

Use the watch function to trigger a callback whenever a piece of reactive state changes.

1
2
3
4
5
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {});

watch's first argument can be different types of reactive "sources": it can be a ref (including computed refs), a reactive object, a getter function, or an array of multiple sources:

(Vue dosc on watchers)[https://vuejs.org/guide/essentials/watchers.html]

Vue's reactive problems (possible)

  • 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)

Vue Cheat sheets

https://www.vuemastery.com/vue-cheat-sheet

Vue Composition api Router Pinia

https://lobotuerto.com/notes/vue-with-typescript-cheat-sheet

https://vuejs.org/guide/typescript/composition-api.html