區域註冊 Component

<div id="app">
  <my-component></my-component>
  <my-component></my-component>
  <my-component></my-component>
</div>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
var vm = new Vue({
  el: '#app',
  components:{
    'my-component':{
      template:'<div class="component">This is a component of Vue!</div>'
    }
  }
})

全域註冊 Component

<div id="app1">
  <my-component></my-component>
  <my-component></my-component>
  <my-component></my-component>
</div>
<div id="app2">
  <my-component></my-component>
  <my-component></my-component>
  <my-component></my-component>
</div>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
//全域 component
Vue.component('my-component',{
  template:'<div class="component">This is a component of Vue!</div>'
})

var vm = new Vue({
  el: '#app1',
})

var vm = new Vue({
  el: '#app2',
})

Data 要用 function 來 return

<div id="app">
  <div class="root"></div>
  <my-component></my-component>
  <my-component></my-component>
  <my-component></my-component>
</div>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
  
.root
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: green
//全域 component
Vue.component('my-component',{
  template:'<div class="component"></div>',
  // component 的 data 要用 function 來 return
  data: function(){
    return {
      msg: 'This is a component of Vue! @component'
    }
  }
})

var vm = new Vue({
  el: '#app',
  data: {
    msg: 'This is a component of Vue! @root'
  }
})

Script Template

  • <script type="text/x-template" id="component"></script> 的寫法也可以換成 <template id="component"></template>
<div id="app">
  <my-component></my-component>
</div>

<script type="text/x-template" id="my-component">
<!-- 根結點只能有一個 div -->
  <div>
    <div class="component">1. </div>
    <div class="component">2. </div>
    <div class="component">3. </div>
    <div class="component">4. </div>
  </div>
  
</script>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
//全域 component
Vue.component('my-component',{
  template:'#my-component',
  data: function(){
    return {
      msg: 'This is a component of Vue! @component'
    }
  }
})

var vm = new Vue({
  el: '#app'
})

Render Function: Vue 自己會 render,但也可以自己寫

<div id="app">
  <my-component></my-component>
</div>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
  
//全域 component
Vue.component('my-component',{
  // template:'<div class="component">1. </div>',
  render: function(createElement){
    return createElement('div',{
      class: 'component'
    }, [this.msg])
  },
  data: function(){
    return {
      msg: 'This is a component of Vue! @component'
    }
  }
})

var vm = new Vue({
  el: '#app'
})

追蹤物件變化 Object.defineProperty

Object.defineProperty

props 傳值基本用法

<div id="app">
<!--   注意:如果這裡沒用 v-bind: ,將會直接把 "msg" 當作字串傳入元件 -->
  <my-component v-bind:parent-msg="msg"></my-component>
</div>

<script type="text/x-template" id="my-component">
  <div>
    <div class="component parent">parentMsg: </div>
    <div class="component">childMsg: </div>
  </div>  
</script>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
  
.parent
  color: red
//全域 component
Vue.component('my-component',{
  template:'#my-component',
  // 在 JS 裡面 parent-msg 是不合法的變數名稱,需改用駝峰式命名 parentMsg
  props: ['parentMsg'],
  data: function(){
    return {
      msg: 'This is a child msg!'
    }
  }
})

var vm = new Vue({
  el: '#app',
  data:{
    msg: 'This is a parent msg!'
  }
})

props 型別驗證


<div id="app">
<!--   注意:如果這裡沒用 v-bind: ,將會直接把 "msg" 當作字串傳入元件 -->
  <my-component :parent-msg="msg" :prop-a="pa" 
                :prop-b="pb" :prop-c="pc"
                :prop-d="pd" :prop-e="pe"
                :prop-f="pf">    
  </my-component>
</div>

<script type="text/x-template" id="my-component">
  <div>
    <div class="component">parentMsg: </div>
    <div class="component">propB: </div>
    <div class="component">propC: </div>
    <div class="component">propD: </div>
    <div class="component">propE: </div>
    <div class="component">propF: </div>
  </div>  
