VUE.JS FROM SCRATCH
Kamil Szubrycht
@kamilszubrycht
RRUG#12
Agenda
- vue-cli
- components
- vue-router
- Vuex
- e2e & unit tests
- devtools
Vue.js + Rails
$ rails new myapp --webpack=vue
$ npm install -g vue-cli
$ vue init webpack myapp
- webpack
- webpack-simple
- simple
vue-cli
? Project name (myapp)
? Project description (A Vue.js project)
? Author (Kamil Szubrycht <kamil.szubrycht@gmail.com>)
? Vue build (Use arrow keys)
+ Runtime + Compiler: recommended for most users
- Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML)
are ONLY allowed in .vue files - render functions are required elsewhere
? Install vue-router? (Y/n)
? Use ESLint to lint your code? (Y/n)
? Pick an ESLint preset (Use arrow keys)
+ Standard (https://github.com/standard/standard)
- Airbnb (https://github.com/airbnb/javascript)
- none (configure it yourself)
? Set up unit tests (Y/n)
? Pick a test runner (Use arrow keys)
+ Jest
- Karma and Mocha
- none (configure it yourself)
? Setup e2e tests with Nightwatch? (Y/n)
? Should we run `npm install` for you after the project has been created? (recommended)
(Use arrow keys)
+ Yes, use NPM
- Yes, use Yarn
- No, I will handle that myself
Project structure
├── README.md
├── build
├── config
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── assets
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.js
│ └── router
├── static
└── test
├── e2e
└── unit
npm commands
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
Components system
https://vuejs.org/images/components.png
Single File Components
<template></template>
<script></script>
<style></style>
Data object
<template>
<div>
{{ name }}
</div>
</template>
<script>
export default {
data () {
return {
name: 'Ragnar'
}
}
}
</script>
Methods
<script>
export default {
data () {
return {
name: 'Ragnar'
}
},
methods: {
setName (name) {
this.name = name
}
}
}
</script>
Watched properies
<script>
export default {
data () {
return {
firstName: 'Ragnar',
lastName: 'Lothbrok',
fullName: 'Ragnar Lothbrok'
}
},
watch: {
firstName (val) {
this.fullName = `${val} ${this.lastName}`
},
lastName (val) {
this.fullName = `${this.firstName} ${val}`
}
}
}
</script>
Computed properies
<script>
export default {
data () {
return {
firstName: 'Ragnar',
lastName: 'Lothbrok'
}
},
computed: {
fullName () {
return `${this.firstName} ${this.lastName}`
}
// fullName: {
// get () {
// return `${this.firstName} ${this.lastName}`
// },
// set (newValue) {
// var names = newValue.split(' ')
// this.firstName = names[0]
// this.lastName = names[1]
// }
// }
}
}
</script>
Directives
<li v-for="user in users">
{{ user.email }}
</li>
<!-- full syntax -->
<a v-on:click="doSomething"> ... </a>
<!-- shorthand -->
<a @click="doSomething"> ... </a>
<input v-model="username">
{{ username }}
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Not A/B</div>
<h1 v-show="ok">Hello!</h1>
<span v-class="
red : hasError,
hidden : isHidden
"></span>
<!-- full syntax -->
<a v-bind:href="url"> ... </a>
<!-- shorthand -->
<a :href="url"> ... </a>
Custom directives
<script>
export default {
(...)
directives: {
randomColor: {
// bind: (el, binding, vnode) => { ... }
// inserted: (el, binding, vnode) => { ... }
// update (el, binding, vnode, oldvnode) => { ... }
componentUpdated (el, binding, vnode, oldvnode) {
el.style.backgroundColor = `#${Math.random().toString(16).substr(-6)}`
}
// unbind: (el, binding, vnode) => { ... }
}
}
}
</script>
Vue.directive('randomColor', {
componentUpdated (el) {...}
})
Lifecycle hooks
<script>
export default {
(...)
// beforeCreate () { ... },
// created () { ... },
// beforeMount () { ... },
mounted () { this.loading = false },
// beforeUpdate () { ... },
// updated () { ... },
// beforeDestroy () { ... },
// destroyed () { ... }
}
</script>
Parent-child relationship
https://vuejs.org/images/props-events.png
Passing props
<!-- parent.vue -->
<template>
<div>
<Child :name='name'></Child>
</div>
</template>
<script>
import Child from '@/components/child';
export default {
components: { Child },
data () {
return {
name: 'Floki'
}
}
}
</script>
<!-- child.vue -->
<template>
<div>
{{ name }}
</div>
</template>
<script>
export default {
// props: ['name']
props: {
name: {
type: String,
required: true
}
}
}
</script>
Emitting events
<!-- parent.vue -->
<template>
<div>
<child @message='showMessage'></child>
</div>
</template>
<script>
import Child from '@/components/child';
export default {
components: { Child },
methods: {
showMessage (message) {
alert(message)
}
}
}
</script>
<!-- child.vue -->
<template>
<div>
<button @click="send">Send</button>
</div>
</template>
<script>
export default {
data () {
return {
message: 'hello!'
}
},
methods: {
send () {
this.$emit('message', this.message)
}
}
}
</script>
Mixins
// loading-state.js
export default {
data () {
return {
isLoading: true
}
},
mounted () {
this.isLoading = false
}
}
<!-- Component.vue -->
<template>
<spinner v-if="isLoading"></spinner>
</template>
<script>
import LoadingState from './mixins/loading-state-mixin'
export default {
mixins: [LoadingState],
// (...)
}
</script>
vue-router
// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Items from '@/components/items.vue'
import Item from '@/components/item.vue'
import ItemDetails from '@/components/item-details.vue'
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{ path: '/', name: 'home', component: Items },
{ path: '/item/:id', name: 'item', component: Item,
children: [
{ path: 'details', name: 'details', component: ItemDetails }
]
}
]
})
vue-router
<!-- App.vue -->
<template>
<div id="app">
<h1>Items</h1>
<div id="menu">
<router-link to="/">
Items
</router-link>
<router-link :to="{ name: 'item', params: { id: 1 }}">
Item 1
</router-link>
<router-link :to="{ name: 'details', params: { id: 1 }}">
Item 1 details
</router-link>
</div>
<router-view></router-view>
</div>
</template>
vue-router
// literal string path
router.push('home')
// object
router.push({ path: 'home' })
// named route
router.push({ name: 'user', params: { userId: 123 }})
// with query, resulting in /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
vue-router
router.beforeEach((to, from, next) => {
// ...
next()
})
router.afterEach((to, from, next) => {
// ...
next(false)
})
export default new VueRouter({
routes: [
{ path: '/', name: 'home', component: Vikings,
beforeEnter: (to, from, next) => {
// ...
next({ path: '/' })
}
}
})
but...
Vuex
https://vuex.vuejs.org/en/images/vuex.png
Vuex
// state.js
export default {
items: []
}
// getters.js
export function items (state) {
return state.items
}
export function item (state, getters) {
(id) => { return getters.items.find(item => item.id === id) }
}
// actions.js
export function fetchItems (context) {
Vue.axios.get('/items').then((response) => {
context.commit('setItems', response.data)
})
}
// mutations.js
export function setItems (state, data) {
state.items = data
}
Vuex
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import * as getters from './getters'
import * as actions from './actions'
import * as mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
Vuex
// Component.vue
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['items'])
},
methods: {
...mapActions({
fetchData: 'fetchItems'
})
}
}
</script>
Unit tests
$ npm install avoriaz --save-dev
import { mount } from 'avoriaz';
import List from '@/components/List';
import Vue from 'vue';
describe('List.vue', () => {
it('adds new item to list on click with avoriaz', () => {
const ListComponent = mount(List);
ListComponent.setData({
newItem: 'brush my teeth',
});
const button = ListComponent.find('button')[0].dispatch('click');
expect(ListComponent.data().listItems).to.contain('brush my teeth');
})
})
e2e tests
module.exports = {
'default e2e tests': function (browser) {
const devServer = browser.globals.devServerURL
browser
.url(devServer)
.waitForElementVisible('#app', 5000)
.assert.elementPresent('.hello')
.assert.containsText('h1', 'Welcome to Your Vue.js App')
.assert.elementCount('img', 1)
.end()
}
}
Devtools
Devtools
Devtools
Links
Thanks!
VUE.JS FROM SCRATCH
By Kamil Szubrycht
VUE.JS FROM SCRATCH
- 1,560