Form Input Bindings

Components

What are Components?

就是Vuejs最強大的功能之一!!!讓HTML有更豐富並保有原生的特色,也可以使用is擴充

如果你有物件的概念,那component可以視為一個class(of OO)的概念

Using Components

Registration

要使用components,就要先註冊。在這裡先介紹「全域註冊」,這種註冊方式可以提供一個元件給所有的Vue物件使用。

HTML

component只有一個靜態字串。

< div  id = "example" >
  < my-component > 
< /div >
Javascript

註冊元件,在HTML有一樣名稱的tag name就可以改成template的內容

Vue.component('my-component', {
  template: '< div >A custom component!< /div >'
})

Vue物件實體,在HTML有一樣id,對應到Vue物件實體的el,就會進行Vue的渲染。
也就是因為有讓Vue實體物件對應上,component自訂義的HTML tag才會渲染成component.template

new Vue({
  el: '#example'
})
Rendered
< div id="example" >
  < div >A custom component!< /div >
< /div >
Result

Local Registration

區域註冊,就是「限定註冊域」,指定components給特定的Vue物件使用。

此例指定local-componentlocal

HTML

component只有一個靜態字串。

< div id="local" >
  < local-component > < /local-component >
< /div >
Javascript
var Child = {
  template: '< div >A custom component!< /div >'
}
new Vue({
  // ...
  components: {
    'local-component': Child
  }
})
Result

DOM Template Parsing Caveats

在HTML渲染中,有存在著某些tag彼此有固定的相依關係

自訂義組件使用這些元素,會有一些問題。

這裡的實驗結果,看不出什麼太大的差別,就差別比較大其它的差別感覺不太出來。
倒是有一種,在is上的tag name,當作template的味道。

HTML
< div id="dom-templae" >
    < table >< table-cell >會搬出table,沒有渲染!!< /table-cell >< /table >
    < ul >< ul-cell >ul沒有渲染!!< /ul-cell >< /ul >
    < ol >< ol-cell >ol沒有渲染!!< /ol-cell >< /ol >
    < section >< section-cell >section沒有渲染!!< /section-cell >< /section >
    < !-- 使用is的效果 -- >
    < table >< tr is="table-cell" >table只是搬出來,沒有渲染!!< /tr >< /table >
    < ul >< li is="ul-cell" >ul沒有渲染!!< /li >< /ul >
    < ol >< li is="ol-cell" >ol沒有渲染!!< /li >< /ol >
    < section >< h5 is="section-cell" >section沒有渲染!!< /h5 >< /section >
< /div >
渲染成
< div id="dom-templae" >
    < tr >< td >有渲染,無is會搬出table< /td >< /tr >
    < table >< /table >
    < ul >< li >有渲染< /li >< /ul >
    < ol >< li >有渲染< /li >< /ol >
    < section >< h6 >有渲染< /h6 >< /section >
    < !-- 使用is的效果 -- >
    < table >< tbody >< tr >< td >有渲染,無is會搬出table< /td >< /tr >< /tbody >< /table >
    < ul >< li >有渲染< /li >< /ul >
    < ol >< li >有渲染< /li >< /ol >
    < section >< h6 >有渲染< /h6 >< /section >
< /div >
Result
不使用is的效果 會搬出table,沒有渲染!!
    ol沒有渲染!!
section沒有渲染!!
使用is的效果

無論is所屬的tag name是什麼,會置換成template的第一層tag name

table只是搬出來,沒有渲染!!
  1. ol沒有渲染!!
section沒有渲染!!

應當注意,如果您使用來自以下來源之一的字符串模板,這些限制將不適用:

因此,有必要的話請使用字符串模板。

data Must Be a Function

componentsdata必須是function。
可以看一下Vue 组件data为什么必须是函数?

Javascript

如果這麼做,console會發出警告告訴你「在組件中data必須是一個函數。」

Vue.component('my-component', {
  template: '< span >{ { message } }< /span >',
  data: {
    message: 'hello'  //被警告
  }
})
console錯誤訊息

