Skip to content

Vue.js

(vue3에 대한 내용은 이 곳 참조)

Vue Cheat sheet

Vue(/vjuː/ 로 발음, view 와 발음이 같습니다.)는 사용자 인터페이스를 만들기 위한 진보적인 프레임워크 입니다. 다른 단일형 프레임워크와 달리 Vue는 점진적으로 채택할 수 있도록 설계하였습니다. 핵심 라이브러리는 뷰 레이어만 초점을 맞추어 다른 라이브러리나 기존 프로젝트와의 통합이 매우 쉽습니다. 그리고 Vue는 현대적 도구 및 지원하는 라이브러리와 함께 사용한다면 정교한 단일 페이지 응용프로그램을 완벽하게 지원할 수 있습니다.

Categories

Libraries

Chart libraries

Project Template

Vue CLI 5.x 부터 babeleslint 만 딱 설치되고 마는듯...

VSCode Plugin

Examples

E.T.C

Hello world

index.html파일은 아래와 같다.

<!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>

app.js파일은 아래와 같다:

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

TypeScript 코딩 준비

vue/valid-v-slot error

다음과 같이 코딩하면,

<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)

해결 방법은 v-slot:item.enable="{item}"v-slot:item[`enable`]="{item}"로 바꿔주면 된다.

또는 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>

IntelliJ계열 사용시 item.server_running 부분에서 "Unrecognized slot name" 경고가 출력된다.

자식 요소 접근하기

물론 props와 events가 존재하지만, 가끔 JavaScript에서 자식 요소에 직접 접근해야 하는 경우가 있습니다. 이 경우, ref 속성을 이용해 자식 요소에 레퍼런스 ID를 할당하여 해결할 수 있습니다. 예를 들어:

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

이제 ref를 이용해 ID를 정의한 컴포넌트 안에서 아래와 같이 작성하면:

this.$refs.usernameInput

<base-input> 인스턴스에 접근할 수 있습니다.

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;
      }
    }
  });

env 파일 접근

vue2의 경우 웹팩 설정에 다음 내용을 추가:

// 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),
  }),
],

vue3의 경우 변수명을 항상 VUE_APP_으로 지정하면 알아서 로드된다.

엘리먼트 접근하기

$el를 사용하면 된다.

element = this.$el;

re-rendering 조건에 대하여

data에 할당된 속성만 재정의 되므로 하위 속성을 수정하면 re-rendering 안된다.

또한 re-rendering 시키고 싶다면 key에 할당된 속성을 변경하면 된다.

Lifecycle

Vue:Lifecycle 항목 참조. 다이어그램은 다음과 같다:

Vue-lifecycle.png

Vue 인스턴스 가이드

인스턴스에서 사용할 수 있는 속성과 API는 다음과 같다.

new Vue({
  el: ,
  template: ,
  data: ,
  methods: ,
  created: ,
  watch: ,
});
  • el - 인스턴스가 그려지는 화면의 시작점 (특정 HTML 태그)
  • template - 화면에 표시할 요소 (HTML, CSS 등)
  • data - 뷰의 반응성(Reactivity)이 반영된 데이터 속성
  • methods - 화면의 동작과 이벤트 로직을 제어하는 메서드
  • created - 뷰의 라이프 사이클과 관련된 속성
  • watch - data에서 정의한 속성이 변화했을 때 추가 동작을 수행할 수 있게 정의하는 속성

Reactivity in Depth

변경 내용을 추적하는 방법

Vue 인스턴스에 JavaScript 객체를 data 옵션으로 전달하면 Vue는 모든 속성에 Object.defineProperty를 사용하여 getter/setters로 변환합니다. 이는 Vue가 ES5를 사용할 수 없는 IE8 이하를 지원하지 않는 이유입니다.

getter / setter 는 사용자에게는 보이지 않으나 속성에 액세스 하거나 수정할 때 Vue가 종속성 추적 및 변경 알림을 수행할 수 있습니다. 한가지 주의 사항은 변환된 데이터 객체가 기록될 때 브라우저가 getter / setter 형식을 다르게 처리하므로 친숙한 인터페이스를 사용하기 위해 vue-devtools를 설치하는 것이 좋습니다.

모든 컴포넌트 인스턴스에는 해당 watcher 인스턴스가 있으며, 이 인스턴스는 컴포넌트가 종속적으로 렌더링되는 동안 "수정"된 모든 속성을 기록합니다. 나중에 종속적인 setter가 트리거 되면 watcher에 알리고 컴포넌트가 다시 렌더링 됩니다.