</script>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
//全域 component
Vue.component('my-component',{
  template:'#my-component',
  props: {
    parentMsg: null, // null 表示不檢查型別
    propA: Number, // 限定數字
    propB: [String, Number], // 可以多種型別
    propC: {
      type: String,
      required: true // 必要欄位
    },
    propD: {
      type: Number,
      default: 100 // 預設值
    },
    propE: {
      type: Object,
      default: function(){
        return {
          message: 'Hello'
        }
      }
    },
    propF: {
      // 自訂驗證條件
      validator: function(value){
        return value > 10
      }
    },
  },
  data: function(){
    return {
      msg: 'This is a child msg!'
    }
  }
})

var vm = new Vue({
  el: '#app',
  data:{
    msg: 'This is a parent msg!',
    pa: 123,
    pb: 'Mike',
    pc: 'Nicole',
    pd: 10,
    pe: {
      message: 'Hi'
    },
    pf: 100
  }
})

emit event 傳值到父層

  • 透過 this.$parent.$emit('事件名稱', 要傳的值) 傳值給父層。

  • 父層在 mounted 中加入監聽 this.$on('事件名稱', 要執行的程式);,該 function(value) 會得到一個由子層傳來的 value。


<div id="app">
  <div class="component">
    <span>Parent: </span>
    <input type="text" v-model="msg">
  </div>
  <my-component :parent-msg="msg"></my-component>
</div>

<script type="text/x-template" id="my-component">
  <div>
    <div class="component">
      <span>Child: </span>
      <input type="text" v-model="message">
      <button @click="emit2Parent">更新到父層</button>
    </div>
  </div>  
</script>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
//全域 component
Vue.component('my-component',{
  template:'#my-component',
  props: {
    parentMsg: String
  },
  data: function(){
    return {
      message: this.parentMsg
    }
  },
  methods:{
    emit2Parent(){
      this.$parent.$emit('update', this.message);
    }
  }
})

var vm = new Vue({
  el: '#app',
  data:{
    msg: 'This is a parent msg!'
  },
  methods:{
    update(value){
      this.msg = value;
    }
  },
  mounted(){
    this.$on('update', this.update);
  }
})

子層可透過 Event Bus 互傳值

  • 舊版 Vue 如果子層要互傳值,必須先傳回父層,再由父層傳到另一個子層。
  • 新版 Vue 可利用 Event Bus,讓子層不需透過父層互相傳值。
  • Event Bus 建立方式: var bus = new Vue();,無須帶入任何參數。
  • 在子層一 bus.$emit('事件名稱', 要傳的值);
  • 在子層二的 created 中監聽 bus.$on('事件名稱', 要執行的程式)
  • Event Bus 可以有很多個,要注意在同一個 Event Bus 中註冊的事件名稱是否重複。

<div id="app">
  
  <my-component1></my-component1>
  <my-component2></my-component2>
</div>

<script type="text/x-template" id="my-component1">
  <div class="component">
    <span>Child1:</span>
    <input type="text" v-model="message">
    <button @click="emit2Child2">更新Child2</button>
  </div>
</script>

<script type="text/x-template" id="my-component2">
  <div class="component">
    <span>Child2:</span>
    <input type="text" v-model="message">
  </div>
</script>
.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
// Event Bus
var bus = new Vue();

Vue.component('my-component1',{
  template:'#my-component1',
  data: function(){
    return {
      message: '這是 Child1 的預設內容'
    }
  },
  methods:{
    emit2Child2(){
      bus.$emit('update', this.message);
    }
  }
})


Vue.component('my-component2',{
  template:'#my-component2',
  data: function(){
    return {
      message: '這是 Child2 的預設內容'
    }
  },
  created(){
    // var self = this;
    // 注意這裡是 callback function, this 不會是原本的 this。
    // 除了在外部另外宣告外,也可使用箭頭函式避掉。
    // bus.$on('update', function(value){
    //   self.message = value;
    // })
    bus.$on('update', (value)=>{
      this.message = value;
    })
  }
})
// 父層
var vm = new Vue({
  el: '#app'
})

