Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
581 lines (457 sloc) 13.7 KB
layout author title date desc bigimg img imgtitle categories tags extras interesting toc package-versions
post
Wouter Van Schandevijl
Vue.js Tutorial
2019-04-19
Learning Vue.js basics with mostly code examples.
url desc origin
vue-tutorial-big.png
Photo by Jacky Lo
url desc origin
vue-tutorial.jpg
What a Vue!
Photo by Fezbot2000
javascript
tutorial
githubproject githubtext
Project used for itenium Vue.js technical session
title
🎑 Vue.js
vue vue-router vuex vue-cli
2.6.6
3.0.1
3.0.1
3.5.3

{% include github-stars.html url="vuejs/vue" desc="Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web." %}

Why Vue

  • 100k+ ⭐: So many people can't be wrong
  • Declarative rendering with a cool, terse syntax
  • Low learning curve
  • Reactive and composable view components
  • Optional official vue-router and vuex (statemanagement)
  • @vue/cli: Scaffold project with optional support for TypeScript, PWA, CSS Pre-processors, Linters, Tests, ...
  • Automatic dependency tracking
  • Virtual DOM
  • Supports IE9+

Official Projects

{% include github-stars.html url="vuejs/vue-router" desc="The official router for Vue.js. " %} {% include github-stars.html url="vuejs/vuex" desc="Centralized State Management for Vue.js." %} {% include github-stars.html url="vuejs/vue-cli" desc="Standard Tooling for Vue.js Development" %} {% include github-stars.html url="vuejs/vue-devtools" desc="⚙ Chrome and Firefox devtools extension for debugging Vue.js applications." %} {% include github-stars.html url="vuejs/vetur" desc="Vue tooling for VS Code." %}

Hello Vue

[HelloVue.html source]({{ 'assets/blog-assets/vue-HelloVue.html' | relative_url}}) {: .title-url}

Html

{% raw %}<script src="https://unpkg.com/vue"></script>

<div id="app">
  <!-- Binding -->
  <h1>{{ product }}</h1>


  <!-- v-bind directive -->
  <img v-bind:src="image" :title="imageTitle">
  <!-- :src, :alt, :title, :class, :style, :disabled -->


  <!-- Conditionals -->
  <div>
    <!-- Falsy will not include in DOM -->
    <p v-if="inventory > 10">In Stock</p>
    <p v-else-if="inventory">Almost sold out</p>
    <p v-else>Out of Stock</p>

    <!-- Render with display: none -->
    <p v-show="inventory < 0">Wuuk?</p>
  </div>


  <!-- Events -->
  <!-- v-on directive shorthand is @ -->
  <button
    v-on:click="cart++; inventory--"
    :disabled="!inventory"
    :class="inventory ? '' : 'disabledButton'"
  >
    Add to Cart
  </button>
  <div class="cart">{{cart}}</div>


  <!-- Loops -->
  <div v-for="variant in variants"
    :key="variant.id"
    :style="{backgroundColor: variant.color}"
    @mouseover="setImage(variant.color, $event)"
  ></div>
</div>{% endraw %}

JavaScript

const vm = new Vue({
    el: '#app',
    data: {
      product: 'Socks',
      image: './assets/socks-green.jpg',
      imageTitle: 'Green Socks',
      variants: [
        {id: 1, color: 'green'},
        {id: 2, color: 'blue'}
      ],
      inventory: 3,
      cart: 0
    },
    methods: {
      addToCart() {
        this.cart++;
        this.inventory--;
      },
      setImage(color, event) {
        console.log('Vue instance with data and methods', this, event);
        this.image = `./assets/socks-${color}.jpg`;
      }
    }
});
console.log('Modify data directly from the console with `vm.product = "Shoes";`');

Templates

Interpolations guide {: .title-url}

Displaying values:

{% raw %}<h1>{{ name.toUpperCase() }}</h1>
<p v-text="name"></p>
<p v-html="rawHtml"></p>{% endraw %}

Computed Properties

Prefer computed properties over doing string formatting in the templates directly as they will only be re-evaluated when a relevant dependency changes.

Computed Properties guide {: .title-url} Usage computed property: {% raw %}<h1>{{ title }}</h1>{% endraw %}:

new Vue({
    data: {name: 'Vuer'},
    computed: {
        title() {
            return this.name.toUpperCase();
        }
    }
});

