Skip to content
Search

Vue.js

lastmod 2023-02-15created 2017-02-06199 edits

(For vue3, see here)

Vue Cheat sheet

Vue (pronounced /vjuː/, the same as view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library focuses on the view layer only, making it very easy to integrate with other libraries or existing projects. And when used in combination with modern tooling and supporting libraries, Vue is also perfectly capable of powering sophisticated single-page applications.

Categories

Libraries

Chart libraries

Project Template

Starting with Vue CLI 5.x, it seems only babel and eslint get installed and that's it...

VSCode Plugin

Examples

E.T.C

Hello world

The index.html file is as follows.

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Vue</title>

        <!-- CSS -->
        <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
    </head>
    <body>
        <div id="app">
            {{ message }}
        </div>

        <!-- JS -->
        <script src="https://unpkg.com/vue/dist/vue.js"></script>
        <script src="app.js"></script>
    </body>
</html>

The app.js file is as follows:

var app = new Vue({
  el: '#app',
  data: {
    message: '안녕하세요 Vue!'
  }
})

Preparing for TypeScript coding

vue/valid-v-slot error

If you write code like this,

<template>
    <!-- ... -->

    <template v-slot:item.enable="{item}">  <!-- WARNING !! -->
      <v-btn icon @click="click"></v-btn>
    </template>

    <!-- ... -->
</template>
ESLint: 'v-slot' directive doesn't support any modifier.(vue/valid-v-slot)

To fix it, change v-slot:item.enable="{item}" to v-slot:item[&#96;enable&#96;]="{item}".

Or add the following rule to ESLint:

    "rules": {
      "vue/valid-v-slot": [
        "error", {
          "allowModifiers": true
        }
      ]
    },

Unrecognized slot name

  <template v-slot:item.server_running="{item}">
    <v-icon v-show="item.server_running" small disabled>
      {{ icons.mdiCheck }}
    </v-icon>
  </template>

When using IntelliJ-family IDEs, an "Unrecognized slot name" warning is shown at item.server_running.

Accessing child elements

Of course props and events exist, but sometimes you may need to access a child element directly in JavaScript. In that case, you can assign a reference ID to the child element using the ref attribute. For example:

<base-input ref="usernameInput"></base-input>

Now, in the component where you defined the ID with ref, you can write:

this.$refs.usernameInput

to access the <base-input> instance.

Example

html file:

<div id="app">
  <h1>{{ message }}</h1>
  <button ref="myButton" @click="clickedButton">Click Me!</button>
</div>

javascript file:

var vm = new Vue({
    el: '#app',
    data: {
      message: 'Hello World!'
    },
    methods: {
      clickedButton: function() {
        console.log(this.$refs);
        this.$refs.myButton.innerText = this.message;
      }
    }
  });

Accessing env files

For vue2, add the following to the webpack config:

// webpack.config.js
const webpack = require('webpack');
const dotenv = require('dotenv');
const env = dotenv.config().parsed;

plugins: [
  new webpack.DefinePlugin({
    VUE_APP_LOCAL_URI: JSON.stringify(env.VUE_APP_LOCAL_URI),
  }),
],

For vue3, variables are loaded automatically as long as their names always start with VUE_APP_.

Accessing the element

Use $el.

element = this.$el;

On re-rendering conditions

Only properties assigned to data are redefined, so modifying sub-properties does not trigger re-rendering.

Also, if you want to force re-rendering, change the property assigned to key.

Lifecycle

See Vue:Lifecycle. The diagram is as follows:

Vue-lifecycle.png

Vue instance guide

The properties and APIs available in an instance are as follows.

new Vue({
  el: ,
  template: ,
  data: ,
  methods: ,
  created: ,
  watch: ,
});
  • el - The starting point on the screen where the instance is drawn (a specific HTML tag)
  • template - Elements to display on the screen (HTML, CSS, etc.)
  • data - Data properties with Vue's reactivity applied
  • methods - Methods that control screen behavior and event logic
  • created - A property related to Vue's lifecycle
  • watch - A property that defines additional actions to run when a property defined in data changes

Reactivity in Depth

How changes are tracked

When you pass a JavaScript object to a Vue instance as its data option, Vue converts all of its properties into getter/setters using Object.defineProperty. This is why Vue does not support IE8 and below, which cannot use ES5.

The getter/setters are invisible to the user, but they let Vue perform dependency tracking and change notification when properties are accessed or modified. One caveat is that browsers format getter/setters differently when converted data objects are logged, so it is a good idea to install vue-devtools for a friendlier interface.

Every component instance has a corresponding watcher instance, which records every property "modified" while the component renders dependently. Later, when a dependency's setter is triggered, it notifies the watcher, and the component re-renders.

Vue_-_how_to_track_changes.png

Async update queue

In case you haven't noticed, Vue performs DOM updates asynchronously. Whenever a data change is observed, it opens a queue and buffers all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it is pushed into the queue only once. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop "tick", Vue flushes the queue and performs the actual (already de-duplicated) work. Internally, Vue tries native Promise.then and MessageChannel for the async queue and falls back to setTimeout(fn, 0).

For example, when you set vm.someData = 'new value', the component does not re-render immediately. It updates in the next "tick", when the queue is flushed. Most of the time you don't need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state. Although Vue.js generally encourages developers to think in a "data-driven" way and avoid touching the DOM directly, sometimes it may be necessary to do so. To wait until Vue.js has finished updating the DOM after a data change, you can use Vue.nextTick(콜백) immediately after the data is changed. The callback is called after the DOM has been updated.

HTML:

<div id="example">{{ message }}</div>

JavaScript:

var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 데이터  변경
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

There is also the vm.$nextTick() instance method. This is especially handy inside components, because it doesn't need the global Vue and the callback's this context is automatically bound to the current Vue instance

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '갱신 안됨'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '갱신됨'
      console.log(this.$el.textContent) // => '갱신 안됨'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '갱신됨'
      })
    }
  }
})

