Joshua's Docs - Vue Cheatsheet

🚨 WARNING: I haven't spent that much time in Vue, and this page has gotten a little dated. A lot of this info will not necessarily be best practice, especially for v3.

Resources

Quick Start

  • Install the CLI (which will also install core)
    • npm install -g @vue/cli
  • Options for quick start:
    • Scaffold project:
      • vue create {{project name}} or vue create . (same folder)
    • Preview a single .vue file
      • vue serve {{filename}}

Vue CLI - UI

You can launch the cli-ui by running vue ui, if CLI version > 3, and vue is installed globally

Gotchas of note (or at least ones I wish someone had told me about)

  • Don't use arrow function syntax for anything that needs to access a Vue instance through this. See SO
  • Iterators (v-for) require unique keys (same as React)
    • Error: > Custom elements in iteration require 'v-bind:key' directives.
    • Just do something like v-for="foo in arr" v-bind:key="foo.bar"
  • Trying to bind a string value to style or class attributes; it must be an array or object (link)
  • Trying to bind non-static asset paths as strings
    • For webpack to work with non-static assets, you can pass paths in one of two ways.
      • Non-binded (normal attribute):
        <img src="./assets/party-parrot.gif" />
      • Or, if you are binding, you need to generate the string of the asset path with require(), you cannot simply return the string
        <template>
        	<img :src="imagePath" />
        </template>
        <script>
        	module.exports = {
        		data: function() {
        			// Needs to be the result from require(), not plain string!!!
        			const partyParrotSrc = require('./assets/party-parrot.gif');
        			return {
        				imagePath: partyParrotSrc
        			};
        		}
        	}
        </script>
    • Note that this doesn't apply to static assets. Details
  • Don't use _ prefix for any Vue component variables, props, etc.
    • It will not work!
    • This is "wont-fix" issue as far as Vue is concerned: Exhibit A, B
  • Don't forget the name! (for global registration)
    • Either as a key/pair on Vue.extend({name:'my-component'}) object, or first argument to Vue.component('my-component',{})

How do I...

  • Use expressions (+, ternary, etc) within attribute binding?
    • Like this:
      <div v-bind:key="item.text + item.link"></div>
    • And, of course, very easy to use within mustache outside of attributes:
      <div>Key: {{item.text + item.link}}</div>
    • WARNING: both the class and style attributes are exceptions to this; you need to pass in objects instead of strings, and for style, you need to use camelCase
  • Use props
  • Use placeholder elements for use with conditionals
    • Although <template> tags aren't strictly the same thing as React Fragments, they are close in that they don't echo out as actual HTML and can be used as placeholders to attach conditional logic on.
  • Subscribe / piggyback / add callback to v-model binding?
    • Don't overthink it! Using a bi-directional v-model binding does not preclude you from attaching other listeners. So, for a standard form input binding where you want the change event, you could so something like this:
      <select v-model="myOption" v-on:change="handleMyOptionChange">
      </select>
  • Apply animations / transitions to elements as they are conditionally rendered
    • Alligator.io has one of the best guides on this
    • Official docs
    • Tip: I highly recommend using named CSS animations, with keyframes, if you have anything mildly complicated in terms of effects.

Loading script files

You might be tempted to put a <script></script> tag directly in your Vue component - don't do it! It will either throw errors, hang the build process, or cause unexpected behavior.

If this is a local script, the best route is always to use built in imports / webpack.

However, if the script is an external script (hosted URL), here are some alternatives:

  • Inject script into page on component mount

    export default {
    	mounted(){
    		let scriptElem = document.createElement('script');
    		scriptElem.src = 'https://example.com/script.js';
    		document.body.appendChild(scriptElem);
    	}
    }
    • Note that this will keep reinjecting the script every time the component is mounted, so you might want to use something like this that checks if the script was already injected:
    data: {
    	scriptSrc: 'https://example.com/script.js'
    }
    mounted: function() {
    	if (!document.querySelector('script[src="' + this.scriptSrc + '"]')){
    		const scriptElem = document.createElement('script');
    		scriptElem.src = this.scriptSrc;
    		document.body.appendChild(scriptElem);
    	}
    }
  • Add it to the main index.html that loads Vue