Attributes

Attributes guide {: .title-url} Binding to attribute with v-bind directive:

<img v-bind:src="val">
<img v-bind:[evaluated]="val">
<img :src="val">

Evaluated should return a bindable property or null.

Shorthands: :src, :alt, :title, :class, :style, :disabled

:style

:style="{fontSize: '13px'}"
:style="{'font-size': styles.size}"
:style="styleObject"
:style="[obj1, obj2]"
  • Use camelCase or kebab-case (quoted)
  • Vendor prefixes are added automatically
  • Can combine html style and Vue :style attributes.

:class

:class="'btn' + btn.type"
:class="[cond ? 'active' : '']"
:class="{active: cond}"

Conditional Rendering guide {: .title-url}

Conditions

{% raw %}<!-- Falsy values will not render -->
<p v-if="inventory > 10">In Stock</p>
<p v-else-if="inventory">Almost sold out</p>
<p v-else>Out of Stock</p>

<!-- Render a block of multiple elements -->
<!-- The template tag itself will not be rendered -->
<template v-if="cond">
    <h1>Title</h1>
    <div>...</div>
</template>

<!-- Falsy values render with display: none -->
<!-- Less DOM changes === better performance -->
<!-- There is no v-else-show and it does not work with templates -->
<p v-show="inventory">In Stock</p>{% endraw %}

v-show

  • Use when you need to toggle something often
  • Always rendered: Costlier initial rendering

v-if

  • Use when the condition is unlikely to change
  • Rendered only when the condition is true

v-once: Will never be updated after initial render.

Lists guide {: .title-url}

Looping

{% raw %}<!-- Render 5 stars -->
<i v-for="i in 5" class="fa fa-star" :key="i"></i>

<!-- Loop through an Array -->
<li v-for="item in ['Gold', 'Silver', 'Bronze']" :key="item">{{ item }}</li>
<li v-for="(item, index) in [1, 2, 3]" :key="item">{{ index + ' ' + item }}</li>

<!-- Loop through an Object -->
<li v-for="(value, key) in {a: 1}" :key="key">{{ value }}</li>
<li v-for="(value, key, index) in {a: 1}" :key="key">{{ value }}</li>{% endraw %}

Like v-if you can use a template to render a block of multiple elements.

:key attribute:

  • Should be a primitive type
  • Recommended unless you want to intentionally rely on the default behavior
  • Required when using v-for with a custom component

Events guide {: .title-url}

Events

<button v-on:click="nr++"></button>
<form @submit="methodName"></form>
<input @keyup.enter="fn(arg1, arg2, ...)">

Event Modifiers

  • @keyup.enter: .enter is called a Key Modifier
  • Modifiers are stackable: .mod1.mod2
  • @keyup.page-down="onPageDown"
  • .prevent == event.preventDefault()
  • .stop == event.stopPropagation(): Shorthand ~.
  • .once: Unregister after first trigger
  • .self: Trigger only when the element is the .target
  • @click.ctrl.exact: Only trigger when ctrl is pressed without any other keys
  • @click.capture: An event on an inner element is handled here before the .target. Shorthand !.
  • .passive: Corresponds to addEventListener's passive option. Shorthand &.

Components

Components guide {: .title-url}

Usage

// Html
// <product :premium="premium" @add-to-cart="updateCart" class="main-prod"></product>

// JavaScript
new Vue({
  data: {
    premium: true,
    cart: [],
  },
  methods: {
    updateCart(product) {
      this.cart.push(product);
    }
  }
});

Declare

{% raw %}const comp = Vue.component('product', {
  // Props with validation
  props: {
    premium: {type: Boolean, required: true, default: false},
    // Types: String, Number, Array, Object, Function, Promise, any ctor
    // Null and undefined always pass validation
    // oneOf: [String, Number],
    // special: {
    //   default() { return 'calculatedValue'; },
    //   validator(value) { return true; }
    // }
  },
  // Props without validation
  // props: ['premium'],
  template: `
    <!-- Must have single root element -->
    <div class="product">
      <h1>{{ product }}</h1>
      <p v-if="premium"><b>FREE Shipping</b></p>

      <button @click="addToCart">
        Add to Cart
      </button>
    </div>
  `,
  data() {
    // data is a function inside a Vue.component()
    // Do return a new object reference each time.
    return {
      product: 'Socks',
      inventory: 3,
    };
  },
  methods: {
    addToCart() {
      this.inventory--;
      this.$emit('add-to-cart', {product: this.product});
    }
  },

  // Local Subcomponent Registration
  // const LocalComponent = {};
  // components: {
  //   'local-component': LocalComponent,
  // }

  // Watches
  watch: {
    iventory(newInventory, oldInventory) {
      // TODO: Call backend to check if product is still available
    }
  },


  // LifeCycle Methods
  created() {
    console.log('Http requests probably here');
  },
  mounted() {},
  updated() {},
  destroyed() {},
  beforeDestroy() {},
});{% endraw %}