Since $nextTick() returns a promise, you can do the same thing using ES2017 async/await syntax.

methods: {
  updateMessage: async function () {
    this.message = '갱신됨'
    console.log(this.$el.textContent) // => '갱신 안됨'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '갱신됨'
  }
}

Some caveats

  • Content inside template slots such as <template v-slot:...> ...</template> sometimes doesn't get "reactivity" applied...
  • If the variable passed via v-model is an Object, modifying a member of the Object may not "react" properly. If you want it to "react" in this case, one way is to use Vue.set(object, key, value).
  • If the variable passed via v-model is an Object and all members of the Object are modified, you must $emit the input event manually.
  • If the variable passed via v-model is an Object and a particular member of the Object is an Array, to make it "react" when that Array is modified, use something like v-if="!value.arr || value.arr.length === 0".

Detecting window size (window.innerHeight) changes

I am sure there are better ways to do this, but this one will work for you until I come up with something:

Basically you will need just one data prop and one watcher to do this.

new Vue({
    el: '#app',
    data() {
        return {
            windowHeight: window.innerHeight,
            txt: ''
        }
    },

    watch: {
        windowHeight(newHeight, oldHeight) {
            this.txt = `it changed to ${newHeight} from ${oldHeight}`;
        }
    },

    mounted() {
        this.$nextTick(() => {
            window.addEventListener('resize', this.onResize);
        })
    },

    beforeDestroy() { 
        window.removeEventListener('resize', this.onResize); 
    },

    methods: {  
        onResize() {
            this.windowHeight = window.innerHeight
        }
    }
});

And this would output the changes

<div id="app">
    <br> Window height: {{ windowHeight }} <br/>
    {{ txt }}
</div>

Style guide

devServer.watchOptions

See webpack-dev-server#devServer.watchFiles. In short:

module.exports = {
  configureWebpack: {
    devServer: {
      watchOptions: {
        ignored: [/node_modules/, /public/],
      }
    }
  }
}

Tutorials (by. LCW)

Getting started with Webpack.

Custom shims

If you want to add your own custom shims such as shims-recc.d.ts, write it not as

import type {Recc} from '@/recc/recc';

but in the form

import Recc from '@/recc/recc';

for symbol complition to work properly in WebStorm-family IDEs ...

The final form is as follows:

import Recc from '@/recc/recc';

declare module 'vue/types/vue' {
  interface Vue {
    $recc: Recc;
  }
}

Troubleshooting

You may need an additional loader to handle the result of these loaders

Install/configure an additional loader where needed.

this.$refs.<refField>.value does not exist

You can do this:

class YourComponent extends Vue {
  $refs!: {
    checkboxElement: HTMLFormElement
  }

  someMethod () {
    this.$refs.checkboxElement.checked
  }
}

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders

If you change a component property directly using $refs, as below...

const button = this.$refs.sign_in_button as VBtn;
button.loading = true;

you get the following error telling you not to mutate a Prop directly.

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "loading"

Translated (into Korean):

상위 구성 요소가 다시 렌더링될 때마다 값을 덮어쓰므로 소품을 직접 변경하지 마십시오.

대신 prop의 값을 기반으로 데이터 또는 계산된 속성을 사용하십시오.

Prop 변경 중: "loading" 

In short, don't change it directly with ref. Use v-bind.

When the above warning appears for a data member not declared as a Prop