Binding

You can bind attributes of an element to a piece of data/state, by using v-bind. This is also called a directive.

For example, to bind an image:

<img v-bind:src="myImageSrc">
<script>
new Vue({
	el: '#app',
	data: {
		myImageSrc: 'images/banner.jpg'
	}
});
</script>

Vue.extend()?

In using Vue components (especially single-file-components), you might see two different styles of exporting the component so that it can be reused. In the docs, you usually see:

export default {
	name: 'myComponent',
	props: {
		msg: String   
	},
	data: {
		myArr: []
	}
}

However, there is another way:

import Vue from 'vue';
export default Vue.extend({
	name: 'myComponent',
	props: {
		msg: String
	},
	data: ()=> ({
		myArr: []
	})
})

Note: notice that data must be a function that returns the data when using extend

The main difference is that Vue.extend() is an API call that explicitly wraps the export in a Vue constructor subclass - that is to say that rather than exporting a generic JS object, your SFC will be exporting a component constructor that is set to construct a component based on the input you fed into Vue.extend(input). For practical purposes, this has a couple important influences:

  • For TypeScript, and intellisense, it means that imports in other files can pick up on the actual Vue component class structure and how your definition fits into it. See this for details.
  • You can instantiate new component instances simply by using the new keyword and the component constructor returned by Vue.extend(). SO.

Echoing out HTML without wrapper elements

If you need to output a single element, without wrappers, from a SFC or component instance, it can be difficult. Especially if you have your HTML as a string.

For example, you can use the v-html binding to set the inner HTML of an element... but it can't be used on the root <template> wrapper, which means no matter what, you will end up with needing a wrapper element around your HTML. Vue used to have triple brace directive, but that was deprecated in v2. All of this is summarized in this issue.

So what is left? We have to explore using Vue at a lower level and hook into the actual template and rendering features. There are a couple ways you can do this:

Use the render function directly

Vue exposes a special method on component instances called render, from which you need to always return a root VNode (Virtual DOM Node). You can generate a VNode by calling createElement (for example, createElement('div')), or by using the vue template compiler methods (more on that further down).

The basics of the render method are here.

Vue.component('my-component',{
	data: {
		myVal: 'Hello'
	},
	render: function
});

You can also combine this approach with the Vue templating engine. The Vue API has a special method, Vue.compile, which takes a template string and returns a render method that in turn returns VNode, so you directly assign the result of Vue.compile to render, like so:

let vueRes = Vue.compile('<div>{{ myVal }}</div>');
Vue.component('my-component',{
	data: {
		myVal: 'Hello'
	},
	render: vueRes.render
});

Basic details on the API can be found here, and the advanced documentation is kind of shared with vue-template-compiler. Note that this adds some overhead to your project.

As an alternative to compile, you can also pass template as a prop to createElement, which basically does the same thing (requires full version of Vue):

Vue.component('my-component',{
	data: {
		myVal: 'Hello'
	},
	render: function(createElement){
		return createElement({
			template: '<div>{{ myVal }}</div>'
		});
	}
});

Be careful about the differences between a functional (aka stateless) component's render function, and a regular render function. They have different signatures, and the value of this changes within them. See below:

Render in functional vs non-functional

In a functional component (denoted by prop functional: true), render takes two arguments, with the second being context. The value of this is also different in a functional component. For example, in a regular render function, you could get the props with this.$props, but in a functional one, you would want to use context.props

Use the Vue Compiler + the template property

Instead of writing components using <template> in SFCs, you can actually pass an HTML string by using the template property on a Vue component instance:

Vue.component('my-component',{
	data: {
		myVal: 'Hello'
	},
	template: '<div>{{ myVal }}</div>'
});

Note that this doesn't actually really help avoiding wrapping if you are dynamically creating your template string from props, since it requires a const input. If you need a more flexible approach, use the render() based approach.

However, this requires have the full Vue compiler + runtime installed, as opposed to just the runtime. See this and this.

According to Vue, this adds an "extra 10kb payload for your app"