Slots

Slots, like React props.children or Angular ng-content.

Slots guide {: .title-url}

Usage

<alert-box>
  <!-- Named Slot -->
  <template v-slot:title>
    <!-- Shorthand #title -->
    Oh noes!
  </template>

  <!-- Default Slot -->
  Something bad happened.
  <!-- Could put default slot into: v-slot:default -->
</alert-box>

Declare

Vue.component('alert-box', {
  template: `
    <div>
      <slot name="title">Error!</slot>
      <slot>Default value</slot>
    </div>
  `
});

Forms

Forms guide {: .title-url}

Html

{% raw %}<form @submit.prevent="onSubmit">
  <input v-model="name" @keydown.ctrl.v.prevent="blockPaste">

  <div v-for="error in errors" :key="error">
    * {{ error }}
  </div>

  <select v-model.number="rating">
    <!-- Other modifiers: -->
    <!-- .lazy => Sync on change event -->
    <!-- .trim => Strip whitespace -->
    <option disabled value="">select</option>
    <option v-for="i in 5" :key="i">{{ i }}</option>
  </select>

  <br>
  <input
    type="checkbox"
    v-model="acceptTerms"
    true-value="yes"
    false-value="no"
  >
  Accept terms

  <button>Submit</button>
</form>{% endraw %}

JavaScript

new Vue({
  data: {
    name: null,
    rating: '',
    acceptTerms: false,
    errors: []
  },
  methods: {
    onSubmit() {
      this.errors = [];
      if (!this.rating) {
        this.errors.push('Please select a rating');
        return;
      }

      const review = {name: this.name, rating: this.rating};
      console.log('Submitting', review);
    },
    blockPaste() {
      console.log('Control + V is not allowed!');
    }
  },
});

Validation not included

{% include github-stars.html url="vuelidate/vuelidate" desc="Simple, lightweight model-based validation for Vue.js" %} {% include github-stars.html url="baianat/vee-validate" desc="Template Based Validation Framework for Vue.js" %}

Caveat

Reuse of <input> elements could lead to strange behavior.

<template v-if="loginType === 'username'">
  <!-- Using `key` so the inputs are not reused when loginType changes -->
  <input placeholder="Enter your username" key="username-input">
</template>

<template v-else>
  <input placeholder="Enter your email address" key="email-input">
</template>

Using v-model on Components {: .title-url}

Custom Model Binding

Vue.component('custom-input', {
    props: ['value'],
    template: `<input :value="value" @input="$emit('input', $event.target.value)">`
})

// Html
// <custom-input v-model="searchText"></custom-input>

new Vue({
    el: '#app',
    data: {
      searchText: ''
    },
});

Reactivity

Array

Vue.js has wrapped array functions so that they are reactive:
push, pop, shift, unshift, splice, sort, reverse

Assigning a new array will reuse DOM elements if possible on the rerender.

It will not detect this.items[0] = 'newVal'. If you must, use:

Vue.set(vm.items, indexOfItem, newValue);
vm.items.splice(indexOfItem, 1, newValue);
vm.$set(vm.items, indexOfItem, newValue);

Objects

Assigning a new property to an object is not reactive. Assign it a default value on initialization.

If you must, use:

Vue.set(vm.userProfile, 'age', 27);
vm.$set(vm.userProfile, 'age', 27);

# Closing Thoughts

I was really surprised by Vue's simplicity and terse syntax due its many shorthands. Tooling in Visual Studio Code and Chrome is really good. The CLI was pretty amazing at setting up a full blown starter project.

So, I learned me some Vue.js for a technical session and am already set on using it for a few small projects.

You can’t perform that action at this time.