See Vue-class-component#Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.

Declaring Reactive Properties

Since Vue does not allow dynamically adding root-level reactive properties, you must initialize Vue instances by declaring all root-level reactive data properties upfront, even with an empty value.

var vm = new Vue({
  data: {
    // declare message with an empty value
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'

If you don't declare message in the data option, Vue will warn you that the render function is trying to access a property that doesn't exist.

There is a technical reason behind this restriction: it eliminates a class of edge cases in the dependency tracking system and makes Vue instances work better with type checking systems. But there is also an important consideration in terms of code maintainability: the data object is like the schema for the component's state. Declaring all reactive properties upfront makes the component code easier to understand when you revisit it later or when another developer reads it.

Error /sockjs-node/info

The following error may be printed in the browser console.

GET http://localhost:4000/sockjs-node/info?t=1555922708541 net::ERR_CONNECTION_REFUSED

Props with type Object/Array must use a factory function to return the default value

How to set the default value when Props are of Object or Array type. In short, the default must be set as a function's return value.{ type: () => { return []; } }

See Vue:Props#Props with type Object/Array must use a factory function to return the default value.

Error in callback for watcher "value": "TypeError: Cannot read property 'call' of undefined"

In the parent component, have the this.current property modified somewhere other than places like created (e.g. an async execution region), as follows.

this.current = {
  username: '',
} as UserItem;

Pass that object to the template.

<form-user v-model="current"></form-user>

If the child (form-user) component uses the current.username property as value.username,

<v-text-field
    :value="value.username"
    @input="inputUsername"
></v-text-field>

the following error occurs.

[Vue warn]: Error in callback for watcher "value": "TypeError: Cannot read property 'call' of undefined"

This is a guess, but..

The component's watcher instance records every property "modified" during rendering, and this is presumably because all properties passed to Vue's data are redefined with Object.defineProperty. (See the Reactivity in Depth page for details.) The error above references username directly, and it presumably occurs because the object itself was modified again after the this.current instance had been redefined with Object.defineProperty. <- Presumably the child component references value.username directly, and this part changed.

Therefore, when modifying the this.current object in the parent component, modify each property directly as follows.

this.current.username = '';

Not working when using a component's v-model and passing it on to a child component's v-model

Look at the example below.

<template>
  <v-card>
    <v-text-field v-model="infoValue"></v-text-field>
  </v-card>
</template>

<script lang="ts">
import {Component, Prop, Emit, Watch} from 'vue-property-decorator';
import VueBase from '@/base/VueBase';

@Component
export default class CardInfoNew extends VueBase {

  @Prop({type: String, default: ''})
  readonly initKey!: string;

  infoKey = '';

  mounted() {
    this.infoKey = this.initKey;
  }
}
</script>

initKey, declared as a Prop, is readonly, so it can't be set as the v-model of v-text-field.

So you create a separate infoKey, but then changes to initKey from the parent component are not reflected.

It works properly only if you check it with Watch as below.

  // ...
  @Watch('initKey')
  onWatchInitKey(value) {
    this.infoKey = value;
  }
  // ...

Maximum call stack size exceeded

[Vue warn]: Error in render: "RangeError: Maximum call stack size exceeded"

Check whether there is a circular reference.

  • Repeated component calls
  • Function recursion

v-slot directive doesn't support any modifier

Change code like this:

<template v-slot:item.actions="{ item }"> ... </template>

to this:

<template v-slot:[`item.actions`]="{ item }"> ... </template>

Cannot GET /...

In an SPA, even when a path like /bla/bla/bla is accessed, the root /index.html file may need to be loaded. Navigating via page links works as the framework intends, but if you navigate by typing the address directly, the resource at that location (e.g. /bla/bla/bla/index.html) does not exist and routing fails.

When using the HTML5 History API, accessing a URL that includes the configured URL would return a 404 response; historyApiFallback serves /index.html in that case too. It looks as if you first go to http://localhost:3080 and then move to the /bla/bla/bla route.

Set the following in the vue.config.js file:

const {defineConfig} = require('@vue/cli-service');
module.exports = defineConfig({
  ...
  devServer: {
    historyApiFallback: true,
  },
});

App not updating to the latest version after deployment unless the browser cache is cleared

See Cache Busting.

See also

Favorite site

Tutorials

Guide

Lint

Examples

References


  1. Meetup_TOAST_Cloud_-Introduction_to_the_JavaScript_Framework_3-_Vue.pdf ↩

  2. Vuejs-tutorial-for-beginner_-_Captain_Pangyo.pdf ↩

  3. Vuejs.kr_-_express-with-vue.pdf ↩

author Zer0 sitemap