vue.js:485 [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

理解這種規則的存在意義很有幫助,讓我們假設用如下方式來繞開Vue的警告:

HTML
< div id="example-2" >
  < simple-counter >< /simple-counter >
  < simple-counter >< /simple-counter >
  < simple-counter >< /simple-counter >
< /div >
Javascript

技術上data 的確是一個函數了,因此Vue 不會警告,
但是我們返回給每個組件的實例卻引用了同一個data 對象

var data = { counter: 0 }  ---> 全域宣告data物件
Vue.component('simple-counter', {
  template: '< button v-on:click="counter += 1" >{{ counter }}< /button >',
  data: function () {
    return data   ---> 回傳 全域data物件
  }
})

new Vue({
  el: '#example-2'
})
Result
Javascript要修改成「回傳一個新物件」 Javascript
data: function () {
  return {
    counter : 0
  }
}
Result

Composing Components

在前面components的練習中,我們都只是在componenttemplate寫靜態的字串,讓它顯示。
接下來會開始引進變數,並且介紹如何修改。

在Vue中,父子組件的關係可以總結為props down, events up

  1. 父組件(Vue物件)通過props向下傳遞數據給子組件
  2. 子組件(component)通過events給父組件發送消息

接下來就來看propsevents的同作機制!

Props

component實例的作用域是獨立的。
這意味著無法直接將父組件的變數傳給子組件使用。
除非,使用props

Passing Data with Props

宣告一段自訂義tag name的component

HTML
< div id="props">
    < child message = "hello!" > < /child >
< /div>
Javascript
Vue.component( 'child' , {  //父組件
  props: [ 'message' ],  // 宣告props
  template: '< span >{ { message } }< /span >'  // 子組件!! 就可以像data 一樣,prop 可以用在模板內
})

在component中的用法,就像在vm實例中,直接呼叫data般this.message這樣使用。

Vue.component('child', {
    props: ['message'],
    template: '< span v-on:click="showit">{ { message } }< /span>',
    methods: {
        showit: function () {
            setTimeout(() => console.log(this.message), 0);
        }
    }
}
Result

camelCase vs. kebab-case

駝峰命名法=小寫分隔線命名法

component指的是自訂tag name的HTML

HtmlElement.attr = component.props

HTML
< !-- kebab-case in HTML -- >
< child my-message="hello!" >< /child >  ---> my-message(HTML) = myMessage(js): render
< child myMessage="hello!" >< /child >   ---> myMessage(HTML) ≠ myMessage(js): not render
Javascript

如果你使用字符串模板,則沒有這些限制。??

Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '< span >{ { myMessage } }< /span >'
})

new Vue({
    el: '#my-message'
})
Result

Dynamic Props

把Vue物件裡的data丟到組件裡。

:my-message="parentMsg"中,若"parentMsg"是字串,則my-message就不用v-bind。但是,若它是變數,要v-bind

HTML
< div id="dynamic_prop" >
  < input v-model="parentMsg" >
  < br >
  < child :my-message="parentMsg" >< /child >
< /div >
Javascript
Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '< span >{ { myMessage } }< /span >'
})

new Vue({
    el: '#dynamic_prop',
    data: {
        parentMsg: 'aaaaaa'
    }
})
數值傳遞圖 Result

Literal vs. Dynamic

初學常犯的問題。

< !-- this passes down a plain string "1" -- >
< comp some-prop="1" >< /comp >

誤以為這樣的1是數字,但其實是文字。要數字要使v-bind

< !-- this passes down an actual number -- >
< comp v-bind:some-prop="1" >< /comp >

One-Way Data Flow

props是父層組件和子層組件的溝通管道。但是它是單向資料流。
所以,不該在子組件內部改變prop。如果你這麼做了,Vue會在控制台給出警告。
那為什麼還會想改呢?有兩個常見的原因

  1. prop 作為初始值傳入後,子組件想把它當作區域變數來用;
  2. prop 作為初始值傳入,由子組件處理成其它資料-輸出。

區域變數: component需要data function

props: [ 'initialCounter' ],
data : function () {
  return { counter : this .initialCounter }
}

初始化變數: component需要computed function

props: [ 'size' ],
computed : {
  normalizedSize : function () {
    return  this .size.trim().toLowerCase()
  }
}

注意在JavaScript中物件和陣列是reference,指向同一個記憶體空間,
如果prop是一個物件或陣列,在子組件內部改變它時,會影響父組件的狀態。

Prop Validation

components的prop可以設計「資料驗證」。如果驗證NG,Vue會發出警告。有助於設計給別人使用時,別人可以正確的使用props

HTML
< div id="validation" >
    < label for="1" >propA: < /label >
    < input id="1" v-model.number="inputA" placeholder="waring: empty string or string" >< br / >

    < label for="2" >propB(number): < /label >
    < input id="2" v-model.number="inputB" >< br / >
    < label for="3" >propB(string): < /label >
    < input id="3" v-model="inputB" >< br / >

    < label for="4" >propC: < /label >
    < input id="4" v-model.number="inputC" placeholder="waring: no default string" >< br / >

    < label for="4" >propD(number, default 100, but not show): < /label >
    < input id="4" v-model.number="inputD" placeholder="waring: no warning, default 100" >< br / >
    < label for="5" >propD(string): < /label >
    < input id="5" v-model="inputD" placeholder="waring: string, default 100" >< br / >

    < label for="6" >propE: < /label >
    < input id="6" v-model="inputE.message" >< br / >

    < label for="7" >propF: < /label >
    < input id="7" v-model.number="inputF" type="number" placeholder="warning: string" >< br / >

    < vali  :prop-a="inputA"
            :prop-b="inputB"
            :prop-c="inputC"
            :prop-d="inputD"
            :prop-e="inputE"
            :prop-f="inputF" >
    < /vali >
