Things I Wish I Did Earlier
A Bit About Me
- Core member of Vue team
- Not a JavaScript or front-end developer by profession
- Learn through trials and errors
#1
export default {
data: () => ({
products: [],
showing: true,
mousePosX: 42,
mousePosY: 42
})
}
#1: Group Data
export default {
data: () => ({
products: [],
uiState: {
showing: true,
mousePosition: {
x: 42,
y: 42
}
}
})
}
-
Code is cleaner, more organized, and readable
-
Works best with IDE/editor code grouping support to help with your focus
export default {
data: () => ({
▸ products: {…}
▸ uiState: {…}
})
}
#2
<style lang="scss" scoped>
.my-component {
font-size: 1.2rem;
border-radius: 5px;
background-color: rebeccapurple;
position: fixed; // 🤔
top: 10px; // 🤔
left: 30px; // 🤔
z-index: 42; // 🤔
}
</style>
#2: Isolate Component Styling
- A component shouldn't know much about its surrounding environment
- Increases portability and maintainability
<style lang="scss" scoped>
.my-component {
font-size: 1.2rem;
border-radius: 5px;
background-color: rebeccapurple;
}
</style>
<style lang="scss" scoped>
.wrapper-component {
.my-component {
position: fixed;
top: 10px;
left: 30px;
z-index: 42;
}
}
</style>
<style lang="scss" scoped>
.my-component {
font-size: 1.2rem;
border-radius: 5px;
background-color: rebeccapurple;
position: fixed;
top: 10px;
left: 30px;
z-index: 42;
}
</style>
#3
<template>
<div>The answer to the universe is {{ answerToUniverse }}</div>
</template>
<script>
export default {
data: () => ({
answerToUniverse: 42
})
}
</script>
<template>
<div>The answer to the universe is {{ answerToUniverse }}</div>
</template>
<script>
export default {
data: () => ({}),
answerToUniverse: 42
}
</script>
<template>
<div>The answer to the universe is {{ $options.answerToUniverse }}</div>
</template>
<script>
export default {
data: () => ({}),
answerToUniverse: 42
}
</script>
#3: Use $options For Non-Reactive Data
-
Less prone to bugs due to side effects
-
Consider grouping these data into e.g. $options.nonReactive or something similar
-
Watch out for name collisions e.g. propsData.
import { eventBus } from '@/utils'
import { userObserver } from '@/observers'
export default {
created () {
userObserver.init()
},
methods: {
fetch (fn) {
return eventBus.$emit(event.$names.FETCH_USER_DATA).then(fn)
}
}
}
#4
import { eventBus } from '@/utils'
import { userObserver } from '@/observers'
export default {
created: () => userObserver.init(),
methods: {
fetch: fn => eventBus.$emit(event.$names.FETCH_USER_DATA).then(fn)
}
}
#4: Make Use of Arrow Functions
- No implicit "this" means less redundant variables and side effects
- Shorter!
import { eventBus } from '@/utils'
import { userObserver } from '@/observers'
export default {
created () {
console.log(this) // the Vue instance
userObserver.init()
},
methods: {
fetch (fn) {
console.log(this) // the Vue instance
return eventBus.$emit(event.$names.FETCH_USER_DATA).then(fn)
}
}
}
#5: Use Functions as Props
<template>
<Communicator :communicate="whisper" />
</template>
<script>
export default {
methods: {
whisper: message => alert(message.toLowerCase())
}
}
</script>
- A prop can accept virtually any valid JavaScript types, including a function (which is an object anyway)
- All prop options (type, default, required, validator) work as-is
- Explicit parent-child communication
#6: Use Object Properties as Props
<template>
<BlogPost
:id="post.id"
:title="post.title"
:content="post.content"
/>
</template>
<script>
export default {
data: () => ({
post: {
id: 42,
title: 'The Duck Song',
content: 'Do you have grapes?'
}
})
}
</script>
<template>
<BlogPost :post="post" />
</template>
<script>
export default {
data: () => ({
post: {
id: 42,
title: 'The Duck Song',
content: 'Do you have grapes?'
}
})
}
</script>
<script id="blogPostComponent">
export default {
props: {
id: {
type: Number,
validator: isUnsignedInteger
},
title: {
type: String,
required: true,
validator: isNotEmpty
},
content: {
type: String,
validator: hasSeveralParagraphs
}
}
}
</script>
<script id="blogPostComponent">
export default {
props: {
post: {
type: Object,
required: true,
validator: value => {
return isUnsignedInteger(value.id)
&& isNotEmpty(value.title)
&& hasSeveralParagraphs(value.content)
}
}
}
}
</script>
<template>
<BlogPost v-bind="post"/>
</template>
<script>
export default {
data: () => ({
post: {
id: 42,
title: 'The Duck Song',
content: 'Do you have grapes?'
}
})
}
</script>
<script id="blogPostComponent">
export default {
props: {
id: {
type: Number,
validator: isUnsignedInteger
},
title: {
type: String,
required: true,
validator: isNotEmpty
},
content: {
type: String,
validator: hasSeveralParagraphs
}
}
}
</script>
- Documented in official docs, but somehow not often seen in the wild
#7: Use Renderless Components as Global Event Handlers
<script id="eventListenerComponent">
import { userService } from '@/services'
export default {
render: h => null,
created () {
[$events.LOG_OUT]: async () => {
await userService.logOut()
location.reload()
}
}
}
</script>
<template>
<div>
<EventListener/>
<!-- Other stuff -->
</div>
</template>
<script id="app">
import EventListener from '@/components/EventListener.vue'
export default {
components: { EventListener }
}
</script>
- Host global event handlers that don’t need to access to a component instance in a template-less, renderless component
- Use it like a normal component, most likely from a root level
- You can even create several of such components and compose them
#8: Code-split Components
import { UserList } from '@/components/UserList.vue'
export default {
components: {
UserList
}
}
export default {
components: {
UserList: () => import('@/components/UserList.vue')
}
}
- Components on demand = smaller initial app size = better performance
- Works even better in conjunction with /* webpackPrefetch: true */ and /* webpackPreload: true */
- With Vue-CLI, prefetch/preload is handled automatically
- Note: Async component testing requires a slightly different approach
#9: Write Snapshot Tests
import { shallowMount } from '@vue/test-utils'
import Component from '@/components/FooBar.vue'
describe('components/foo-bar', () => {
it('renders correctly', () => {
expect(shallowMount(Component)).toMatchSnapshot())
}
})
-
Catches UI breakages and unwanted copy changes early
-
Reduces visual testing overhead (but doesn't replace it)
-
Supported out of the box by Jest + Vue Test Utils
#10: Use Mixins with Caution
<template>
<button @click.prevent="addNew">New User</button>
</template>
<script>
import { PostMixin, UserMixin } from '@/mixins'
export default {
mixins: [PostMixin, UserMixin]
}
</script>
- Mixins
- Introduce implicit dependencies
- Are prone to naming clashes
- Are hard to test in isolation
- Consider a different component composition approach
- Coming in Vue 3: Function-based Component API
Bonus
-
Keep non-Vue code out of Vue
-
Resist the tendency to make every method Vue component's. Instead, keep methods and properties neutral (i.e. in a service/module) if you don't need Vue-related objects and functions.
-
-
BYOB (Build Your Own Blocks)
-
Instead of adding new npm packages, consider creating your own libraries, plugins, directives etc. if the functionality is simple enough.
-
-
Learn how Vue's reactivity works
-
Understand common reactivity caveats and how to work around them
-
Avoid reactivity-related bugs and write more performant code.
-
Thank You!
Vue – Things I Wish I Did Earlier
By Phan An
Vue – Things I Wish I Did Earlier
Things I wish I did earlier with Vue.js
- 780