Dima Vishnevetsky
Front-End Expert
Get the most out of Vue
Vue.js Tips & Tricks
Dima Vishnevetsky
📐 UX / UI Designer
🖼 Media Developer Expert @ Cloudinary
👨🏫 #HackathonMentor
🎓 Lecturer
🗣 International Tech speaker
👨💻 Front End Expert
Co-founder of
Vue.js Israel community leader
Transparent Wrappers
<template>
<input
:value="value"
@input="$emit('input', $event.target.value)"
>
</template>
BaseInput.vue
<BaseInput @focus.native="someFunc">
<BaseInput @focus.native="someFunc">
<template>
<label>
{{ label }}
<input
:value="value"
@input="$emit('input', $event.target.value)"
>
</label>
</template>
Problem
<BaseInput @focus="someFunc">
<template>
<label>
{{ label }}
<input
:value="value"
@input="$emit('input', $event.target.value)"
@focus="$emit('focus', $event)"
>
</label>
</template>
Bad solution
<BaseInput @focus="someFunc">
<template>
<label>
{{ label }}
<input
:value="value"
v-on="listeners"
>
</label>
</template>
Good solution
computed: {
listeners() {
return {
...this.$listeners,
input: event => this.$emit('input', event.target.value)
}
}
}
No need for the ".native" modifier anymore
<template>
<label>
{{ label }}
<input
:value="value"
v-on="listeners"
>
</label>
</template>
Another problem
<BaseInput
placeholder="Placeholders are EVIL 😈"
@focus="someFunc"
>
<BaseInput
placeholder="Placeholders are EVIL 😈"
@focus="someFunc"
>
<template>
<label>
{{ label }}
<input
v-bind="$attrs"
:value="value"
v-on="listeners"
>
</label>
</template>
Best solution
inheritAttrs: false
Re-rendering route views
<template>
<router-view
:key="$route.fullPath"
></router-view>
</template>
Custom v-model
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
// this allows using the `value` prop for a different purpose
value: String,
// use `checked` as the prop which take the place of `value`
checked: {
type: Number,
default: 0
}
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
Custom prop validators
Vue.component('my-component', {
props: {
propA: {
validator: function (value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
Functional components
Vue.component('my-functional-component', {
functional: true,
// Props are optional
props: {
// ...
},
// To compensate for the lack of an instance,
// we are now provided a 2nd context argument.
render: function (createElement, context) {
return createElement('p', 'Functional component')
}
})
<template functional>
<p>{{ props.someProp }}</p>
</template>
<script>
export default {
props: {
someProp: String
}
}
</script>
const EmptyList = { /* ... */ }
const TableList = { /* ... */ }
const OrderedList = { /* ... */ }
const UnorderedList = { /* ... */ }
Vue.component('smart-list', {
functional: true,
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
},
render: function (createElement, context) {
function appropriateListComponent () {
const items = context.props.items
if (items.length === 0) return EmptyList
if (typeof items[0] === 'object') return TableList
if (context.props.isOrdered) return OrderedList
return UnorderedList
}
return createElement(
appropriateListComponent(),
context.data,
context.children
)
}
})
Example
Don't use v-for with v-if
<template>
<div>
<p
v-for="item of items"
v-if="item < 5"
:key="item"
>
{{ item }}
</p>
</div>
</template>
<script>
export default {
data () {
return {
items: [...Array(1000).keys()]
}
}
};
</script>
Bad example
<template>
<div>
<p
v-for="item of filteredItems"
:key="item"
>
{{ item }}
</p>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
items: [...Array(1000).keys()]
}
},
computed: {
filteredItems: function () {
return this.items.filter(function (item) {
return item < 5
})
}
}
};
</script>
Good example
Best practices
Detailed prop definitions
// This is only OK when prototyping
props: ['status']
Bad
props: {
status: {
type: String,
required: true,
validator: function (value) { /* ... */ }
}
}
Best
Component files
Vue.component('TodoList', {
// ...
})
Vue.component('TodoItem', {
// ...
})
Bad
components/
|- TodoList.vue
|- TodoItem.vue
Best
Base components
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
Bad
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
Best
Single instance components
components/
|- Heading.vue
|- MySidebar.vue
Bad
components/
|- TheHeading.vue
|- TheSidebar.vue
Best
Tightly coupled component names
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- TodoList/
|- Item/
|- index.vue
|- Button.vue
|- index.vue
Bad
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
Best
Full-word component names
components/
|- SdSettings.vue
|- UProfOpts.vue
Bad
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
Best
Simple expressions in templates
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
Bad
<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
Best
Dima Vishnevetsky
@dimshik100
www.dimshik.com
By Dima Vishnevetsky