Technically, If you only need the compiler for a single file, you can pull it in by changing your import from:

import Vue from 'vue'

To:

import Vue from 'vue/dist/vue.esm'
// or 
import Vue from 'vue/dist/vue.js'
// or
import vue from 'vue/dist/vue.common'

, but this is really not recommended, and you should just change your global config. If you are using the new CLI config, you can simply use runtimeCompiler: true.

v-runtime-template

v-runtime-template is an interesting library that looks to reduce the complexity of these approaches. The site alligator.io has a good writeup on using it here.


Dynamically creating components from string, AJAX, etc.

Most of this overlaps with the section above, on "Echoing out HTML" - that is to say, your options are mainly one of:

  • Using the template prop
  • Using the render function prop - assigning the render function generated by Vue.compile()
  • Using the render function prop - directly

Note that the Vue docs actually has a dedicated section on Async components; the gist of their advice seems to be to pass a promise wrapper as the second argument to Vue.component, that eventually resolves with the template string. There is also a good S/O answer that illustrates this.


TypeScript stuff

Typescript in SFC (.vue files)

This is something I'm currently having issues with - it compiles fine, but intellisense does not seem to be kicking in when editing TS inside as SFC using <script lange="ts"></script>. Even when using the go-to extension Vetur.

One solution that worked for me was to remove the lang="ts", and then add it back, and suddenly intellisense started working again.

Or, you can always keep your TS as a separate file (this actually can help keep things clean with large components anyways):

views/Home.vue:

<template>
</template>
<script lang="ts">
// @ts-ignore
import c from './home.ts';
export default c;
</script>

views/Home.ts

import Vue from 'vue';
export default Vue.extend({
	// ...
});

Oddities in creating components with Vue.extend

  • You might get strange type errors, that don't actually prevent transpiling success, if you fail to annotate the return types of your custom functions (in methods, or computed, or even VNode) (SO)
  • data needs to be a function that returns an object, not the object itself
    • However, don't use an arrow function (despite many docs recommending it) or else you lose the correct scope of this
let myComponent = Vue.extend({
	name: 'MyComponent',
	data: function(){
		return {
			name: 'Joshua'
		}
	},
	computed: {
		lowerName: function(): string {
			return this.name.toLowerCase()
		}
	},
	methods: {
		logName: function(): void {
			console.log(this.name);
		}
	}
});

Make sure to include name when using Vue.extend() API

If you omit the name field when using Vue.extend({}), you are likely to run into issues with hot-reloading, and this specific error:

vue.runtime.esm.js?... [Vue warn]: Unknown custom element: ___ - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Issues with props, this showing as any, computed, etc.

I noticed a strange issue with TypeScript and Vue.extend(), where it would randomly stop working within a SFC (.vue) file as far as TSC and intellisense goes. Mostly it seemed on the intellisense side, where things would still compile, but was making editing almost unusable because it was breaking this, which was showing as any within methods, computed, etc. I'm sure this will get better with time and newer Vue versions, but the issue, at least for me, is completely resolved by just using TypeScript outside of SFC (single file components).

In many cases, literally all I had to do to fix my issues was copy all the TS, move to a separate TS file, paste it, and then import it within the SFC I wanted to use it in. For example, something like this:

MyComponent.vue:

<template>
	<!-- stuff -->
</template>
<script lang="ts">
import c from './MyComponent-Code.ts';
export default c;
</script>

And then in MyComponent-Code.ts, follow the normal pattern of extending Vue:

import Vue from 'vue';
export default Vue.extend({
	// ...
});

Complex prop types

Prop types can be a little confusing with TS & Vue, and there are multiple ways to declare them.

TypeScript Interfaces as Vue Prop Types

Let's say we have a custom interface we want to accept as a prop (really taking in an object):

interface Human {
	name: String,
	age: Number
}

To use this custom interface as a prop type, you now have two options.

Option A: cast as function that returns interface:

export default Vue.extend({
	props: {
		person: {
			type: Object as () => Human
		},
	}
});

Option B: A newer syntax, explicit casting to PropType

