INTRODUCTION TO VUE
LET'S START WITH A DEMO
OUTLINE
-
Introduction
-
DOM Interactions
-
Components
-
Computed properties
-
Handling Events
-
The Vue Instance
-
Vue CLI
-
Vue Router
-
Handling Forms
-
Directives and Filters
INTRODUCTION
Introduction
Over the last 10 years, web pages became more and more powerful thanks to JavaScript 💪
JavaScript code moved from server side to client side (browser)
Introduction
= POOR JS CODE 😓
Introduction
-
Approchable
-
Versatile
-
Performant
-
Maintanable
-
Testable
Introduction
Introduction
Component based development
<body>
<tweets>
<tweet text="This is my 1st tweet!"><tweet>
<tweet text="This is my 2nd tweet!"><tweet>
<tweet text="This is my 3rd tweet!"><tweet>
</tweets>
</body>
Component based development
<!-- tweet component -->
<div class="tweet-container">
<div class="username">@johndoe</div>
<div class="tweet-text">This is my 1st tweet!</div>
</div>
Introduction
Component based development
A component is an encapsulated set of behaviours / features / logics
A component has a defined interface that allows creation / reusability
Introduction
Component based development
ALL major JS frameworks use COMPONENTS
Introduction
DOM INTERACTIONS
DOM Interactions
Text rendering
<div id="app">
<span>Hello {{ name }}!</span>
</div>
new Vue({
el: '#app',
data() {
return {
name: 'John Doe'
}
}
})
DOM Interactions
Text rendering
<div id="app">
<span>Hello {{ name }}!</span>
</div>
when name value changes, the HTML is updated
data object is reactive
DOM Interactions
Text rendering
<span>{{ const name = 'John Doe' }}</span>
A binding ({{ ... }}) can only contain 1 single expression
⚠️ Not working! This is a statement, not an expression
DOM Interactions
Attributes
<button v-bind:disabled="buttonDisabled">Click me</button>
<!-- or -->
<button :disabled="buttonDisabled">Click me</button>
// ...
data() {
return {
buttonDisabled: true
}
}
DOM Interactions
Attributes
<a :href="url">Google</a>
// ...
data() {
return {
url: 'https://www.google.com'
}
}
DOM Interactions
Class binding
<div :class="{ 'is-active': isActive }"></div>
// ...
data() {
return {
isActive: true
}
}
is-active will be added to the div if isActive is truthy
DOM Interactions
Style binding
<div :style="{ width: width, backgroundColor: red }"></div>
// ...
data() {
return {
width: '100px',
red: '#FF0000'
}
}
DOM Interactions
Conditional rendering
<div v-if="quantity === 0">Nothing</div>
<div v-else-if="quantity > 5">Some</div>
<div v-else-if="quantity > 10">Some more</div>
<div v-else>Many</div>
v-else-if must follow v-if directive
v-else must follow v-if or v-else-if directive
DOM Interactions
Conditional rendering
<div v-show="isVisible">Hey there!</div>
v-show will always render the element unlike v-if
(v-show only toggles display css property)
DOM Interactions
List rendering
<ul>
<li v-for="name in names">{{ name }}</li>
</ul>
// ...
data() {
return {
names: ['John', 'Jane', 'Alice']
}
}
v-for ... of also works (closer to JavaScript standard)
DOM Interactions
List rendering
<ul>
<li v-for="(name, index) in names">
{{ index }} - {{ name }}
</li>
</ul>
// ...
data() {
return {
names: ['John', 'Jane', 'Alice']
}
}
DOM Interactions
List rendering
<ul>
<li v-for="(prop, key) in user">
{{ key }}: {{ prop }}
</li>
</ul>
// ...
data() {
return {
user: {
name: 'John',
age: 22,
state: 'CA, California'
}
}
}
COMPONENTS
Components
The most powerful feature of Vue
Components
Global registration
// Register before root Vue instance
Vue.component('hello-world', {
template: '<div>Hello, World!</div>'
})
new Vue({
el: '#app'
})
<div id="app">
<hello-world></hello-world>
</div>
Components
Local registration
const HelloWorld = {
template: '<div>Hello, World!</div>'
}
new Vue({
// ...
components: {
// Only available in parent's template
'hello-world': HelloWorld
}
})
Components
.vue files
⚠️ Available with Webpack
and vue-loader
Components
.vue files
Components
Communication
Components
Props
Every component instance has its own isolated scope
A prop is a custom template attribute
<template>
<span>{{ message }}</span>
</template>
<script>
export default {
props: ['message']
})
</script>
HelloWorld.vue
Components
Static props
<!-- If registered as HelloWorld component -->
<HelloWorld message="Hello, World!"></HelloWorld>
Hello, World!
⚠️ When using non-string templates, camelCased prop names need to use their kebab-case equivalents
(does not apply to .vue files)
Components
Dynamic props
helloMessage is dynamic and comes from HelloWorld's parent component
<HelloWorld v-bind:message="helloMessage"></HelloWorld>
<!-- or -->
<HelloWorld :message="helloMessage"></HelloWorld>
Components
Props validation
// ...
props: {
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
},
propD: {
type: Number,
default: 100
}
}
A prop type can be:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
Components
slots
Sometimes, components needs to be composed this way
<Header />
<Checkout>
<Item name="product1"></Item>
<Item name="product2"></Item>
<Item name="product3"></Item>
</Checkout>
Content distribution: includes the parent “content” inside the component’s own template
Components
slots
<!-- Checkout component template -->
<div>
<h2>Checkout</h2>
<slot>
<!-- only visible if no content to be distributed -->
<span>There's no item in your cart</span>
</slot>
</div>
<div>
<h2>Checkout</h2>
<span>product1</span>
<span>product2</span>
<span>product3</span>
</div>
<Checkout>
<Item name="product1"></Item>
<Item name="product2"></Item>
<Item name="product3"></Item>
</Checkout>
COMPUTED PROPERTIES
Computed properties
<div>
{{ message.split('').reverse().join('') }}
</div>
Sometimes, you need to transform data for display
In-template expressions are convenient but this 👆 is bad
👉 USE COMPUTED PROPERTIES FOR COMPLEX LOGIC
Computed properties
<div>
<span>{{ message }}</span>
<span>{{ reversedMessage }}</span>
</div>
A computed property is a reactive function that returns data in another form
// ...
data() {
return { message: 'Hello, World!' }
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
Hello, World! !dlroW ,olleH
Computed properties
<div>
<span>{{ message }}</span>
<span>{{ reversedMessage }}</span>
</div>
The value of reversedMessage will always be dependent of message
👇
Hello, World! !dlroW ,olleH
Every time message changes, reversedMessage is updated
Computed properties
this.reversedMessage // Ok -> !dlroW ,olleH
this.reversedMessage() // Error
A computed property is a getter function (can't pass arguments)
Computed properties
<span>{{ greetings() }}</span>
It is possible to call a method in a template
// ...
data() {
return { name: 'John Doe' }
},
methods: {
greetings() {
return `Hello, ${this.name}!`
}
}
Computed properties
<span>{{ greetings }}</span>
But here 👇, better use a computed property
// ...
data() {
return { name: 'John Doe' }
},
computed: {
greetings() {
return `Hello, ${this.name}!`
}
}
Computed properties
A computed property is cached based on its dependencies
A method will be called on every re-render phase
WHEREAS
HANDLING EVENTS
Handling events
<span>{{ counter }}</span>
<button v-on:click="add">Add 1</button>
<!-- or -->
<button @click="add">Add 1</button>
Listening to DOM events
// ...
data() {
return { counter: 0 }
},
methods: {
add() {
this.counter++
}
}
Handling events
<button @click="hello('John')">Hello</button>
Listening to DOM events
// ...
methods: {
hello(name) {
console.log(`Hello ${name}`)
}
}
Event's listeners (= methods) can have arguments
Handling events
<button @click="intercept('hello', $event)">
Intercept me!
</button>
Listening to DOM events
// ...
methods: {
intercept(msg, $event) {
console.log(msg)
$event.preventDefault()
}
}
$event gives access to the native DOM event
Handling events
<!-- Child component template -->
<button @click="add">Add 1</button>
Communication from child to parent component
// Child component instance
// ...
methods: {
add() {
this.$emit('addNumber', 1)
}
}
Child component emits a custom event
Handling events
<!-- Parent component template -->
<Child @addNumber="add"></Child>
Communication from child to parent component
// Parent component instance
// ...
methods: {
add(value) { // Here value is 1
this.counter += value
}
}
Parent component listens to child custom event
THE VUE INSTANCE
The Vue Instance
import Vue from 'Vue'
import App from './App'
// vm: ViewModel
const vm = new Vue({
el: '#app',
template: '<App />',
components: { App },
// other options
})
Creating a Vue Instance
The Vue Instance
Root Instance
App
Header
Week
Movie
TimeFilter
GenreFilter
CountryFilter
Movie
Movie
Ticket
Ticket
The Vue Instance
Reactive Data
const vm = new Vue({
data() {
return { one: 1 }
}
})
vm.two = 2 // will not render a view update!
⚠️ Properties in data are only reactive if they existed when the instance was created
The Vue Instance
$watch
const vm = new Vue({
data() {
return { one: 1 }
},
watch: {
one(newVal, oldVal) {
// do something
}
}
})
vm.one = 2 // will trigger watch function
The Vue Instance
$watch (immediate)
const vm = new Vue({
// ...
watch: {
one: {
immediate: true,
handler(newVal, oldVal) {
/* will execute immediately
and on each update */
}
}
}
})
The Vue Instance
Instance Lifecycle Hooks
The Vue Instance
Instance Lifecycle Hooks
The Vue Instance
Instance Lifecycle Hooks
The Vue Instance
Instance Lifecycle Hooks
const vm = new Vue({
// ...
beforeCreate() { ... },
created() { ... },
beforeMount() { ... },
mounted() { ... },
beforeUpdate() { ... },
updated() { ... },
beforeDestroy() { ... },
destroyed() { ... }
})
VUE CLI
Vue CLI
# install vue-cli
npm install -g @vue/cli
# create a new project with command line
vue create hello-vue
# create a new project with web UI
vue ui
The official tool to quickly scaffold Vue SPA
Vue CLI
Install default presets
Vue CLI
Or install all features you need 👌
Vue CLI / UI
Vue CLI / UI
Vue CLI / UI
Vue CLI / UI
Vue CLI
Static Assets Handling
Static assets can be handled by webpack (like source code)
import Vue from 'vue'
import App from './App'
import '@/assets/css/style.css'
import { movies } from '@/assets/json/movies.json'
<img src="require('@/assets/img/image.jpg')">
Vue CLI
Static Assets Handling
Or they can be placed in public folder
data () {
return {
// configured in vue.config.js
baseUrl: process.env.BASE_URL
}
}
<img :src="`${baseUrl}image.png`">
Vue CLI
Environment Variables and Modes
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git
These files must be place in the project root
-
development: used by vue-cli-service serve
-
production: used by vue-cli-service build
-
test: used by vue-cli-service test
Vue CLI
Environment Variables and Modes
# .env.development
VUE_APP_DEBUG_MODE=true
# .env.production
VUE_APP_DEBUG_MODE=false
// Application code
console.log(process.env.VUE_APP_DEBUG_MODE);
// true when `npm run serve`
// false when `npm run build`
VUE ROUTER
Vue Router
What's a Router?
to display different "pages" (views)
Navigate to different URLs
without reloading application
Vue Router
What's a Router?
HEADER
MENU
/foo
/bar
/baz
Vue Router
Installation
npm install --save vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue Router
Getting started
Map components to routes
import Router from 'vue-router'
import Foo from '@/components/Foo.vue'
import Bar from '@/components/Bar.vue'
const router = new Router({
routes: [
{ name: 'foo', path: '/foo', component: Foo },
{ name: 'bar', path: '/bar', component: Bar }
]
})
new Vue({ el: '#app', router, /* ... */ })
Vue Router
Getting started
Specify where to render the components with
<template>
<Header />
<Menu />
<!-- components will be dynamically
rendered here according to the route -->
<router-view />
</template>
<router-view />
Vue Router
Navigation with <router-link>
<template>
<!-- <router-link> will be rendered as <a> -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</template>
Navigation with $router
// ...
methods: {
goToFoo() {
this.$router.push('/foo')
}
}
Vue Router
Dynamic Route Matching
// ...
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User }
]
<!-- User component -->
<template>
<div>Hello user {{ $route.params.id }}</div>
</template>
-
http://localhost:3000/user/1
-
http://localhost:3000/user/2
Vue Router
Nested Routes
routes: [
{ path: '/user/:id', component: User,
children: [
{ path: 'profile', component: UserProfile },
{ path: 'posts', component: UserPosts }
]
}
]
-
http://localhost:3000/user/1/profile
-
http://localhost:3000/user/1/posts
Vue Router
Nested Routes
<!-- App component -->
<template>
<Header />
<router-view />
</template>
<!-- User component -->
<template>
Hello user {{ $route.params.id }}
<!-- Here is rendered
`UserProfile` / `UserPosts` component -->
<router-view />
</template>
Vue Router
this.$router.push({ name: 'user', params: { id: 1 } })
// /foo?search=new
this.$router.push({ path: 'foo', query: { search: 'new' } })
<router-link :to="{ name: 'user', params: { id: 1 } }">
User 1
</router-link>
Navigation with <router-link>
Navigation with $router
HANDLING FORMS
Handling Forms
Input bindings: text
<template>
<form>
<input v-model="fullName">
<p>Fullname is {{ fullName }}</p>
</form>
</template>
Two-way data-binding with v-model
(⚠️ v-model can only be used with input, textarea and select)
Handling Forms
Input bindings: text
<script>
data() {
return {
fullName: ''
}
}
</script>
The initial value has to be declared inside the data function of the component
Handling Forms
Input bindings: text
<input
:value="fullName"
@input="fullName = $event.target.value">
<input v-model="fullName">
is syntax sugar for 👇
Handling Forms
Input bindings: checkbox
<input type="checkbox" v-model="checked">
<input type="checkbox" value="John" v-model="checkedNames">
<input type="checkbox" value="Jane" v-model="checkedNames">
<!-- checkedNames will be [] or ["John"] or ["Jane"]
or ["John", "Jane"] -->
Multiple checkboxes (Array of value)
Single checkbox (boolean)
Handling Forms
Input bindings: radio
<input type="checkbox" value="John" v-model="name">
<input type="checkbox" value="Jane" v-model="name">
<input type="checkbox" value="Alice" v-model="name">
<!-- name will be "John" or "Jane" or "Alice" -->
Handling Forms
Input bindings: select
<select v-model="user">
<option disabled value="">Select someone</option>
<option>John</option>
<option>Jane</option>
<option>Alice</option>
</select>
<!-- user will be "John" or "Jane" or "Alice" -->
Handling Forms
Input bindings: select with dynamic options
<select v-model="selectedUser">
<option v-for="user in users" :value="user.id">
{{ user.name }}
</option>
</select>
data() {
return {
selectedUser: 1,
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Alice' }
]
}
}
Handling Forms
Form validation
USE A PLUGIN
DIRECTIVES AND FILTERS
Directives and Filters
Custom directives
A directive overrides / enhances the behaviour of an element of the DOM
👉 Use only when you need to access / manipulate the DOM
Directives and Filters
Custom directives
// Register a global custom directive called `v-focus`
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
<input v-focus>
Directives and Filters
Directive's hooks functions
bind
inserted
update
componentUpdate
unbind
Directives and Filters
Directive's hooks arguments
inserted(el, binding, /* ... */) {
/*
* - el: the element the directive is bound to
* - binding: an object with the following properties:
* - name: the name of the directive without the `v-`
* - value: the value passed to the directive
* - oldValue: the previous value
*/
}
Directives and Filters
Directive's hooks arguments
<div v-color="#fff"></div>
bind(el, binding) {
el.style.backgroundColor = binding.value
}
Example 1: set div background color
Directives and Filters
Directive's hooks arguments
<h1
v-greetings="{ text: 'Hello, World!', color: '#fff' }">
</h1>
bind(el, binding) {
el.textContent = binding.value.text
el.style.backgroundColor = binding.value.color
}
Example 2: set h1 text and background color
Directives and Filters
Filters
A filter is a function that formats text
👉 Filters can be used in mustache interpolations and v-bind expressions
Directives and Filters
Filters
// Register a global filter called `uppercase`
Vue.filter('uppercase', function (value) {
if (!value) return ''
return value.toString().toUpperCase()
})
<!-- greetings = 'Hello, World!' -->
<h1>{{ greetings | uppercase }}</h1>
<!-- HELLO, WORLD! -->
Directives and Filters
Filters
<!-- filters can be chained -->
<h1>{{ greetings | filterA | filterB }}</h1>
<!-- filters can take arguments -->
<h1>{{ price | currency(2, '$') }}</h1>
Vue.filter('currency', function (price, format, symbol) {
// ...
})
Introduction to Vue
By Nicolas Payot
Introduction to Vue
- 1,282