Vue.js 入門介紹

邱俊霖

GitHub

Vue.js

  • 建構畫面的漸進式框架
  • 專注在視圖層,簡單易上手
  • 方便與第三方函式庫或計有項目整合
  • Virtual DOM

Virtual DOM

Photo by Jollen Chen

來啊!互相傷害啊!

React.js vs Vue.js

  • Virtual DOM
  • Reactive and Composable
  • Focus in the core library.
    • Routing
    • Global state management

React.js

The richness of ecosystem

Vue.js

Vue React
Fastest 23ms 63ms
Median 42ms 81ms
Average 51ms 94ms
95th Perc. 73ms 164ms
Slowest 343ms 453ms

Vanilla JS

Ref.

Who chose Vue.js ?

終於開始了

其實還沒

MVVM

開始使用 Vue.js

Vue.js 不支援 IE8 及其以下版本,因為 Vue.js 使用了 IE8 不能模擬的 ECMAScript 5 特性。

 

CDN


<script src="https://unpkg.com/vue/dist/vue.js"></script>

Vue-cli

npm install vue-cli -g
vue init webpack my-vue-project

Single file component

基礎使用

聲明式渲染

// HTML
<div id="app">
  {{ message }}
</div>
// JS
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

Hello Vue!

Constructor

var vm = new Vue({
  // options
})

需要傳入一個 option object

Options

  • el
  • data
  • methods
  • computed
  • template

el

  • Type: string | HTMLElement

Provide the Vue instance an existing DOM element to mount on.

data

  • Type: Object | Function

Vue will recursively convert its properties into getter/setters to make it “reactive”.

Update Data

Update Object

  • return new Object

Update Array

  • push()
  • pop()
  • splice()
  • shift()
  • unshift()
  • sort()
  • reverse()

methods

  • { [key: string]: Function }

All methods will have their this context automatically bound to the Vue instance.

var vm = new Vue({
  data : { 
    a: 1 
  },
  methods : {
    plus : function () {
      this.a++
    }
  }
})

vm.plus()
vm.a // 2

computed

  • { [key: string]: Function | { get: Function, set: Function } }

All getters and setters have their this context automatically bound to the Vue instance.

var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 僅讀取,值只須為函數
    aDouble: function () {
      return this.a * 2
    },
    // 讀取和設置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})
vm.aPlus    // -> 2
vm.aPlus = 3
vm.a        // -> 2
vm.aDouble // -> 4

template

  • Type: string

模板將會替換掛載的元素。掛載元素的內容都將被忽略。

// HTML
<div id="app">
  <p>{{ message }}</p>
</div>