// You need to make sure to import PropType from Vue
import Vue, {PropType} from 'vue'
export default Vue.extend({
	props: {
		person: {
			type: Object as PropType<Human>
		}
	},
});

Thanks to this article for clearing these options up.

Unions in Vue Props

If you need to use a union type, you can put the types that make up the union within an array, and have that as the prop type:

Vue.extend({
	props: {
		unionProp: {
			type: [String, Number]
		}
	}
});

You can even include interfaces as part of the union (example).

Prop Type Safety and Compile-Time Checking

The default way of declaring props on a component and then passing them on the parent enforces run-time type checking, but not static / compile-time type checking, even with TypeScript. This is a rather important distinction to make, since a CI/CD system might let you merge code that compiles fine, but immediately explodes when the page tries to load and leaves the user with a blank screen, missing components, and/or fatal errors.

⚠ Warning: By compile-time, I mean the compiling / transpiling of the TypeScript by TSC, not Vue's template compiler or any Vue internals.

Honestly, every time I look into this and ways around it, I end up unhappy with what I find. It looks like even with the newest Vue 3 Composition API and the improved TypeScript support it comes with, prop types are still checked at run-time, but not at compile-time.

Here are the exceptions / workaround:

  • Use jsx or tsx instead of .vue SFC template files
    • I have had trouble finding documentation that confirms or denies this, but I have a feeling that part of why static type checking on props is basically not a thing is due to the complexity of Vue SFC templates; they are their own thing, separate from JS/TS.
      • I also find type inference / intellisense support in SFCs to be dodgy already; even with Vetur
    • Since jsx is essentially just JavaScript, it can be more easily understood (natively) by both the build system and the IDE
    • Here is an example of someone who got prop type safety working, with IDE support too, by using JSX
    • The downside here is that SFCs are a huge selling point of Vue; JSX is closely coupled with the React project, and using JSX with Vue just feels... off
  • Pass prop TS type to consumer, manually enforce type before passing / binding
    • In your component, you could export the type / interface that represents part of your props, and then in the parent, import it. Next, manually type a variable as that type, before passing it through in the <template> section.
    • On S/O, I came across an example of how someone used this manual approach
    • This also leads me to another complaint; it seems as though you cannot re-use TS interfaces as props directly; you can only re-use them as nested properties (see above section on TS interfaces as Vue props).

⚠ Warning: Again, I have had trouble finding documentation on why props are checked this way (part of my frustration!), but another reason might be intentional. Just as I am complaining about the lack of static type checking with props because it lets type errors sneak through to the runtime, the inverse is true as well - even if Vue perfectly supported static prop type checking, just because the code compiled without errors doesn't mean it won't later throw runtime exceptions for bad props.
After all, props are usually highly dynamic, and you might end up passing user input, API responses, and other things you can't always anticipate through props.


Using VSCode with Vue

If you are using the popular "Vetur" VSCode plugin, and want to change the tab spacing / indentation, you need to change the option in workspace settings JSON:

{
	"vetur.format.options.tabSize": 4
}

Vue Router

Interrupting the normal flow

You can use "Navigation Guards" to modify routing programatically, as it happens.

For example, you could use beforeEach to redirect user to login view based on state.

const router = new VueRouter({...});

router.beforeEach((to, from, next) => {
	// ...
	let isLoggedIn = getIsLoggedIn();
	if (!isLoggedIn && to.path!=='/login'){
		// Force redirect to /login
		next('/login');
	}
	else {
		// Proceed as normal
		next();
	}
});

Be aware that it a little too easy to write a circular loop this way and end up with a maximum call stack size exceeded error.

Preserving a component's state on route change

keep-alive can be used to preserve a component as it is swapped out, but keep in mind that you need to specify the individual component to be preserved, not the route itself. And if using include to specify a specific component, it needs to match the name.

<keep-alive include="subComponent">
	<router-view></router-view>
</keep-alive>

Details

Vuex

Actions vs Mutations?

TLDR:

  • How are you trying to update state?
    • Simple, synchronously
      • Use mutations
    • Complex and/or using async (api,db calls, etc)
      • Use actions