Vue_-_how_to_track_changes.png

비동기 갱신 큐

눈치채셨는지 모르겠지만, Vue는 DOM 업데이트를 비동기로 합니다. 데이터 변경이 발견 될 때마다 큐를 열고 같은 이벤트 루프에서 발생하는 모든 데이터 변경을 버퍼에 담습니다. 같은 Watcher가 여러 번 발생하면 대기열에서 한 번만 푸시됩니다. 이 버퍼링된 중복의 제거는 불필요한 계산과 DOM 조작을 피하는 데 있어 중요합니다. 그 다음, 이벤트 루프 “tick”에서 Vue는 대기열을 비우고 실제 (이미 중복 제거 된) 작업을 수행합니다. 내부적으로 Vue는 비동기 큐를 위해 네이티브 Promise.thenMessageChannel를 시도하고 setTimeout(fn, 0)으로 돌아갑니다.

예를 들어, vm.someData = 'new value'를 설정하면, 컴포넌트는 즉시 재 렌더링되지 않습니다. 큐가 플러시 될 때 다음 “tick” 에서 업데이트됩니다. 대개의 경우 이 작업을 신경 쓸 필요는 없지만 업데이트 후 DOM 상태에 의존하는 작업을 수행하려는 경우 까다로울 수 있습니다. Vue.js는 일반적으로 개발자가 “데이터 중심”방식으로 생각하고 DOM을 직접 만지지 않도록 권장하지만 때로는 건드려야 할 수도 있습니다. Vue.js가 데이터 변경 후 DOM 업데이트를 마칠 때까지 기다리려면 데이터가 변경된 직후에 Vue.nextTick(콜백)을 사용할 수 있습니다. 콜백은 DOM이 업데이트 된 후에 호출됩니다.

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
})

또한 vm.$nextTick() 인스턴스 메소드가 있습니다. 이는 내부 컴포넌트들에 특히 유용합니다. 왜냐하면 전역 Vue가 필요없고 콜백의 this 컨텍스트가 자동으로 현재 Vue 인스턴스에 바인드될 것이기 때문입니다

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) // => '갱신됨'
      })
    }
  }
})

$nextTick()promise를 반환하므로, ES2017 async/await 문법을 사용하여 똑같은 동작을 수행할 수 있습니다.

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

몇 가지 주의 사항

  • <template v-slot:...> ...</template>과 같은 템플릿 슬롯 안의 내용은 "반응형"이 적용되지 않을 때가 있더라...
  • v-model로 전달되는 변수가 Object일 경우 Object의 멤버를 수정하면 정상적으로 "반응"하지 않을 수 있다. 이 경우 "반응"하게 하고 싶다면, Vue.set(object, key, value)를 사용하는 방법이 있다.
  • v-model로 전달되는 변수가 Object일 경우 Object의 모든 멤버가 수정될 경우 수동으로 input 이벤트를 $emit 해야 한다.
  • v-model로 전달되는 변수가 Object이고, Object의 특정 멤버가 Array일 경우 이 Array를 수정했을 때 "반응"하게 하고 싶다면 v-if="!value.arr || value.arr.length === 0"와 같이 사용하면 된다.

윈도우크기(window.innerHeight) 변화 감지

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

webpack-dev-server#devServer.watchFiles 항목 참조. 간단히:

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

Tutorials (by. LCW)

Webpack과 함께 시작.

Custom shims

shims-recc.d.ts와 같이 직접 커스텀 shims를 추가하고 싶다면

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

가 아닌,

import Recc from '@/recc/recc';

의 형태로 해야 WebStorm계열에서 정상적으로 symbol complition 이 적용되더라 ...

최종 형태는 다음과 같다:

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

필요한 부분에 추가 로더를 설치/설정 하면 된다.

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

다음과 같이, $refs를 사용하여 직접 컴포넌트 속성을 변경하면...

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

Prop을 직접 변경하지 말라는 다음과 같은 에러가 발생된다.

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"

번역하면:

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

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

Prop 변경 중: "loading" 

한마디로, ref를 써서 직접 변경하지 말라. v-bind 사용해라.

Prop 으로 지정하지 않은, Data member 인데 위의 경고가 출력될 경우

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