// JS
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  template: `
    <div>
      Hey, {{ message }}
    </div>
  `
})
var vm = new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` points to the vm instance
    console.log('a is: ' + this.a)
  },
  mounted: function() {
    console.log('mounted')
  }
})
// -> "a is: 1"

Instance Lifecycle Hooks

注意事項

  • data
  • methods
  • computed
  • etc.

以上都不能使用 arrow function,否則 this 將不是 Vue instance

MDN

Directive

  • v-bind
  • v-if
  • v-show
  • v-for
  • v-on
  • v-model

帶有 v- 前綴的特殊屬性

預期值是單一 JavaScript 表達式( v-for 除外 )

v-bind

// HTML
<div id="app-2">
  <span v-bind:title="message">
    Hover your mouse over me for a few seconds
    to see my dynamically bound title!
  </span>
</div>
// JS
var app2 = new Vue({
  el: '#app-2',
  data: {
    message: 'You loaded this page on ' + new Date()
  }
})

v-if

// HTML
<div id="app-3">
  <p v-if="seen">Now you see me</p>
</div>
// JS
var app3 = new Vue({
  el: '#app-3',
  data: {
    seen: true
  }
})

v-show

// HTML
<div id="app-3">
  <p v-show="seen">Now you see me</p>
</div>
// JS
var app3 = new Vue({
  el: '#app-3',
  data: {
    seen: true
  }
})

v-if vs v-show

  • v-if 切換時的性能消耗較大
  • v-show 初始渲染消耗較大

v-on

// HTML
<div id="app-5">
  <p>{{ message }}</p>
  <button v-on:click="reverseMessage">Reverse Message</button>
</div>
// JS
var app5 = new Vue({
  el: '#app-5',
  data: {
    message: 'Hello Vue.js!'
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    }
  }
})
// HTML
<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
// JS
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
// CSS
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
.fade-enter, .fade-leave-active {
  opacity: 0
}

v-for

// HTML
<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
// JS
var app4 = new Vue({
  el: '#app-4',
  data: {
    todos: [
      { text: 'Learn JavaScript' },
      { text: 'Learn Vue' },
      { text: 'Build something awesome' }
    ]
  }
})
1. Learn JavaScript
2. Learn Vue
3. Build something awesome

v-model

// HTML
<div id="app-6">
  <p>{{ message }}</p>
  <input v-model="message">
</div>
// JS
var app6 = new Vue({
  el: '#app-6',
  data: {
    message: 'Hello Vue!'
  }
})

v-model

// HTML
<input v-model="something">
// HTML
<input v-bind:value="something" v-on:input="something = $event.target.value">

 只是個語法糖衣

Shorthands

v-bind

<!-- full syntax -->
<a v-bind:href="url"></a>
<!-- shorthand -->
<a :href="url"></a>

v-on

<!-- full syntax -->
<a v-on:click="doSomething"></a>
<!-- shorthand -->
<a @click="doSomething"></a>

Component

Registration

// Define a new component called todo-item
Vue.component('todo-item', {
  template: '<li>This is a todo</li>'
})
<ol>
  <!-- Create an instance of the todo-item component -->
  <todo-item></todo-item>
</ol>

聲明式 props

Vue.component('todo-item', {
  // The todo-item component now accepts a
  // "prop", which is like a custom attribute.
  // This prop is called todo.
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

子組件不能夠修改 props

Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { text: 'Vegetables' },
      { text: 'Cheese' },
      { text: 'Whatever else humans are supposed to eat' }
    ]
  }
})
<div id="app-7">
  <ol>
    <!-- Now we provide each todo-item with the todo object    -->
    <!-- it's representing, so that its content can be dynamic -->
    <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
  </ol>
</div>
1. Vegetables
2. Cheese
3. Whatever else humans are supposed to eat

Options

  • el
  • data
  • methods
  • computed
  • template
  • props

Component 注意事項

  • 不能有 el option
  • data 必須是 function type
  • data, methods, computed 都不能使用 arrow function,否則 this 將不是 Vue instance

MDN

<div id="app">
  <app-nav></app-nav>
  <app-view>
    <app-sidebar></app-sidebar>
    <app-content></app-content>
  </app-view>
</div>

Use Component

Slot

<!-- my-component's template -->
<div>
  <h2>我是子組件的標題</h2>
  <slot>
    只有在沒有要分發的內容時才會顯示。
  </slot>
</div>
<div>
  <h1>我是父組件的標題</h1>
  <my-component>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </my-component>
</div>
<div>
  <h1>我是父組件的標題</h1>
  <div>
    <h2>我是子組件的標題</h2>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </div>
</div>

多個 Slot

<!-- app-layout -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<app-layout>
  <h1 slot="header">這裡可能是一個頁面標題</h1>
  <p>主要內容的一個段落。 </p>
  <p>另一個主要段落。 </p>
  <p slot="footer">這裡有一些聯繫信息</p>
</app-layout>
<div class="container">
  <header>
    <h1>這裡可能是一個頁面標題</h1>
  </header>
  <main>
    <p>主要內容的一個段落。 </p>
    <p>另一個主要段落。 </p>
  </main>
  <footer>
    <p>這裡有一些聯繫信息</p>
  </footer>
</div>
  • 只能有一個匿名 slot
  • 其他必須是具名 slot

Custom Events

  • Listen to an event using $on(eventName)
  • Trigger an event using $emit(eventName)

Custom Events

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    increment: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

使用自定義事件的表單輸入組件

<input v-model="something">

<input v-bind:value="something" v-on:input="something = $event.target.value">

// in component
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>

在組件中觸發父組件的 v-model

  • 接受一個 value 屬性
  • 在有新的 value 時觸發 input 事件
<currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
  template: `
    <span>
      <input
        v-bind:value="value"
        v-on:input="updateValue($event.target.value)"
      />
    </span>
  `,
  props: ['value'],
  methods: {
    // 不能直接更新值,而是使用此方法來對輸入值進行觸發 input event
    updateValue: function (value) {
      this.$emit('input', value);
    }
  }
})

組件命名約定

<!-- 在HTML模版中始終使用kebab-case -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<title-cased-component></title-cased-component>

當註冊組件(或者 props )時,可以使用kebab-case ,camelCase ,TitleCase

// 在組件定義中
components: {
  // 使用kebab-case 形式註冊
  'kebab-cased-component' : { /* ... */ },
  // register using camelCase
  'camelCasedComponent': { /* ... */ },
  // register using TitleCase
  'TitleCasedComponent': { /* ... */ }
}

在 HTML 模版中,請使用 kebab-case 形式:

Local Registration

var Child = {
  template: '<div>A custom component!</div>'
}
new Vue({
  // ...
  components: {
    // <my-component> will only be available in parent's template
    'my-component': Child
  }
})

有時間再講系列

Vue-router

Vuex

Vue.js 入門介紹

By 邱俊霖

Vue.js 入門介紹

  • 467