From the official docs (emphasis mine):

Actions are similar to mutations, the differences being that:

  • Instead of mutating the state, actions commit mutations.
  • Actions can contain arbitrary asynchronous operations.

What does this mean in practical terms? Well, it really seems to come down to complexity and if you need to modify state using async.

If you just need to modify a single bit of state, and the entire chain of logic depends on simple synchronous actions, then you are better off using a mutation. Example:

Settings panel -> user unchecks "allow tracking" -> triggers Vuex mutation to set state.allowTracking to false.

No part of the above chain required an async flow, such as waiting for a response from another service.

A more complex example that would require using actions is:

Settings panel -> user unchecks "allow tracking" -> triggers Vuex action to...

  • set state.allowTracking to false
  • (Async) tell third-party tracking API to delete user's log
    • If success, unsets state variable that points to ID of log

Vuetify

Important Reminder from docs:

In order for your application to work properly, you must wrap it in a v-app component. This component is required for ensuring proper cross-browser compatibility. It can exist anywhere inside the body, but must be the parent of ALL Vuetify components.

Resources

  • Default icons - search - link

Icons

Version 1.5 used Material, Version 2.0+ uses Material Design Icons (prefixed with mdi). MDI is actually not installed/served locally - it actually inserts a CSS link tag into your index.html file, pointing to https://cdn.jsdelivr.net/npm/@mdi/.../materialdesignicons.min.css. You can easily swap this out for any icon set, such as Material or FontAwesome.

Using the grid system

12 point grid system

Structure should follow:

  • Pre 2.0: v-container -> v-layout -> v-flex
  • Post 2.0: v-container -> v-row -> v-col

Example - a 2x2 grid:

<v-container grid-list-md>
	<v-layout wrap row>
		<v-flex xs6>
			<div class="myContent"></div>
		</v-flex>
		<v-flex xs6>
			<div class="myContent"></div>
		</v-flex>
		<v-flex xs6>
			<div class="myContent"></div>
		</v-flex>
		<v-flex xs6>
			<div class="myContent"></div>
		</v-flex>
	</v-layout>
</v-container>

Coming from Materialize

Some standard conversions

What Materialize Vuetify
Text Alignment Link
- .left-align
- .right-align
- .center-align
Link
- .text-left
- .text-right
- .text-center
Or, specify screen size
- .text-md-center (only center on medium viewport size)
auto-center via margins NA
<button style="margin-left:auto; margin-right:auto;"></button>
Shortcut class: mx-auto (docs)
Hide on {size} Link
- .hide (all)
- .hide-on-{size}
Link
- d-none (all)
- hidden-{size}-{condition}

Global Styling in Vue

Refer to this as the go-to guide.

Options:

  • In your root App.vue SFC, add an "unscoped" style section
  • Create a static CSS file, and import it the regular way with <link rel="stylesheet" href="..."> in your index.html served file.
  • Use a dedicated loader to load non-standard CSS sources, such as SASS, which would fall under the guide for pre-processors.
    • Non-CLI Setup
      • Usually this is accomplished by adding a config for the loader in your webpack config file, under rules. Essentially the rule says that if test matches flie content (based on filename or <style> block), then use ____ loader to pre-process.
      • SASS/SCSS example
    • CLI Setup
      • WARNING: The CLI setup uses some "magic" to hide a lot of the manual config that is required with webpack. Unless you eject, ignore the guides for webpack configs.

      • If you have created your project with CLI 3, and both sass-loader and sass is installed in package.json, you should just be able to start using SASS right away:
        • by writing <style lang="scss"></style> in your templates.
        • Or by using import ./myScssFile.scss in your main TS or JS file that loads Vue.
      • If you want to mess with specific config options, such as globally importing variables, or processing scss files, you will want to create/edit a file called vue.config.js in your project root.

Vue 3.0

Composition API

📘 --> Main Doc Entry Point

📃 --> Free Cheat Sheet from Vue Mastery

Markdown Source Last Updated:
Thu Sep 24 2020 05:38:44 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Mon Aug 19 2019 17:06:24 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback