Introduction to Vue.js

Introduction

GitHub Repo

  • Why Vue.js

    • Declarative
    • Legible
    • Easy to Maintain
    • Powerful
    • A collection of the best of the best
    • Elegant
    • Gives me what I want when I need it, and gets out of my way
    • A way to be really productive
    • Goodness gracious it’s freaking fun
  • Comparison with other frameworks

    • A virtual DOM
    • Reactive components that offer the View layer only
    • Props and a Redux-like store similar to React
    • Conditional rendering, and services, similar to Angular
    • Inspired by Polymer for simplicity and performance, Vue offers a similar development style as HTML, styles, and JavaScript are composed in tandem
    • Slightly better performance than React, no use of polyfills like Polymer, and an isolated, less opinionated view than Angular, which is an MVC

Course Agenda

  • What this course is:

    • Introduction
    • Stuff to get you going
    • Exercises to help to learn
    • Covering basic premises, and a couple of more advanced features
  • Won’t cover

    • ES6(we’ll use it but won’t explain it)
    • Details - so that we can cover more ground
    • Deployment
    • Edge-cases

Directives & Data Rendering

Vue Instance

  • Intro & Vue Instance

  • Obligatory example

    • Hello World!
1
<div id="app">{{ text }} Nice to meet vue.</div>
1
2
3
4
5
6
new Vue({
el: '#app',
data: {
text: 'Hello world'
}
})

Comparing Vanilla JavaScript

  • Vanilla JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const items = [
'thingie',
'another thingie',
'lots of stuff',
'yadda yadda'
]

function listOfStuff() {
let full_list = ''
items.forEach(item => {
full_list += `<li> ${item} </li>`
})
const contain = document.querySelector('#container')
contain.innerHtml = `<ul> ${full_list} </ul>`
}
  • Vue
    • clean
    • semantic
    • declarative
    • legible
    • easy to maintain
    • reactive
1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
data: {
items: [
'thingie',
'another thingie',
'lots of stuff',
'yadda yadda'
]
}
})
1
2
3
4
5
<div id="app">
<li v-for="item in items">
{{ item }}
</li>
</div>

Directives

  • Directives

    • v-text
    • v-html
    • v-show
    • v-if
    • v-else
    • v-else-if
    • v-for
    • v-on
    • v-bind
    • v-model
    • v-pre
    • v-cloak
    • v-once
  • v-for

    • Loops through a set of values(previous example - item in items)
    • can also do a static number
  • v-model

    • Creates a relationship between the data in the instance/component and a form input, so you can dynamically update values
    • accepting user input and managing it in a responsible manner
    1
    2
    3
    4
    5
    6
    7
    8
    new Vue({
    el: '#app',
    data() {
    return {
    message: 'This is a good place to type things.'
    }
    }
    })
    1
    2
    3
    4
    5
    6
    <div id="app">
    <h3>Type here:</h3>
    <textarea v-model="message" class="message" rows="5" maxlength="72"/>
    <br>
    <p class="booktext">{{ message }}</p>
    </div>
  • Modifiers

    • v-model.trim will strip any leading or trailing whitespace from the bounding string
    • v-model.number changes strings to number inputs
    • v-model.lazy won’t populate the content automatically, it will wait to bind until an event happens.(It listens to change events instead of input)
  • v-if/v-show

    • Is a conditional that will display information depending on meeting a requirement. This can be anything - buttons, forms, divs, components
  • v-if/v-else

    • Pretty straightforward - you can conditionally render one thing or another. There’s also v-else-if
  • v-bind or :

    • One of the most useful directives so there’s a shortcut! We can use it for so many things - class and style binding, creating dynamic props, etc…
  • v-once & v-pre

    • Not quite as useful, v-once will not update once it’s been rendered
    • v-pre will print out the inner text exactly how it is, including code(good for documentation)
  • v-on or @

    • Extremely useful so there’s a shortcut! v-on is great for binding to events like click and mouseenter. You’re able to pass in a parameter for the event like (e)
    • We can also use ternaries directly
  • Modifiers

    • @mousemove.stop is comparable to e.stopPropogation()
    • @mousemove.prevent is like e.preventDefault()
    • @submit.prevent will no longer reload the page on submission
    • @click.once not to be confused with v-once, this click event will be triggered once
    • @click.native so that you can listen to native events in the DOM
  • v-html

    • Great for strings that have html elements that need to be rendered
  • Warnings:

    • Not the same as templates: inserted as plain HTML
    • Don’t use on user-rendered content, avoid XSS attacks
  • v-text

    • Similar to using mustache templates
  • Warning:

    • If you want to dynamically update, it’s recommended to use mustache templates instead
  • Intro to Vue.js: Rendering, Directives, and Events

Challenge: Calculator

exercise link

my solution

Methods, Computed & Watchers

Methods

slides

  • Methods
    • Are bound to the Vue instance, they are incredibly useful for functions you would like to access in directives

Working with Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Vue({
el: '#app',
data: {
newComment: '',
comments: [
'Looks great Julianne!',
'I love the sea.',
'Where are you at?'
]
},
methods: {
addComment() {
this.comments.push(this.newComment)
this.newComment = ''
}
}
})
1
2
3
4
5
6
7
8
9
10
<ul>
<li v-for="comment in comments">
{{ comment }}
</li>
</ul>
<input
v-model="newComment"
@keyup.enter="addComment"
palceholder="Add a comment"
/>

Computed Properties

  • Computed
    • Computed properties are calculations that will be cached and will only update when needed
    • Highly performant but use with understanding
Computed Methods
Runs only when a dependency has changed Runs whenever an update occurs
Cached Not cached
should be used as a property, in place of data Typically invoked from v-on/@, but flexible
By default getter only, but you can define a setter Getter/setter

Challenge: Updating a Blog

exercise

solution

Reactive Programming

  • Watchers & Vue’s Reactivity System

  • What is reactive?

    • Reactive programming is programming with asynchronous data streams
    • A stream is a sequence of ongoing events ordered in time that offer some hooks with which to observe it
    • When we use reactive premises for building applications, this means it’s very easy to update state in reaction to events
    • The introduction to Reactive Programming you’ve been missing
  • What is reactive?

  • In Vue

    • Vue takes the object, walks through its properties and converts them to getter/setters

      1
      2
      3
      4
      5
      new Vue({
      data: {
      text: 'msg'
      }
      })
    • Vue cannot detect property addition or deletion so we create this object to keep track

Watchers

  • Each components has a watcher instance

  • The properties touched by the watcher during the render are registered as dependencies

  • When the setter is triggered, it lets the watcher know, and causes the component to re-render

  • The Vue instance is the middleman between the DOM and the business logic

  • Watch updates the DOM only if it’s required - performing calculations in JS is really performant but accessing the DOM is not. So we have a virtual DOM which is like a copy, but parsed in JavaScript

  • It will only update the DOM for things that need to be changed, which is faster

  • Watchers

    • Good for asynchornous updates, and updates/transitions with data changes
  • We’re going to ‘watch’ any data property declared on the Vue instance

  • When watching a property, you trigger a method on change:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    new Vue({
    el: '#app',
    data() {
    return {
    bottom: false,
    beers: []
    }
    },
    watch: {
    bottom(bottom) {
    if(bottom) {
    this.addBeer()
    }
    }
    }
    })
  • We also have access to the new value and the old value

    1
    2
    3
    4
    5
    watch: {
    watchedProperty(value, oldValue) {
    // your dope code here
    }
    },
  • We can also gain access to nested values with ‘deep’:

    1
    2
    3
    4
    5
    6
    7
    8
    watch: {
    watchedProperty {
    deep: true,
    nestedWatchedProperty(value, oldValue) {
    // your dope code here
    }
    }
    }
  • Transitioning state with watchers

Components

Templates

  • Templates

    • Vue.js uses HTML-based template syntax to bind the Vue instance to the DOM, very useful for components
    • Templates are optional, you can also write render function with optional JSX support
    • We will start with strings(only useful for small cases) and then to scirpt, and then to single-file templates in the next section
  • Start with a simple template(not terribly useful)

    1
    2
    3
    4
    const app = new Vue({
    el: '#app',
    template: '<div>Hello world!</div>'
    })
    1
    <div id="app"></div>
  • 上面的代码当页面复杂起来时可读性明显不强,也许我们需要下面的语法来增加可读性

    1
    2
    3
    4
    5
    6
    7
    8
    <header></header>
    <aside>
    <sidebar-item v-for="item in items"></sidebar-item>
    </aside>
    <main>
    <blogpost v-for="post in posts"></blogpost>
    </main>
    <footer></footer>

Introducing Components

  • Components

    • A collection of elements that are encapsulated into a group that can be accessed through one single element
  • Simplest Component

    1
    2
    3
    4
    5
    6
    7
    Vue.component('child', {
    template: '<div>Hello world!</div>'
    })

    new Vue({
    el: '#app'
    })
    1
    2
    3
    <div id="app">
    <child></child>
    </div>
  • Slightly better, with props

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Vue.component('child', {
    props: ['text'],
    template: `<div>{{ text }}</div>`
    })

    new Vue({
    el: '#app',
    data() {
    return {
    message: 'Hello world!'
    }
    }
    })
    1
    2
    3
    <div id="app">
    <child :text="message"></child>
    </div>

Props

  • Props

    • Passing data down from the parent to the child

      • props: ['text']
    • Props are intended for one way communication

    • You can think of it a little like the component holds a variable that is waiting to be filled out by whatever the parent sends down to it

    • Types & validation

      • props: { text: [String, Number] }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        Vue.component('child', {
        props: {
        text: {
        type: String,
        required: true,
        default: 'Hello world!'
        }
        },
        template: `<div>{{ text }}</div>`
        })
    • Note: Objects and arrays need their defaults to be returned from a function:

      1
      2
      3
      4
      5
      6
      text: {
      type: Object,
      default() {
      return { message: 'Hello world!' }
      }
      }
  • You don’t need to necessarily pass the data in props to the child, either, you have the option of using Vue instance data or a static value as you see fit

    • We use v-bind or : to dynamically bind props to data on the parent
  • Each component instance has its own isolated scope

    • data must be a function
  • camelCasing will be converted

    • props: ['booleanValue']
  • In HTML it will be kebab-case

    • <checkbox :boolean-value="booleanValue"></checkbox>

Refactoring into a Component

原文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
el: '#app',
data() {
return {
newComment: '',
comments: [
'Looks great Julianne!',
'I love the sea',
'Where are you at?'
]
}
},
methods: {
addComment() {
this.comments.push(this.newComment)
this.newComment = ''
}
}
})
1
2
3
4
5
6
7
8
9
10
<ul>
<li v-for="comment in comments">
{{ comment }}
</li>
</ul>
<input
v-model="newComment"
v-on:keyup.enter="addComment"
placeholder="Add a comment"
/>
组件化为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Vue.component('individual-comment', {
template: `<li> {{ commentpost }} </li>`,
props: ['commentpost']
})

new Vue({
el: '#app',
data: {
newComment: '',
comments: [
'Looks great Julianne!',
'I love the sea',
'Where are you at?'
]
},
methods: {
addComment() {
this.comments.push(this.newComment)
this.newComment = ''
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<li
is="individual-comment"
v-for="comment in comments"
v-bind:commentpost="comment"
></li>
</ul>
<input
v-model="newComment"
v-on:keyup.enter="addComment"
placeholder="Add a comment"
/>
  • In JS
1
2
3
4
Vue.component('individual-comment', {
template: '#comment-template',
props: ['commentpost']
})
  • In HTML
1
2
3
4
5
6
7
<script type="text/x-template" id="comment-template">
<li>
<img class="post-img" v-bind:src="commentpost.authorImg" />
<small>{{ commentpost.author }}</small>
<p class="post-comment">"{{ commentpost.text }}"</p>
</li>
</script>
  • Another option: Local component
    • we can do it since it’s all one file
1
2
3
4
5
6
7
8
9
10
11
const IndividualComment = {
template: '#comment-template',
props: ['commentpost']
}

new Vue({
// ...
components: {
'individual-commnet': IndividualComment
}
})

Communicating Events

1
<my-component @myEvent="parentHandler"></my-component>
1
2
3
4
5
methods: {
fireEvent() {
this.$emit('myEvent', eventValueOne, eventValueTwo)
}
}
  • $emit

    • The child is reporting it’s activity to the parent
  • JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.component('child', {
props: ['text'],
template: '#child',
methods: {
talkToMe() {
// this.text = 'forget introductions, I want a taco' // 更改为以下
this.$emit('changetext', 'forget introductions, I want a taco')
}
}
})

new Vue({
el: '#app',
data() {
return {
message: 'hello mr. magoo'
}
}
})
  • HTML
1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<!-- <child :text="message"></child> 更改为以下 -->
<child :text="message" @changetext="message = $event"></child>
</div>

<script type="text/x-template" id="child">
<div>
<p>{{ text }}</p>
<button @click="talkToMe">Let's talk</button>
</div>
</script>

Slots

1
2
3
4
5
6
<script type="text/x-template" id="childarea">
<div class="child">
<slot><slot>
<p>It's a veritable slot machine!</P>
</div>
</script>
1
2
3
4
5
6
7
8
9
10
<div id="app">
<h2>We can use slots to populate content</h2>
<app-child>
<h3>This is slot number one</h3>
</app-child>
<app-child>
<h3>This is slot number two</h3>
<small>I can put more info in, too!</small>
</app-child>
</div>
  • Slots Example

  • You can have defaults

    • <slot>I am some default text</slot>
  • You can have more than one

    • Or just keep things tidy by naming them

      1
      2
      <slot name="headerinfo"></slot>
      <h1 slot="headerinfo">I will populate the headerinfo slot</h1>
      1
      2
      3
      4
      5
      6
      <div id="post">
      <main>
      <slot name="header"></slot>
      <slot></slot>
      </main>
      </div>
      1
      2
      3
      4
      <app-post>
      <h1 slot="header">This is the main title</h1>
      <p>I will go in the unnamed slot!</p>
      </app-post>
      1
      2
      3
      4
      <main>
      <h1>This is the main title</h1>
      <p>I will go in the unnamed slot!</p>
      </main>
  • slot example

Keep-Alive

1
2
3
4
5
<keep-alive>
<component :is="selected">
<!-- ... -->
</component>
</keep-alive>

Challenge: Refactoring into a Component

exercise

solution

Vue CLI & Nuxt.js

Introducing Vue ClI

  • Why
    • Building precesses that allow us to use great features like ES6, or Sass, easy to use other libraires
    • We’re going to build and concatenate single file templates, which are AWSOME(not biased)
    • Not load all our files at startup(lazy load, async)
    • Server-side rendering, code-splitting, performance metrics…
    • Build/prod versions
1
2
3
4
5
6
7
npm install -g vue-cli

vue init webpack-simple my-project
cd my-project
yarn

npm run dev
  • (eventually we’ll ues webpack not webpack-simple)

  • Single File Templates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<!-- write your HTML with Vue in here -->
</div>
</template>

<script>
export default {
// write your Vue component logic here
}
</script>

<style scoped>
/* write your styles for the component in here */
</style>
  • Vue files mean to context-shifting
1
2
3
4
5
6
7
import New from './components/New.vue'

export default {
components: {
appNew: New
}
}
1
<app-new></app-new>

Vue CLI Walkthrough

  • You’ll start off with an App file in your /src/ directory with a Hello.vue file in the /components/ directory. This is really nice beaucase you can see already how you’d set up these files, and how imports and exports might work

Vue Snippets

  • Snippets save lives
1
<style scoped></style>
  • This allow us to very easily scope the styles for this component to only this component

    • @import styles: vue-style-loader
  • slots in Vue components with the scoped style tags, they apply to the component that has the slots

1
2
3
4
5
<template>
<div>
<slot></slot>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style scoped>
.label {
fill: black;
}
.bottle, .wine-text {
fill: white;
}
.flor {
fill: #ccc;
}
.bkimg {
filter:url(#inverse)
}
</style>

Lifecycle Hooks

  • The lifecycle hooks provide you a method so that you might trigger something precisely at different junctures of a component’s lifecycle. Components are mounten when we instantiate them, and in trun unmounted, for instance when we toggle them in a v-if/v-else statement

    • beforeCreate
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • activated
    • deactivated
    • beforeDestory
    • destoryed
    • errorCaptured(2.5.0+)
  • Demystifying Vue Lifecycle Methods

  • newVue()

  • beforeCreate()

    • observe data && init events
  • created()

    • template options and render
  • beforeMount()

    • create virtual DOM el and repalce ‘el’ with it
  • mounted()

    • beforeUpdate()
      • virtual DOM and patch
    • updated()
    • activated()
      • keep-alive component reactivated
    • deactivated()
  • beforeDestory()

    • teardown watchers, child components, event listeners
  • destoryed()

  • Lifecycle hooks also auto-bind the instance so that you can use the component’s state, and methods. Again, you don’t have to console.log to find out what this refers to!

  • For this reason though, you shouldn’t use an arrow function on a lifecycle method, as it will return the parent instead of giving you nice binding out of the box.

Introducing Nuxt.js

  • Why?
    • Automatic Code Splitting
    • Server-Side Rendering
    • Powerful Routing System with Asynchornous Data
    • Static File Serving
    • ES6/ES7 Transpilation
    • Hot reloading in Development
    • Pre-processor: SASS, LESS, Stylus, etc
    • Write Vue files

Nuxt.js Application Walkthrough

1
2
3
4
5
6
7
npm install -g vue-cli

vue init nuxt/starter my-project
cd my-project
yarn

npm run dev

Animations

Introducing Animations

  • Transitions vs. Animations

  • Transition

    • Modal Example
1
2
3
4
5
6
<script type="text/x-template" id="childarea">
<div>
<h2>Here I am!</h2>
<slot></slot>
</div>
</script>
1
2
3
4
5
6
7
8
9
10
<div id="app">
<h3>Let's trigger this here modal</h3>
<button @click="toggleShow">
<span v-if="isShowing">Hide child</span>
<span v-else>Show child</span>
</button>
<app-child v-if="isShowing" class="modal">
<button @click="toggleShow">Close</button>
</app-child>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Child = {
template: 'childarea'
}

new Vue({
el: '#app',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing
}
},
components: {
appChild: Child
}
})
1
2
3
4
5
6
7
<transition name="fade">
<app-child v-if="isShowing" class="modal">
<button @click="toggleShow">
Close
</button>
</app-child>
</transition>
  • Default ‘v-‘ prefix, otherwise name=”name”

  • Example

    1
    2
    3
    .v-enter-active {
    transition: color 1s;
    }
  • Reusable for other components

    1
    2
    3
    4
    5
    6
    .fade-enter-active, .fade-leave-active {
    transition: opacity 0.25s ease-out;
    }
    .fade-enter, .fade-leave-to {
    opacity: 0;
    }
    • This is unnecessary, as it’s default

      1
      2
      3
      .fade-enter-to, .fade-leave {
      opacity: 1;
      }
  • CodePen demo

  • Great

    • But what would happen if we wanted to make that background content fade out of view, so that the modal took center stage and the background lost focus?

    • Can’t use the transition component

      1
      2
      3
      4
      5
      6
      7
      <div :class="[isShowing ? blurClass : '', bkClass]">
      <h3>Let's trigger here modal!</h3>
      <button @click="toggleShow">
      <span v-if="isShowing">Hide child</span>
      <span v-else>Show child</span>
      </button>
      </div>
      1
      2
      3
      4
      5
      6
      7
      8
      .bk {
      transition: all 0.1s ease-out;
      }

      .blur {
      filter: blur(2px);
      opacity: 0.4;
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      new Vue({
      el: '#app',
      data() {
      return {
      isShowing: false,
      bkClass: 'bk',
      blurClass: 'blur'
      }
      },
      ...
      })

CSS Animation

  • Still <transition /> component, but

    • enter-active-class=”toasty”

    • leave-active-class=”bounceOut”

      1
      2
      3
      .toasty {
      toasty 1s ease both;
      }
  • Can also hook into CSS animation libraries this way

  • Bounce a ball

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <div id="app">
    <h3>Bounce the Ball!</h3>
    <button @click="toggleShow">
    <span v-if="isShowing">Get it gone!</span>
    <span v-else>Here we go!</span>
    </button>
    <transition
    name="ballmove"
    enter-active-class="bouncein"
    leave-active-class="rollout">
    <div v-if="isShowing">
    <app-child class="child"></app-child>
    </div>
    </transition>
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @mixin ballb($yaxis: 0) {
    transform: translate3d(0, $yaxis, 0);
    }

    @keyframes bouncein {
    1% { @include ballb(-400px); }
    20%, 40%, 60%, 80%, 95%, 99%, 100% { @include ballb() }
    30% { @include ballb(-80px); }
    50% { @include ballb(-40px); }
    70% { @include ballb(-30px); }
    90% { @include ballb(-15px); }
    97% { @include ballb(-10px); }
    }

    .bouncein {
    animation: bouncein 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
    }

    .ballmove-enter {
    @include ballb(-400px);
    }
  • CodePen demo

Challenge: Adding Animation

exercise

Transition Mode

  • in-out
    • The current element waits until the new element is done transitioning in to fire
  • out-in
    • The current element transitions out and then the new element transitions in
1
2
3
4
<transition name="flip" mode="out-in">
<slot v-if="!isShowing"></slot>
<img v-else src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/cartoonvideo14.jpeg" />
</transition>
  • Vue transition modes

  • Vue in-out modes - no modes contrast

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .flip-enter-active {
    transition: all .2s cubic-bezier(0.55, 0.085, 0.68, 0.53); //ease-in-quad
    }

    .flip-leave-active {
    transition: all .25s cubic-bezier(0.25, 0.46, 0.45, 0.94); //ease-out-quad
    }

    .flip-enter, .flip-leave-to {
    transform: scaleY(0) translateZ(0);
    opacity: 0;
    }

JavaScript Hooks

  • Custom Naming

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @enter-cancelled="enterCancelled"

    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    @leave-calcelled="leaveCancelled"
    :css="false"
    >
    </transition>
  • Most basic example

    1
    2
    3
    4
    5
    6
    7
    <transition
    @enter="enterEl"
    @leave="leaveEl"
    :css="false"
    >
    <!-- put element here -->
    </transition>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    methods: {
    enterEl(el, done) {
    // entrance animation
    done()
    },
    leaveEl(el, done) {
    // exit animation
    done()
    }
    }
  • Vue Book Content Typer

code
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
new Vue({
el: '#app',
data() {
return {
message: 'This is a good place to type things.',
load: false
}
},
methods: {
beforeEnter(el) {
TweenMax.set(el, {
transformPerspective: 600,
perspective: 300,
transformStyle: "preserve-3d",
autoAlpha: 1
});
},
enter(el, done) {
...
tl.add("drop");
for (var i = 0; i < wordCount; i++) {
tl.from(split.words[i], 1.5, {
z: Math.floor(Math.random() * (1 + 150 - -150) + -150),
ease: Bounce.easeOut
}, "drop+=0." + (i/ 0.5));
...
}
}
})

Connect to Interaction

Simple Transition

  • Progress bar

    • nuxt.config.js
    1
    2
    3
    4
    /*
    ** Customize the progress-bar color
    */
    loading: { color: '#3B8070' },
    • to
    1
    2
    loading: false,
    css: ['assets/normalize.css', 'assets/main.css'],
    • and create assets/normalize.css, and blank assets/main.css
    1
    2
    3
    4
    5
    6
    7
    8
    .page-enter-active, .page-leave-active {
    transition: all .25s ease-out;
    }
    .page-enter, .page-leave-active {
    opacity: 0;
    transform: scale(0.95);
    transform-origin: 50% 50%;
    }

Page-Specific Transitions

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="container">
<h1>This is the content page</h1>
<p><next-link to="/">Home page</next-link></p>
</div>
</template>

<script>
export default {
transition: 'fadeOpacity'
}
</script>
  • Either assets/main.css or <scope style> within the template
1
2
3
4
5
6
7
.fadeOpacity-enter-active, .fadeOpacity-leave-active {
transition: opacity 0.35s ease-out;
}

.fadeOpacity-enter, .fadeOpacity-leave-active {
opacity: 0;
}
  • Page-specific JavaScript hooks
Review
1
2
3
4
5
6
7
8
9
10
11
12
13
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"

@before-leave="beforeLeave"
@leave="leave"
@after-leave="leave"
@leave-cancelled="leaveCancellled"
:css="false"
>
</transition>
1
2
3
4
5
6
7
8
9
10
methods: {
enter(el, done) {
// entrance animation
done()
},
leave(el, done) {
// exit animation
done()
}
}
  • Available with nuxt.js
  • What about the single element?
1
2
3
4
5
6
7
8
9
10
11
enter(el, done) {
TweenMax.to('h1', 1, {
color: 'red'
})
TweenMax.to(el, 1, {
rotationY: 360,
transformOrigin: '50% 50%',
ease: Back.easeOut
})
done()
},
  • or Lifecycle Hooks
1
2
3
4
5
mounted() {
TweenMax.to('h1', 1, {
color: 'red'
})
}

Planning & Fancy Demo

  • Things that you need to stay constant can live outside of the pages
layouts/default.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<nuxt />
</div>
</template>

<script>
import Navigation from '-components/Navigation.vue'

export default {
components: {
Navigation
}
}
</script>

Filters, Mixins & Custom Directives

  • Current Slide

  • The first thing to understand about filters is that they aren’t replacements for methods, computed values, beaucase filters don’t transform the data, just the output that the user sees.

  • Two ways to register a new filter:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // global
    Vue.filter('filterName', function(value) {
    return // thing to transform
    })

    // locally, like methods or computed

    filters: {
    filterName(value) {
    return // thing to transform
    }
    }
  • Use it like this:

    1
    {{ data | filter }}
  • In JS:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    new Vue({
    el: '#app',
    data() {
    return {
    customer1total: 35.43
    }
    },
    filters: {
    tip15(value) {
    return (value * 0.15).toFixed(2)
    }
    }
    })
  • In HTMl:

    1
    2
    3
    4
    5
    <div id="app">
    <h2>Tip Calculator</h2>
    <p><strong>Total: {{ customer1total }}</strong></p>
    <p>15%: {{ customer1total | tip15 }}</p>
    </div>
  • CodePen demo

  • You can chain them:

    1
    {{ data | filterA | filterB }}
  • The first will be applied first, second after

  • Chained filters

  • You can pass arguments:

    1
    {{ data | filterName(arg1, arg2) }}
    1
    2
    3
    4
    5
    6
    // arguments are passed in order after the value
    filters: {
    filterName(value, arg1, arg2) {
    return // thing to transform
    }
    }
  • Filters sounds like it would be good to filter a lot of data, but filters are rerun on every single update, so better to use computed, for values like these that should be cached

Mixins

  • It’s a common situation: you have two components that are pretty similar, they share the same basic functionality, but there’s enough that’s different about each of them that you come to a crossroads: do I split this component into two different components? Or do I keep one component, but create enough variance with props that I can alter each one?

  • OO makes code understandable by encapsulating moving parts. FP makes code understandable by minimizing moving parts. - Micheal Feathers on Twitter

  • A mixin allows you to encapsulate one piece of functionality so that you can use it in different components throughout the application

  • If written correctly, they are pure - they don’t modify or change things outside of the function’s scope, so you will reliably always receive the same value with the same inputs on multiply executions

  • This can be really powerful

  • A simple usecase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// modal
const modal = {
template: '#modal',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing
}
},
components: {
appChild: Child
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// tooltip
const Tooltip = {
template: '#tooltip',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing
}
},
components: {
appChild: Child
}
}
  • Extract the functionality
1
2
3
4
5
6
7
8
9
10
11
12
const toggle = {
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Modal = {
template: '#modal',
mixins: [toggle],
components: {
appChild: Child
}
}

const Tooltip = {
template: '#tooltip',
mixins: [toggle],
components: {
appChild: Child
}
}
  • CodePen demo - Mixin

  • Some examples

    • getting dimensions of the viewport and component
    • gathering specific mousemove events
    • base elements of charts
  • Example repo by Paul Pflugradt

  • Merging with mixins

    • By default, mixins will be applied first, and the component will be applied second so that we can override it as necessary
    • The component has the last say
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// mixin
const hi = {
mounted() {
console.log('Hello from mixin')
}
}
// Vue instance or component
new Vue({
el: '#app',
mixins: [hi],
mounted() {
console.log('Hello from Vue instance')
}
})
// Out put in console
// Hello from mixin
// Hello from Vue instance
  • Global mixins!
    • Global mixins are literally applied to every single component. One use I can think of that makes sense is something like a plugin, where you may need to gain access to everything
    • But still, the use case for them is extremely limited and they should be considered with great caution
1
2
3
4
5
6
7
8
9
Vue.mixin({
mounted() {
console.log('Hello from mixin')
}
})

new Vue({
...
})
  • This console.log would now appear in every component

Custom Directives

1
<p v-tack>I will now be tracked onto the page</p>
1
2
3
4
5
Vue.directive('tack', {
bind(el, binding, vnode) {
el.style.position = 'fixed'
}
})
  • Directive deep dive

    • v-example - this will instantiate a directive, but doesn’t accept any argument. Without passing a value, this would not be very flexible, but you could still hang some piece of functionality off of the DOM element

    • v-example="value" - this will pass a value into the directive, and the directive figures out what to do based off of that value

      • <div v-if="stateExample">I will show up if stateExample is true</div>
    • v-example="string" - this will let you use a string as an expression

      • <p v-html="'<strong>this is an example of a string in some text</strong>'"></p>
    • v-example:arg="value" - this allows us to pass in an argument to the directive. In the example below, we’re binding to a class, and we’d style it with an object, stored separately

      • <div v-bind:class="someClassObject"></div>
    • v-example:arg.modifier="value" - this allows us to use a modifier. The example below allows us to call preventDefault() on the click event

      • <button v-on:submit.prevent="onSubmit"></button>
      1
      2
      3
      4
      <div id="app">
      <p>Scroll down the page</p>
      <p v-track="70">Stick me 70px from the top of the page</p>
      </div>
      1
      2
      3
      4
      5
      6
      Vue.directive('tack', {
      bind(el, binding, vnode) {
      el.style.position= 'fixed'
      el.style.top = binding.value + 'px'
      }
      })
  • Passing an argument

    1
    <p v-tack:left="70">I'll now be offset from the left instead of the top</p>
    1
    2
    3
    4
    5
    6
    7
    Vue.directive('tack', {
    bind(el, binding, vnode) {
    el.style.position = 'fixed'
    const s = (binding.arg === 'left' ? 'left' : 'top')
    e.style[s] = binding.vlaue + 'px'
    }
    })
  • Simple directive with arg

  • More than one value

    1
    <p v-tack="{ top: '40', left: '100' }">Stick me 40px from the top of the page  and 100px from the left of the page</p>
    1
    2
    3
    4
    5
    6
    7
    Vue.directive('tack', {
    bind(el, binding, vnode) {
    el.style.position = 'fixed'
    el.style.top = binding.value.top + 'px'
    el.style.left = binding.value.left + 'px'
    }
    })
  • Simple directive with two values

  • LET’S MAKE SOMETHING

    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
    Vue.directive('scroll', {
    inserted(el, binding) {
    let f = (evt) => {
    if(binding.value(evt, el)) {
    window.removeEventListener('scroll', f)
    }
    }
    window.addEventListener('scroll', f)
    }
    })

    // main app
    new Vue({
    el: '#app',
    methods: {
    handleScroll(evt, el) {
    if(window.scrollY > 50) {
    TweenMax.to(el, 1.5, {
    y: -10,
    opacity: 1,
    ease: Sine.easeOut
    })
    }
    return window.scrollY > 100
    }
    }
    })
    1
    2
    3
    <div class="box" v-scroll="handleScroll">
    <p>Lorem ipsum dolor ist amet, consectetur adipisicing elit. a</p>
    </div>
  • Custom Scroll Directive

Vuex

Introducing Vuex

  • current slide

  • What is Vuex

    • Centralized store for shared data and logic, even shared methods or async
    • Unidirectional data flow
    • Influenced by Flux application architecture
    • Similar to Redux
    • You can still use Redux if you like but this is Vue’s version
  • Why use Vuex?

    • In a complex single page application, passing state between many components, and especially deeply nested or sibling components, can get complicated quickly. Having one centralized place to access your data can help you stay organized
  • When to use Vues?

    • “You just konw”
    • Or multiple instances of children/siblings communicating
    • Or I’d like to “see” what all of the state looks like and keep it organized in one place
    • Warning: not a replacement for single component methods

Examining a Vuex Setup

  • How to use Vuex
1
2
3
npn i --save vuex
#or
yarn add vuex
  • I set it up this way: within my /src/ directory, I create another directory named store(this is a preference, you could also just create a store.js file in that same directory), and a file named store.js

  • The initial set up in store.js would look something like this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import Vue from 'vue'
    import Vuex from 'vuex'

    Vue.use(Vuex)

    export const store = new Vuex.store({
    state: {
    key: value
    }
    })
  • In our main.js file, we’d perform the following updates

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import Vue from 'vue'
    import App from './App.vue'

    import { store } from './store/store' // add this

    new Vue({
    el: '#app',
    store: store, // add this
    template: '<App />',
    components: { App }
    })
  • Getters will make values able to show statically in our templates. In other words, getters can read the value, but not mutate the state

  • Mutations will allow us to update the state, but they will always be synchornous. Mutations are the only way to change data in the state in the store

  • Actions will allow us to update the state, asynchronously, but will use an existing mutation. This can be very helpful if you need to perform a few different mutations at once in a particular order, or reach out to a server

  • We separate actions and mutations because we don’t want to get into an ordering problem

Basic Abstract 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
28
export const store = new Vuex.Store({
state: {
counter: 0
},
// showing things, not mutating state
getters: {
tripleCounter: (state) => {
return state.counter * 3
}
},
// mutating the state
// mutations are always synchronous
mutations: {
increment: (state, num) => {
state.counter += num
}
},
// commits the mutation, it's asynchronous
actions: {
// showing passed with payload, represented as asyncNum(an object)
asyncIncrement: ({commit}, asyncNum) => {
setTimeout(() => {
// the asyncNum objects could also just be static amounts
commit('increment', asyncNum.by)
}, asyncNum.duration)
}
}
})
  • Actions

    • We have to pass in the context: context.state, context.getter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      actions: {
      // increment(context) {
      // constext.commit('increment')
      // }
      // destructure it
      increment({context}) {
      commit('increment')
      },
      asyncIncrement({commit}, duration) {
      setTimeout(() => {
      commit('increment')
      }, duration)
      }
      }
      1
      2
      3
      4
      5
      6
      // in component
      methods: {
      asyncIncrement() {
      this.$store.dispatch('asyncIncrement', { duration: 1000 })
      }
      }
  • On the component itself, we would use computed for getters(this makes sense because the value is already computed for us), and methods with commit to access the mutations, and methods with dispatch for the actions

  • In the Vue instance or a component:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    computed: {
    value() {
    return this.$store.getters.tripleCounter
    }
    },
    methods: {
    increment() {
    this.$store.commit('increment', 2)
    },
    asyncIncrement() {
    this.$store.dispatch('asyncIncrement', 2)
    }
    }
  • Or, you can use a spread operator, useful when you have to work with a lot of getters/mutations/actions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { mpaActions } from 'vuex'

    export default {
    // ...
    methods: {
    ...mapActions([
    // map this.increment() to this.$store.commit('increment')
    'increment',
    'decrement',
    'asyncIncrement'
    ])
    }
    }
  • This allows us to still make our own computed properties if we wish

Vuex Example Walkthrough

Wrapping Up