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!

InterNations

github.com/phanan

twitter.com/notphanan

phanan.net

Made with Slides.com