利用 :is 切換顯示的元件 + keep-alive

  • 切換元件預設無法保留在該元件操作的狀態,可透過在元件外面加上 keep-alive 標籤解決,不會再次執行 created 階段。

<div id="app">
  <button @click="currentView = 'view1'">View1</button>
  <button @click="currentView = 'view2'">View2</button>
  <button @click="currentView = 'view3'">View3</button>
  <keep-alive>
    <my-component :is="currentView"></my-component>
  </keep-alive>  
</div>

<script type="text/x-template" id="view1">
  <div class="component">
    <h1>View 1</h1>
    <input type="text" placeholder="輸入文字,並切換 View 試試!">
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nobis cumque eius illo officia consequuntur similique illum sint assumenda harum placeat doloremque blanditiis earum, quaerat minus, fugiat voluptate, nesciunt sit enim!</p>    
    <img src="http://lorempixel.com/400/200/sports/" alt="">
  </div>
</script>

<script type="text/x-template" id="view2">
  <div class="component">
    <h1>View 2</h1>
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nobis cumque eius illo officia consequuntur similique illum sint assumenda harum placeat doloremque blanditiis earum, quaerat minus, fugiat voluptate, nesciunt sit enim!</p>    
    <img src="http://lorempixel.com/400/200/sports/" alt="">
  </div>
</script>

<script type="text/x-template" id="view3">
  <div class="component">
    <h1>View 3</h1>
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nobis cumque eius illo officia consequuntur similique illum sint assumenda harum placeat doloremque blanditiis earum, quaerat minus, fugiat voluptate, nesciunt sit enim!</p>    
    <img src="http://lorempixel.com/400/200/sports/" alt="">
  </div>
</script>


.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
  >img
    width: 100%
Vue.component('view1',{
  template:'#view1'
})

Vue.component('view2',{
  template:'#view2'
})

Vue.component('view3',{
  template:'#view3'
})

// 父層
var vm = new Vue({
  el: '#app',
  data:{
    currentView: 'view1'
  }
})

使用 slot 在元件挖洞填入

  • 原本在元件的 Tag 中輸入任何文字都不會被編譯的,但如果在元件中某處放入 slot 的 Tag,就可以被傳入。
  • 可以有多個 slot ,只要給它們對應的 name 即可。

<div id="app">
  <my-component>
    <h1 slot="title">用 slot 挖洞</h1>
    <p slot="subtitle">副標題被我替換掉了,哈哈哈...</p>
  </my-component>
</div>

<template id="my-component">
  <div class="component">
    <slot name="title">這邊放標題文字</slot>
    <slot name="subtitle">這邊放副標題</slot>
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nobis cumque eius illo officia consequuntur similique illum sint assumenda harum placeat doloremque blanditiis earum, quaerat minus, fugiat voluptate, nesciunt sit enim!</p>    
    <img src="http://lorempixel.com/400/200/sports/" alt="">
  </div>
</template>


.component
  border: 1px solid #000
  padding: 10px
  margin: 10px
  color: blue
  >img
    width: 100%
Vue.component('my-component',{
  template:'#my-component'
})

// 父層
var vm = new Vue({
  el: '#app',
  data:{
    
  }
})


http-vue-loader

  • 元件可以寫成單一 vue 檔,但通常需要使用 Webpack + Vue loader 做編譯。
  • 現在 vue 檔可以直接透過 http-vue-loader 套件進行編譯,方便測試。
  • 直接引入 <script src="https://unpkg.com/http-vue-loader"></script> ,透過 httpVueLoader('hello.vue') 即可。
  • 正式上線的網站,建議還是以 Webpack + Vue loader 做編譯。
  • 在 vue 裡頭設定 style 如果加上 scoped,表示該樣式設定就僅限於該元件使用,就算名稱與父層或其他元件相同也不會互相汙染。
<div id="app">
  <my-component></my-component>
</div>
var vm = new Vue({
  el: '#app',
  components:{
    'my-component': httpVueLoader('https://mike2014mike.github.io/sample/2018-09-04/hello.vue')
  }
})

參考