Claudio Bisconti
mail: claudio.bisconti@comm-it.it
linkedin: linkedin.com/in/claudiobisconti
github: github.com/cbspire
About me
- Sales Account
- Technical Leader
- Fullstack software developer
@
What I know
Vue.js
Created by Evan You (Ex Google)
First release in February 2014
Actually used by
- Laravel
- Alibaba
- Xiaomi
- Netflix
- Gitlab
- Facebook 🤫
- and more others
Vue.js
/vjuː/ => { 'view' }
- Progressive framework
- for building user interfaces
- Modern and lightweight approach
- Components oriented
- with declarative rendering
- and reactivity approach
- where models are simple Javascript object
Vue.js initialization
var vm = new Vue({
el: '#vue-app',
data: {
meetup: {
name: 'Awesome Vue Vol.1',
availability: true
}
}
})
<div id="vue-app">
<div>
<h2>{{ meetup.name }}</h2>
<button v-if="meetup.availability">
Join now
</button>
</div>
</div>
HTML
Javascript
Declarative rendering like Angular
'new Vue' define a Vue instance
We can change availability from:
vm.meetup.availability = false
Components
Components logic
Toolbar
Sidebar
Content / Routing?
Footer
List of Contents
News
Form Login
Avatar
Drawer from the left
MyApp
News
News
Components logic
Study before write
- Before start we need to study the architecture
- ... and the interactions
Divide et Impera
- You need to simplify your workflow
-
In this phase, you can kill your UI/UX designer
(the judge will undestand)
Delegate
- Every component must have only one goal
.vue files
single file component
<template>
<!-- Component template -->
</template>
<script>
export default {
name: 'ComponentName',
// use: <component-name> ...
}
</script>
<style>
// Component style
</style>
<template> tag
Used to render your component
<script> tag
Used to write the logic of component
<style> tag
Styling your component
It needs webpack, I'm sorry 🙁
.vue files
alternative solution
<!-- File .vue -->
<template>
<!-- Component template -->
</template>
<script src="./component.js"></script>
<style src="./component.css"></style>
One important thing to note is that separation of concerns is not equal to separation of file types.
https://vuejs.org/v2/guide/single-file-components.html#What-About-Separation-of-Concerns
It needs webpack again, I'm sorry 🙁
Component registration
import MyComponent from '../components/my-component'
export default {
...
components: {
MyComponent
}
}
Very useful documentation
https://vuejs.org/v2/guide/components-registration.html
Global: Declaration of intent 😇
Local
<!-- Global Registration -->
Vue.component('my-component-name', { /* ... */ })
Vue <template>
<template>
<v-app>
<v-navigation-drawer app></v-navigation-drawer>
<v-toolbar app></v-toolbar>
<v-content>
<v-container fluid>
<router-view></router-view>
</v-container>
</v-content>
<v-footer app></v-footer>
</v-app>
</template>
We can also use a render function or JSX like React
Only one rule:
You must have a single root component
Sample vuetify structure
Template syntax
<template>
<template v-if="meetup.step == 'template-syntax'">
Boring
</template>
<template v-else-if="meetup.step == 'computed-properties'">
Awesome!
</template>
<template v-else>
Not bad <!-- I hope -->
</template>
</template>
v-if
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
v-for
Template syntax
<template>
<div v-show="error">
<modal-dialog title="Error"></modal-dialog>
</div>
</template>
v-show
<div>
<h1 v-once>
{{ title }}
</li>
</div>
v-once (one time interpolation)
<template>
<div v-html="htmlTextVariable"></div>
</template>
v-html
Bindings
<div>
<div class="actions">
<button v-on:click="myClickHandler"> CLICK ME </button>
<button @click="myClickHandler"> CLICK ME </button>
</div>
<div class="contents">
<p> {{ myTextData }} </p>
<p v-html="myHtmlData"></p>
</div>
<div class="forms">
<button v-bind:id="myDynamicId">Used? Mah</button>
<input type="text" v-bind:disabled="inputIsDisabledValue" value="" />
<input type="password" :value="passwordData" :class="dynamicClasses" />
</div>
<div class="expressions">
<p>{{ ok ? 'YES' : 'NO' }}</p>
<p>{{ number + 1 }}</p>
<p>{{ array.join(',') }}</p>
<p>{{ if (ok) { return message } }}</p> <!-- It doesn't work even if you pray -->
</div>
</div>
Forms && v-model
<div>
<form>
<input v-model="username" type="text">
<input v-model="email" type="text">
<textarea v-model="message" placeholder=""></textarea>
</form>
</div>
Modifiers
<div>
<form>
<input v-model.lazy="username" type="text" @change="checkUserExists">
<input v-model.trim="email" type="text">
<input v-model.number="age" type="number">
</form>
</div>
<template> slots
<template>
<div class="flex">
<avatar :src="user.image" />
<div class="content">
<p> {{ user.name }}</p>
<slot></slot>
</div>
</form>
</template>
Sample template Component UserCard
<div>
<user-card :user="user" />
<user-card :user="user">
<p>{{ user.description }}</p>
</user-card>
</div>
Template usage
Slots can have naming
https://vuejs.org/v2/guide/components-slots.html#Named-Slots
<script> tag
<script>
import Avatar from './avatar'
import _ from 'lodash'
export default {
name: 'UserCard', // <user-card>
components: { Avatar },
props: ['user'],
data() {
return {
notificationsCount: 0,
showDropdown: false
}
},
computed: {
initials () {
return this.user.first_name.substr(0,1) + this.user.last_name.substr(0,1)
}
},
methods: {
getNotifications(id) {
fetch(...).then(result => { this.notificationsCount = result.data.count });
}
},
watch: {
user: (newValue, oldValue) => {
...
}
}
mounted() {
this.getNotifications(this.user.id)
}
}
</script>
Componet lifecycle
Props
used for passing data from parent to child
export default {
props: [
'user',
'size',
'notificationCount'
]
}
Props
used for passing data from parent to child
export default {
props: {
user: Object,
size: String,
notificationCount: Number
}
})
Prop Types
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
Props
used for passing data from parent to child
export default {
props: {
user: {
type: Object,
required: true,
default: () => {}
},
size: {
type: String,
required: true,
validator (value) {
return ['small', 'normal', 'large', 'big'].includes(value)
},
default: 'normal'
},
notificationCount: {
type: Number,
validator (value) {
return value >= 0
},
default: 0
}
}
})
Prop Types
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
Data
internal property of component
export default {
...
data() {
return {
notificationsCount: 0,
showDropdown: false
}
},
}
Data must be a function,
otherwise changing a data property affect the data of all other instances
https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function
Methods
functions inside components
export default {
data() {
notificationsCount: 0,
showDropdown: false
},
methods: {
getNotifications(id) {
fetch(...).then(result => {
this.notificationsCount = result.data.count
});
},
toggle() {
this.showDropdown = !this.showDropdown
}
},
mounted() {
this.getNotifications(this.user.id)
}
}
export default {
data() {
notificationsCount: 0,
showDropdown: false
},
methods: {
async getNotifications(id) {
let result = await fetch(...)
this.notificationsCount = result.data.count
},
toggle() {
this.showDropdown = !this.showDropdown
}
},
mounted() {
this.getNotifications(this.user.id)
}
}
Invoking Methods
from template or script
<template>
<div>
<button @click="onClick">CLICK ME</button>
<ul>
<li v-for="item in items" :key="item.id">
<button @click="onItemClick(item)">CLICK ME</button>
</li>
</ul>
</div>
</template>
export default {
methods: {
onClick() {
// ...
},
onItemClick(item) {
// ...
},
initSomething() {
// ...
}
},
mounted() {
this.initSomething(this.items.length)
}
}
Computed properties
reactive functions, similar to methods
However, the difference from methods is that computed properties are cached based on their dependencies
<template>
<avatar-circle :initials="initials" />
</template>
<script>
export default {
props: ['user', 'size', 'notificationCount'],
computed: {
initials () {
return this.user.first_name.substr(0,1) +
this.user.last_name.substr(0,1)
}
}
}
</script>
Computed properties
How it works
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get() {
console.log('get!');
return temperature;
},
set(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
Object.defineProperty
🕐
Computed properties
<template>
<input type="text" v-model="search" />
<ol>
<li v-for="user in filteredUsers" :key="user.id">
{{ user.last_name }} {{ user.first_name }}
</li>
</ol>
</template>
<script>
import _ from 'lodash'
export default {
name: 'UserCard',
props: ['users'],
data() {
return {
search: ''
}
},
computed: {
filteredUsers() {
const search = this.search.toLowerCase().trim()
return _.filter(this.users, (user) => {
return _.startsWith(user.last_name.toLowerCase(), search)
});
}
}
}
</script>
Computed properties
<template>
<div :class="dynamicClass">
<img :src="user.image" class="rounded" />
<div class="content"> {{ fullName }} </div>
</div>
</template>
<script>
export default {
name: 'UserCard',
props: ['user', 'size'],
computed: {
dynamicClass() {
return {
'flex': true,
'user': this.user,
[`role-${this.user.role}`]: this.user,
[`size-${this.size}`]: this.size
}
}
}
}
</script>
let user = {
role: 'admin',
image: 'http://lorempixel.com/200/200/',
fullName: 'Claudio Bisconti'
}
<user-card :user="user" size="large" />
<!--
<div class="flex user role-admin size-large">
<img src="http://lorempixel.com/200/200/" class="rounded">
<div class="content"> Claudio Bisconti </div>
</div>
-->
Watchers
<template>
<div>
<input type="text" v-model="search" />
<p v-if="warning">You must write more then 3 characters</p>
</div>
</template>
<script>
export default {
data() {
return {
search: '',
warning: false,
}
},
watch: {
search(newValue, oldValue) {
this.warning = newValue.length < 3
}
}
}
</script>
While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary.
Watchers
<template>
<div>
<input type="text" v-model="search" />
<p> {{ this.waiting }} </p>
</div>
</template>
<script>
export default {
data() {
return {
search: '',
waiting: ''
}
},
watch: {
search(newValue, oldValue) {
this.waiting = 'Waiting for you to stop typing...'
this.debounceGetAnswer()
}
},
created: function () {
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer() {
this.answer = 'Trying connection...'
// ...fetch, search and whatever you want
}
}
}
</script>
<style> tag
let you decorate your component
<style>
...
</style>
<style lang="scss">
...
</style>
<style scoped>
...
</style>
<style>
...
</style>
<style src="./file.css"></style>
<style scoped> tag
let you decorate your component
<template>
<div class="example">
hi
</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>
hi
</div>
</template>
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
Vue.js Tools
vue-cli 3
$ vue create awesome-vue
$ yarn serve
$ yarn prod
- Interactive project scaffolding
- Zero config rapid prototyping
- Extensible via plugin
- Webpack freeeee (se... magari!)
vue ui 😱
"na figata pazzesca" Cit.
Vue.js devtools
Demo (real)time?
Conclusions 🤭
Thanks
<template>
<div>
<h1>THANKS FOR YOUR ATTENTION</h1>
<h2 v-if="count >= 0">
Questions: {{ count }}
</h2>
<template v-else>
<h2>No? Great!</h2>
<add-question :onSubmit="addQuestion"></add-question>
</template>
</div>
</template>
<script>
import AddQuestion from './add-question'
export default {
name: 'QuestionSlide',
components: { AddQuestion },
data: () => {
return {
count: 0,
questions: []
}
},
methods: {
addQuestion(question) {
this.questions.push(question)
}
}
}
</script>
Awesome Vue Vol.2
What I think to do
- Vue router
- Vuex
- Events communication
- Custom v-model inputs
- Large application structure
- Unit testing
Bonus pack:
- How to Build a cordova app from vue-cli in 30 seconds (download excluded)
Awesome Vue Vol.1
By Claudio Bisconti
Awesome Vue Vol.1
A simple introduction to Vue.js
- 1,275