Declaring Reactive Properties

Vue는 루트 수준 반응 속성을 동적으로 추가하는 것을 허용하지 않으므로 빈 값이 있더라도 모든 루트 수준 반응 데이터 속성을 미리 선언하여 Vue 인스턴스를 초기화해야 합니다.

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

data 옵션에서 message를 선언하지 않으면 Vue는 render 함수가 존재하지 않는 속성에 접근하려고 한다고 경고할 것입니다.

이 제한 뒤에는 기술적인 이유가 있습니다. 종속성 추적 시스템에서 극단적인 경우 클래스를 제거하고 Vue 인스턴스가 유형 검사 시스템에서 더 잘 작동하도록 합니다. 그러나 코드 유지 관리 측면에서도 중요한 고려 사항이 있습니다. 데이터 개체는 구성 요소 상태에 대한 스키마와 같습니다. 모든 반응 속성을 미리 선언하면 나중에 다시 방문하거나 다른 개발자가 읽을 때 구성 요소 코드를 더 쉽게 이해할 수 있습니다.

Error /sockjs-node/info

브라우저 콘솔에 다음과 같은 에러가 출력될 수 있다.

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

Props를 Object 또는 Array 타입으로 설정할 경우 기본 값 설정 방법. 간단히, 함수의 반환값으로 기본값을 설정해야 한다.{ type: () => { return []; } }

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"

부모 컴포넌트에서 다음과 같이 this.current 속성을 created 같은 곳이 아닌, 다른 위치 (e.g. 비동기 실행 영역)에서 수정되도록 한다.

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

템플릿에 해당 객체를 넘긴다.

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

current.username속성을 자식(form-user)컴포넌트에서 value.username 으로 사용하면,

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

다음과 같은 에러가 발생된다.

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

추정이지만..

컴포넌트의 watcher 인스턴스가, 렌더링되는 동안 "수정"된 모든 속성을 기록하는데, Object.defineProperty 으로 Vue의 data로 전달된 모든 속성을 재정의 하기 때문으로 추정된다. (자세한 내용은 반응형에 대해 깊이 알아보기 페이지 참조) 위의 에러는 username을 직접 참조하는데, this.current 인스턴스가 Object.defineProperty 으로 재정의된 이후, 다시 객체 자체가 수정되어 발생된 이슈로 추정된다. <- 하위 컴포넌트는 value.username에 직접 참조해서, 이 부분이 바뀐 것으로 추정됨.

따라서 부모컴포넌트에서 this.current 객체 수정시 다음과 같이 프로퍼티 각각을 직접 수정해야 한다.

this.current.username = '';

컴포넌트의 v-model을 상용, 하위 컴포넌트의 v-model로 다시 전달할 경우 작동하지 않는 현상

아래의 예제를 보자.

<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>

Prop 으로 설정한 initKeyreadonly 라서 v-text-fieldv-model로 설정할 수 없다.

따라서 infoKey를 별도로 만들게 되는데, 이 때 상위 컴포넌트에서 initKey를 변경하면 반영되지 않는다.

아래와 같이 Watch로 확인해야 정상적으로 작동한다.

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

Maximum call stack size exceeded

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

순환 참조를 했는지 확인해보자.

  • 컴포넌트 반복 호출
  • 함수 재귀

v-slot directive doesn't support any modifier

다음과 같이 된 코드를:

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

다음과 같이 변경:

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

Cannot GET /...

SPA에서는 /bla/bla/bla같은 경로를 접근해도 루트의 /index.html파일로 읽어야 할 수 있다. 페이지 링크를 통해 이동하면 프레임워크의 의도대로 이동되지만, 만약 직접 주소를 쳐서 이동하면 해당 위치의 리소스(e.g. /bla/bla/bla/index.html)가 존재하지 않아 라우팅에 실패한다.

historyApiFallback는 HTML5의 History API를 사용하는 경우에, 설정해놓은 url 을 포함하는 url에 접근했을때 404 responses를 받게 되는데 이때도 /index.html을 서빙해주는 효과가 있다. 마치 http://localhost:3080 로 일단 갔다가, /bla/bla/bla 라는 라우터로 이동하는 것처럼 보인다.

vue.config.js파일에 다음과 같이 설정하면 된다:

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

앱 배포 후 브라우저 캐시를 클리어하지 않으면 최신버전으로 갱신되지 않는 현상

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