< /div >
Javascript
new Vue({
    el: '#validation',
    data: {
        inputA: 0,
        inputB: 0,
        inputC: 'chris',
        inputD: 10,
        inputE: { message: 0},
        inputF: 11
    },
    components: {
        'vali': {
          props : {
            propA: Number ,   // 基本資料型別檢查(`null` 意思是任何類型都可以)
            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
              }
            }
          },
          template: `< ol >
              < li >propA: {{propA}}< /li >
              < li >propB: {{propB}}< /li >
              < li >propC: {{propC}}< /li >
              < li >propD: {{propD}}< /li >
              < li >propE: {{propE}}< /li >
              < li >propF: {{propF}}< /li >
          < /ol >`
      }
    }
})
Reault
Input








Output

Non-Prop Attributes

例子的練習,參考Vue学习总结笔记(二):组件

prop屬性的意思,指的是一般的屬性,要提供給「使用component的開發者」
在不修改template的情況,想修改component渲染完的根元屬性,該如何做?

HTML
< bs-date-input id="non-prop" data-3d-date-picker="true" >< /bs-date-input >
Javascript
new Vue ({
    el: '#non-prop',
    components: {
        'bs-date-input': {
            template: `< div >component< /div >`
        }
    }
})
Rendered
< div id="non-prop" data-3d-date-picker="true" >component /div >
Result

Replacing/Merging with Existing Attributes

在component的例子,HTML有class="date-picker-theme-dark"
componenttemplateclass="form-control"

HTML
< bs-date-input id="merging-attri" data-3d-date-picker="true" class="date-picker-theme-dark" >< /bs-date-input >
Javascript
new Vue ({
    el: '#merging-attri',
    components: {
        'bs-date-input': {
            template: `< input type="date" class="form-control" >`
        }
    }
})

渲染完會長怎樣呢?

Rendered
< input type="date" class="form-control date-picker-theme-dark" id="merging-attri" data-3d-date-picker="true" >

HTML的attri會取「聯集」的結果class="form-control date-picker-theme-dark"

Result

Custom Events

我們前面學了 父對子 的變數值傳遞方式,接下來要說的是 子對父 的變數值傳遞方式

Using v-on with Custom Events

讓我們從熟悉的角度切入

每一個Vue物件本身,都擁有event interface

$on$emit並不是addEventListenerdispatchEvent的別名。

父組件可以直接監聽在子層裡template裡的事件

無法使用$on直接監聽子層觸發的事件。必須直接在template使用v-on

HTML
< div id="counter-event-example">
  < p>{ { total } }< /p>
  < button-counter v-on:increment="incrementTotal">< /button-counter>  < -- 3. 子層的自訂義increment事件,觸發父層的incrementTotal
  < button-counter v-on:increment="incrementTotal">< /button-counter>
< /div>
Javascript
Vue.component('button-counter', {
  template: '< button v-on:click="incrementCounter" >{ { counter } }< /button >', //1. 子組件用v-on執行incrementCounter
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')  //2. 自訂事件觸發$emit給父層的Method
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1  //4. 執行父層函數,更新total
    }
  }
})
數值傳遞圖 Result

{{ total }}

Binding Native Events to Components

如果你不要自訂,要用原生的,在templatev-on加上.native的修飾字

< my-component v-on:click.native="doTheThing">< /my-component>

.sync修飾符

不同於v1.x版的雙向綁定的用途,在v2.x版.sync當作一個自動附加父組件v-on偵聽器的屬性。目的在於讓子組件改變父組件狀態的代碼更容易被區分。,也就只是作為一個編譯時的語法糖存在。

< comp :foo.sync="bar">< /comp>

會幫你加上

< comp :foo="bar" @update:foo="val => bar = val">< /comp>

組件需要更新foo時,可以這樣寫

this .$emit( 'update:foo' , newValue)

Form Input Components using Custom Events

一般而言,我們用雙向綁定vue實例與HTML,在HTML加上v-mode

< input v-model="something">

Vue會幫你改成這樣

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

components使用時,需要這樣寫

< custom-input
  v-bind:value="something"
  v-on:input="value => { something = value }">
< /custom-input>
練習

暫時先看到這

接下來,做vue-cli的練習