Vue Basics (Worksheet)
Resources & Infos
- Vue
- unpkg src: https://unpkg.com/vue
- node.js + npm
- Vue CLI:
npm install -g @vue/cli
Terminology
- State Management => All data in one place = Store = Single-Source-of-Truth
- SPA / MPA – Single Page Application / Multi Page Application
Vue root instance
new Vue({
el: '#app',
data: {
// supplied data
entries: datahere
},
computed: {
// computed data, will be updated if data changes
},
methods: {
// custom functions
}
})
<!-- SCOPE: -->
<div id="app">
<!-- accessible by vue -->
</div>
<div id="something">
<!-- NOT accessible by vue -->
</div>
$mount
Alternatively to the el property, the app element can be declared with $mount(selector) method:
new Vue({
data: { ... },
...
}).$mount('#app');
// Or, after instantiatig the Vue root instance
new Vue({...});
// ...
Vue.$mount('#app');
Directives (v-…)
Binding Data (v-bind & v-model)
One-Way (v-bind/:)
Text: {{ entries.title }
Attributes: prefix with v-bind:, omit {{ }} -> v-bind:src="entries.src"
Shortform: omit v-bind (but leave :)::src="entries.src"
Bidirectional (v-model)
v-model="..."
Itterating (v-for)
v-for="dataPoint in dataSet"
<!-- entries = dataset, entry = data point (entries[n]) -->
<ul v-for="entry in entries">
<li>entry.title</li>
</ul>
Note: Its recomended (and enforced by ESLint) to bind a key to for directives. If no unique key is available in the dataset, use the itteration index (The example below shows binding dynamic classnames aswell):
<ul
v-for="(entry, index) in entries"
:key="index"
>
<li
class="item"
:class="['item-'+index, 'item--'+entry.type]"
>
entry.title
</li>
</ul>
Interaction (v-on/@)
<span v-on:click="someMethod(entry.id)">Click me!</span>
<!-- same (@ short form): -->
<span @click="someMethod(entry.id)">Click me!</span>
=> methods are defined in the Vue component within the object methods.
Show/hide (v-if & v-show)
v-if="boolMethod()" / v-show="boolMethod()"
Shows/hides content based on bool value of method.
v-ifadds/removes element from dom. Use if element has dynamic contentv-showshow/hides elmente viadisplay:none. Use with static content.
Components
Components are own Vue instances, similar to the one inititialized via new Vue.
Vue.component('component-name', {
props: ['list','of','available','props'],
template: `<p>I am a <em>component</em></p>`,
methods: {
// Component methods hede
}
});
Passing on props (+ itterating)
<div
v-for="items in list"
>
<some-component
v-bind:items="items"
v-bind:list="list"
/>
</div>
binds / passes on the props items and list which can be utilized in the component definition.
Vue Cli
Install : npm install -g @vue/cli
In project parent folder: vue create <project-name>
Basic component nesting
src/components/ComponentName.vue
- Define component markup in
<template> - export component
name, list of used/receivedpropsetc. (egcomputed) in<script> - define component (
scopedattribute) or global styles in<style>(optionally uselang=""attribute)
<template>
<div>
<!-- component markup -->
</div>
</template>
<script>
export default {
name: 'ComponentName',
data(): {
return {
// custom component data here
}
},
props: ['item', 'someProp']
}
</script>
<style scoped>
/* component only styles */
</style>
<style>
/* global styles */
</style>
src/App.vue
- Embend
<Component />in<template>, bind props (=>v-bind/:) importcomponentexportwithcomponentsproperty
<template>
<div>
<ComponentName
v-for="item in items"
:item="item"
:someProp="someOtherProp"
/>
</div>
</template>
<script>
import ComponentName from './components/ComponentName';
export default {
name: 'App',
components: {
ComponentName
}
}
</script>
Custom Events (child → parent)
1. Emitting the event from the child component, usingthis.$emit("custom-event-name", { ... }) :
const BelovedChild = {
template: `
<button
@click="callOut"
/>
`,
data() {
return {
msg: ''
}
}
methods: {
callOut() {
this.$emit("calling-out", {
message: this.msg
})
}
}
}
2. Listening to the event (parent component) using @cutom-event-name="…"
<UnderstandingParent
@calling-out="someResponse"
/>
Event Bus (A → BUS → B )
Concept: Send ($emit) an event and listen ($on) to it via an independant Vue() intance (= Event Bus). Use the created() lifecycle hook of the responding component for the event listener registration.
const EventBus = new Vue();
const BelovedChild = {
...,
methods: {
// Sending:
callOut() {
EventBus.$emit("calling-out", {
message: this.msg
})
}
}
}
const UnderstandingParent = {
...,
methods: {
someResponse(event) {
// do something with event.message
}
}
created() {
// Listening:
EventBus.$on("calling-out", event => someResponse(event));
}
})
Vuex
- Manages state via getters, mutations and actions.
- getters, mutations and actions are objects containing methods.
- the methods of mutations and actions take exact two parameters: state (mutations) respectively context (actions) and the payload as the second parameter.
- the store (
new Vuex.Store({state, getters, setters, actions}) is connected via the root Vue instance - actions use the
comit('MUTATION_NAME', payload)method to comit to the mutation - the actions itself are called via the
dispatch('actionName', payload')method - the store from the Vue root instance can be accessed with
this.$store
~ Usage (eg. in store/index.js):import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ modules: {...} });
const state = {
basket: [...]
}
const getters = {
getBasket() {
return state.basket
}
}
const mutations = {
ADD_TO_BASKET (state, payload) {
state.basket.push(payload)
}
}
const actions = {
addToBasket (context, payload) {
context.commit('ADD_TO_BASKET', payload);
}
}
const store = new Vuex.Store({state, getters, setters, actions});
const AddToBasketBtn = {
...
methods: {
addItemToBasket(item) {
this.$store.dispatch('addToBasket', item)
}
}
}
mapGetters (vuex)
using getters can end up in a logn list of computed property functions, simply returning a store item via the getter, e.g
computed: {
cartItems() {
return this.$store.getters.cartItems;
},
cartTotal() {
return this.$store.getters.cartTotal;
},
// so on / so forth ...
}
To simplify this vuex has a mapper function, mapGetters (needs to be imported [→ import { mapGetters } from 'vuex'] but is part of the vuex package, so no extra npm for this).
It takes an array of the getter names.
This code results to the same as the one above
computed: mapGetters([
'cartItems',
'cartTotal'
])
The problem with the code above is, that further computed properties (maybe involving calculations) can not be simply appended. To solve this, spread (...) the mapGetters into an object like this
computed: {
...mapGetters([
'cartItems',
'cartTotal'
]),
cartTaxes() {
// some calculations maybe involving the cardTotal...
}
}
mapActions
same as mapGetters, but for actions (mapActions(['a','b','...'])).
The payload of the event it being transmitted to the named Action (eg @click="addCartItem(cartItems)" => mapActions(['addCardItem']) dispatches action addCardItem with the payload cardItem.)
Note: It’s also possible to map a function name to another by using an object instead of an array and the key/value notation:
...mapActions({
srcFunctionName: 'mappedFunctionName'
})
For more see: A complete guide to mapping in Vuex
conventions
mutation types (convention)
for maintainability and scalability reasons it is recommended to store the mutations per module group into a separate js file.
For example consider the mutation UPDATE_CART_ITEMS within a cart module.
- Outsorce it to a file called
mutation-types.jswithin the module folder (/cart/) :export const UPDATE_CART_ITEMS = 'UPDATE_CART_ITEMS'(repeat for all other cart mutation types) - Import it to the index.js, where the mutations are situated:
import * as types from './mutation-types'; - Replace now the mutation
UPDATE_CART_ITEMS (state, payload) {}with[types.UPDATE_CART_ITEMS] (state, payload) {}as well as the occurence ofUPDATE_CART_ITEMSin the actions (without[], justtypes.UPDATE_CART_ITEMSnota bene.
outsource store constants (state, mutations, actions, getters)
… for further maintainability.
Example with actions:
- outsource
const actions = {...}tomodule_name/actions.jsand export it (export const actions = { ... }). - import it (destructured) again in the
module_name/index.jslikeimport { actions } from './actions'. Finito.
Routing
Installnpm install --save vue-router
Implement
1. Define Routes
// routes.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import myComponent from './components/myComponent'
// more imports ...
Vue.use(VueRouter)
const routes = [
{
path: '/mypath',
component: myComponent
},
// more route objects ...
]
export const router = new VueRouter({
mode: 'history', // nice urls (otherwise #/... )
routes
})
2. Import/use router
// main.js
import Vue from 'vue'
import App from './App.vue'
import { router } from './routes' // <=
// more imports ...
new Vue({
router, // <=
// store, render: ...
}).$mount('#app');
3. Place rout links (<router-link to="...">) and component placeholders (<router-view />)
// app.vue
<template>
<div>
...
// Link to route
// Of course this would be within its own component with nice markup ...
<router-link to="/mypath" class="pseudo-nav-link">
Linktext
</router-link>
...
<router-view /> // injects myComponent (defined in routes var in routes.js)
...
</div>
</div>
dynamic routes
Use : folowed by custom attr name.
path: "product/:id",
component: productItem,
props: true // optionally send attrs drirectly to component props
For dynamic router-links bind use :to= (binded) like:to="`/products/${productItem.id}`"
catch-all route
Add * path to end of routes
path: "*",
component: ...
children property
Loads component in component (in place of <router-view> tag) if path matches.
example for path books/list
const appRoutes = [
{
path: '/books',
component: '../layouts/BooksLayout.vue',
children: [{
path: 'list',
component: ''../pages/BooksListPage.vue''
}]
]
Important: No beginnign slash for child path!
$router
Like $store from vuex, there is a globaly accessible $router variable, holding infos and methods like …
$router.go(-1): history -1$router.push('/cart'): navigates to /cart route
Watchers
Watches when value changes
comupted {
token() {
return ...
}
},
watch: {
token(newValue, oldValue) {
if (this.token != 'something') {
...
}
}
Navigation Guards
router.js
const routes = {
path: '/login',
component: Signin,
// Example: if logged in, redirect '/login' to '/'
beforeEnter: (to, from, next) => {
const token = localStorage.getItem('token');
// dynamic route params are stored in to.params
if (token) {
next('/')
} else {
next();
}
}
},
}
const router = new VueRouter({...})
// Global route guard
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token');
if (!token && to.path !== '/login') {
next('/login');
} else {
next();
}
});
export default router;
Module Lazy Loading
Use an import function for the component as compontent whitin the routes definition:
const appRouts = [
{
path: '/',
component: () => import('../path/to/Component.vue')
}
]
Important: Use file extension suffix (.vue)