Vue.js-組件詳解

學習筆記: 組件詳解

組件詳解

組件與複用

Vue組件須要註冊後纔可使用。註冊有全局註冊和局部註冊兩種方式。html

全局註冊git

Vue.component('my-component', {});

要在父實例中使用這個組件,必需要在實例建立前註冊,以後就能夠用<my-component></my-component>的形式來使用組件。github

Vue.component('my-component', {
    template: `<div>這是一個組件</div>`
});

template的DOM結構必須被一個元素包含,缺乏<div></div>會沒法渲染並報錯。設計模式

在Vue實例中,使用components選項能夠局部註冊組件,註冊後的組件只在該實例做用域下有效。數組

組件中也可使用components選項來註冊組件,使組件能夠嵌套。瀏覽器

var Child = {
    template: `<div>局部註冊組件的內容</div>`
};

new Vue({
    el: '#app',
    components: {
        'my-component': Child
    },
});

Vue組件的模板在某些狀況下會受到HTML的限制,好比<table>內規定只容許是<tr><td><th>等這些表格元素,因此在<table>內直接使用組件時無效的。這種狀況下,可使用特殊的is屬性來掛載組件緩存

<div id="app">
    <table>
        <tbody is="my-component"></tbody>
    </table>
</div>

Vue.component('my-component', {
    template: `<div>這裏是組件內容</div>`
});

常見的限制元素還有<ul><ol><select>服務器

除了template選項外,組件中還能夠像Vue實例那樣使用其餘的選項,好比datacomputedmethods等。app

可是在使用data時,data必須是函數,而後將數據return出去frontend

JavaScript對象是引用關係,若是return的對象引用了外部的一個對象,那這個對象就是共享的,任何一方修改都會同步。

使用props傳遞數據

組件不只要把模板的內容進行復用,更重要的是組件間進行通訊。

一般父組件的模板中包含子組件,父組件要正向地向子組件傳遞數據或參數,子組件接收後根據參數的不一樣渲染不一樣的內容或者執行操做。這個正向傳遞數據的過程經過props來實現。

在組件中,使用選項props聲明須要從父級接收的數據,props的值能夠是兩種,一種是字符串數組,一種是對象

<my-component message="來自父組件的數據"></my-component>

props: ['message'],
template: `<div>{{message}}</div>`,

props中聲明的數據與組件data函數中return的數據主要區別就是props的數據來自父級,而data中的是組件本身的數據,做用域是組件自己,這兩種數據均可以在模板template及計算屬性computed和方法methods中使用。

因爲HTML特性不區分大小寫,當使用DOM模板時,駝峯命名的props名稱要轉爲短橫線分割命名。

<my-component warning-text="提示信息"></my-component>

有時候,傳遞的數據並非直接寫死,而是來自父級的動態數據,這時可使用指令v-bind動態綁定props的值,當父組件的數據變化時,也會傳遞子組件。

<div id="app">
    <input type="text" v-model="parentMessage">
    <my-component :message="parentMessage"></my-component>
</div>

props: ['message'],
template: `<div>{{message}}</div>`,

data: {
    parentMessage: ''
}

這裏用v-model綁定了父級的數據parentMessage,當經過輸入框任意輸入時,子組件接收到的props["message"]也會實時響應,並更新組件模板。

單向數據流

業務中會常常遇到兩種須要改變prop的狀況,一種是父組件傳遞初始值進來,子組件將它做爲初始值保存起來,在本身的做用域下能夠隨意使用和修改。這種狀況能夠在組件data內再聲明一個數據,引用父組件的prop

<div id="app">
    <my-component :init-count="1"></my-component>
</div>

Vue.component('my-component', {
    props: ['initCount'],
    template: `<div>{{count}}</div>`,
    data() {
        return {
            count:this.initCount
        }
    }
});

組件中聲明瞭數據count,它在組件初始化時會獲取來自父組件的initCount,以後就與之無關了,只用維護count,這樣就能夠避免直接操做initCount

另外一種狀況就是prop做爲須要被轉變的原始值傳入,這種狀況用計算屬性就能夠。

<div id="app">
    <my-component :width="100"></my-component>
</div>

Vue.component('my-component', {
    props: ['width'],
    template: `<div :style="style">組件內容</div>`,
    computed: {
        style: function () {
            return {
                width: this.width + 'px'
            }
        }
    }
});

由於用CSS傳遞寬度要帶單位(px),數值計算通常不帶單位,因此統一在組件內使用計算屬性。

在JavaScript中對象和數組時引用類型,指向同一個內存空間,因此 props是對象和數組時,在子組件內改變是會影響父組件。

數組驗證

prop須要驗證時,須要對象寫法。

通常當組件須要提供給別人使用時,推薦都進行數據驗證。好比某個數據必須是數字類型,若是傳入字符串,就會在控制檯彈出警告。

<p data-height="565" data-theme-id="0" data-slug-hash="WywyjV" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="prop" class="codepen">See the Pen prop by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

驗證的type類型能夠是:

  • String
  • Number
  • Boolean
  • Object
  • Array
  • Function

type也能夠是一個自定義構造器,使用instanceof檢測。

組件通訊

組件關係可分爲父組件通訊、兄弟組件通訊、跨級組件通訊。

自定義事件

當子組件須要向父組件傳遞數據時,就要用到自定義事件。

v-on除了監聽DOM事件外,還能夠用於組件之間的自定義事件。

JavaScript的設計模式——觀察者模式方法:

  • dispatchEvent
  • addEventListener

Vue組件的子組件用$emit()來觸發事件,父組件用$on()來監聽子組件的事件。

父組件也能夠直接在子組件的自定義標籤上使用v-on來監聽子組件觸發的自定義事件。

<p data-height="365" data-theme-id="0" data-slug-hash="ZRWjKv" data-default-tab="js,result" data-user="whjin" data-embed-version="2" data-pen-title="自定義事件" class="codepen">See the Pen 自定義事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

在改變組件的data "counter"後,經過$emit()再把它傳遞給父組件,父組件用@increase@reduce$emit()方法的第一個參數是自定義事件的名稱。

除了用v-on在組件上監聽自定義事件外,也能夠監聽DOM事件,這時能夠用.native修飾符表示監聽時一個原生事件,監聽的是該組件的根元素。

<my-component @click:native="handleClick"></my-component>

使用v-model

Vue能夠在自定義組件上使用v-model指令。

<my-component v-model="total"></my-component>

組件$emit()的事件名時特殊的input,在使用組件的父級,並無在<my-component>上使用@input="handler",而是直接用了v-model綁定的一個數據total

<my-component @input="handleGetTotal"></my-component>

v-model還能夠用來建立自定義的表單輸入組件,進行數據雙向綁定。

<p data-height="365" data-theme-id="0" data-slug-hash="zaqJBQ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="v-model雙向綁定" class="codepen">See the Pen v-model雙向綁定 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

實現這樣一個具備雙向綁定的v-model組件要知足下面兩個要求:

  1. 接收一個value屬性
  2. 在有新的value時觸發input事件

非父子組件通訊

在實際業務中,除了父子組件通訊外,還有不少非父子組件通訊的場景,非父子組件通常有兩種,兄弟組件跨多級組件

Vue 1.x版本中,除了$emit()方法外,還提供了¥dispatch()$broadcast()

$dispatch()用於向上級派發事件,只要是它的父級(一級或多級以上),均可以在Vue實例的events選項內接收。

此實例只在Vue 1.x版本中有效:

<p data-height="365" data-theme-id="0" data-slug-hash="pKyOOY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="dispatch派發事件" class="codepen">See the Pen dispatch派發事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

$broadcast()是由上級向下級廣播事件,用法徹底一致,方向相反。

這兩種方法一旦發出事件後,任何組件均可以接收到,就近原則,並且會在第一次接收到後中止冒泡,除非返回true

這些方法在Vue 2.x版本中已廢棄。

在Vue 2.x中,推薦任何一個空的Vue實例做爲中央事件總線(bus),也就是一箇中介。

<p data-height="365" data-theme-id="0" data-slug-hash="dKMgvJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-bus事件總線" class="codepen">See the Pen Vue-bus事件總線 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

首先建立了一個名爲bus的空的Vue實例;而後全局定義了組件component-a;最後建立了Vue實例app

app初始化時,也就是在生命週期mounted鉤子函數裏監聽了來自bus的事件on-message,而在組件component-a中,點擊按鈕後會經過bus把事件on-message發出去。此時app就會接收到來自bus的事件,進而在回調裏完成本身的業務邏輯。

這種方法巧妙而輕量地實現了任何組件間的通訊,包括父子、兄弟、跨級。

若是深刻使用,能夠擴展bus實例,給它添加datamethodscomputed等選項,這些都是能夠公用的。

在業務中,尤爲是協同開發時很是有用,由於常常須要共享一些通用的信息,好比用戶登陸的暱稱、性別、郵箱等,還有用戶的受權token等。

只需在初始化時讓bus獲取一次,任什麼時候間、任何組件就能夠從中直接使用,在單頁面富應用(SPA)中會很實用。

除了中央事件總線bus外,還有兩種方法能夠實現組件間通訊:父鏈和子組件索引。

父鏈

在子組件中,使用this.$parent能夠直接訪問該組件的父實例或組件,父組件也能夠經過this.$children訪問它全部的子組件,並且能夠遞歸向上或向下無限訪問,直到根實例或最內層的組件。

<div id="app">
    <p>{{message}}</p>
    <component-a></component-a>
</div>

Vue.component('component-a', {
    template: `<button @click="handleEvent">經過父鏈直接修改數據</button>`,
    methods: {
        handleEvent: function () {
            this.$parent.message = '來自組件component-a的內容'
        }
    }
});
var app = new Vue({
    el: '#app',
    data: {
        message: ''
    }
});

儘管Vue容許這樣操做,但在業務中,子組件應該儘量地避免依賴父組件的數據,更不該該去主動修改它的數據,由於這樣使得父子組件緊耦合,只看父組件,很難理解父組件的狀態,由於它可能被任意組件修改,理想狀態下,只有組件本身能修改它的狀態。

父子組件最好仍是經過props$emit()來通訊。

子組件索引

當子組件較多時,經過this.$children來遍歷出須要的一個組件實例是比較困難的,尤爲是組件動態渲染時,它們的序列是不固定的。

Vue提供了子組件索引的方法,用特殊的屬性ref來爲子組件指定一個索引名稱。

<p data-height="365" data-theme-id="0" data-slug-hash="dKMLXY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$refs" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/dKMLXY/">Vue-$refs</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

在父組件模板中,子組件標籤上使用ref指定一個名稱,並在父組件內經過this.$refs來訪問指定名稱的子組件。

$refs只在組件渲染完成後才填充,而且它是非響應式的。它僅僅做爲一個直接訪問子組件的應急方案,應當避免在模板或計算屬性中使用 $refs

Vue 2.x將v-elv-ref合併成ref,Vue會自動去判斷是普通標籤仍是組件。

使用slot分發內容

當須要讓組件組合使用,混合父組件的內容與子組件的模板時,就會用到slot,這個過程叫作內容分發

  • <app>組件不知道它的掛載點會有什麼內容。掛載點的內容是由<app>的父組件決定的。
  • <app>組件極可能有它本身的模板。

props傳遞數據、events觸發事件和slot內容分發就構成了Vue組件的3個API來源,再複雜的組件也是由這3部分構成。

做用域

父組件中的模板:

<child-component>
    {{message}}
</child-component>

這裏的message就是一個slot,可是它綁定的是父組件的數據,而不是組件<child-component>的數據。

父組件模板的內容是在父組件做用域內編譯,子組件模板的內容是在子組件做用域內編譯。

<div id="app">
    <child-component v-modle="showChild"></child-component>
</div>

Vue.component('child-component', {
    template: `<div>子組件1</div>`,
});
var app = new Vue({
    el: '#app',
    data: {
        showChild: true
    }
});

這裏的狀態showChild綁定的是父組件的數據。

在子組件上綁定數據:

<div id="app">
    <child-component></child-component>
</div>

Vue.component('child-component', {
    template: `<div v-model="showChild">子組件</div>`,
    data() {
        return {
            showChild: true
        }
    }
});
var app = new Vue({
    el: '#app',
});

所以,slot分發的內容,做用域是在父組件上

單個slot

在子組件內使用特殊的<slot>元素就能夠爲這個組件開啓一個slot(插槽),在父組件模板裏,插入在子組件標籤內的全部內容將替代子組件的<slot>標籤及它的內容。

<div id="app">
    <child-component>
        <p>分發的內容</p>
        <p>更多分發的內容</p>
    </child-component>
</div>

Vue.component('child-component', {
    template: `
    <div>
        <slot>
           <p>若是沒有父組件插入內容,我將做爲默認出現。</p>
        </slot>
    </div>
            `,
});
var app = new Vue({
    el: '#app',
});

子組件child-component的模板內定義了一個<slot>元素,而且用一個<p>做爲默認的內容,在父組件沒有使用slot時,會渲染這段默認的文本;若是寫入了slot,就會替換整個<slot>

子組件 <slot>內的備用內容,它的做用域是子組件自己。

具名Slot

<slot>元素指定一個name後能夠分發多個內容,具名slot能夠與單個slot共存。

<p data-height="265" data-theme-id="0" data-slug-hash="RJRVQJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-slot" class="codepen">See the Pen Vue-slot by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

子組件內聲明瞭3個<slot>元素,其中在<div class="main">內的<slot>沒有使用name特性,它將做爲默認slot出現,父組件沒有使用slot特性的元素與內容都將出如今這裏。

若是沒有指定默認的匿名slot,父組件內多餘的內容都將被拋棄。

在組合使用組件時,內容分發API相當重要。

做用域插槽

做用域插槽是一種特殊的slot,使用一個能夠複用的模板替換已渲染元素。

<div id="app">
    <child-component>
        <template scope="props">
            <p>來自父組件的內容</p>
            <p>{{props.msg}}</p>
        </template>
    </child-component>
</div>

Vue.component('child-component', {
    template: `
<div class="container">
    <slot msg="來自子組件的內容"></slot>
</div> 
    `,
});
var app = new Vue({
    el: '#app',
});

子組件的模板,在<slot>元素上有一個相似props傳遞數據給組件的寫法msg="xxx",將數據傳遞到插槽。

父組件中使用了<template>元素,並且擁有一個scope="props"的特性,這裏的props是一個臨時變量。

template內能夠經過臨時變量props訪問來自子組件插槽的數據msg

做用域插槽更具表明性的用例是列表組件,容許組件自定義應該如何渲染列表每一項。

<div id="app">
    <my-list :book="books">
        <!--做用域插槽也能夠是具名的Slot-->
        <template slot="book" scope="props">
            <li>{{props.bookName}}</li>
        </template>
    </my-list>
</div>

Vue.component('my-list', {
    props: {
        books: {
            type: Array,
            default: function () {
                return [];

            }
        }
    },
    template: `
<ul>
    <slot name="book" v-for="book in books" :book-name="book.name"></slot>
</ul>
    `,
});

子組件my-list接收一個來自父級的prop數組books,而且將它在namebookslot上使用v-for指令循環,同時暴露一個變量bookName

做用域插槽的使用場景是既能夠複用子組件的slot,又可使slot內容不一致。

訪問slot

Vue 2.x提供了用來訪問被slot分發的內容的方法$slots

<p data-height="365" data-theme-id="0" data-slug-hash="vrKZew" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$slots" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/vrKZew/">Vue-$slots</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

經過$slots能夠訪問某個具名slot,this.$slots.default包括了全部沒有被包含在具名slot中的節點。

組件高級用法

遞歸組件

給組件設置name選項,組件在它的模板內能夠遞歸地調用本身。

<div id="app">
    <child-component :count="1"></child-component>
</div>

Vue.component('child-component', {
    name: 'child-component',
    props: {
        count: {
            type: Number,
            default: 1
        }
    },
    template: `
<div class="child">
    <child-component :count="count+1" v-if="count<3"></child-component>
</div>
    `,
});

組件遞歸使用能夠用來開發一些具備未知層級關機的獨立組件,好比級聯選擇器和樹形控件等。

內聯模板

組件的模板通常都是在template選項內定義的,Vue提供了一個內聯模板的功能,在使用組件時,給組件標籤使用inline-template特性,組件就會把它的內容當作模板,而不是把它當內容分發,這讓模板更靈活。

<p data-height="265" data-theme-id="0" data-slug-hash="OEXjLb" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-inline-template" class="codepen">See the Pen Vue-inline-template by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

在父組件中聲明的數據message和子組件中聲明的數據msg,兩個均可以渲染(若是同名,優先使用子組件的數據)。這是內聯模板的缺點,就是做用域比較難理解,若是不是很是特殊的場景,建議不要輕易使用內聯模板

動態組件

Vue.js提供了一個特殊的元素<component>用來動態地掛載不一樣的組件,使用is特性來選擇要掛載的組件。

<p data-height="365" data-theme-id="0" data-slug-hash="zaBdyY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-component" class="codepen">See the Pen Vue-component by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

能夠直接綁定在組件對象上:

<div id="app">
    <component :is="currentView"></component>
</div>

var Home = {
    template: `<p>Welcome home!</p>`
};

var app = new Vue({
    el: '#app',
    data: {
        currentView: Home
    }
});

異步組件

Vue.js容許將組件定義爲一個工廠函數,動態地解析組件。

Vue.js只在組件須要渲染時觸發工廠函數,而且把結果緩存起來,用於後面的再次渲染。

<div id="app">
    <child-component></child-component>
</div>

Vue.component('child-component', function (resolve, reject) {
    window.setTimeout(function () {
        resolve({
            template: `<div>我是異步渲染的!</div>`
        })
    }, 1000)
});

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

工廠函數接收一個resolve回調,在收到從服務器下載的組件定義時調用。也能夠調用reject(reason)指示加載失敗。

其餘

$nextTick

異步更新隊列

Vue在觀察到數據變化時並非直接更新DOM,而是開啓一個隊列,並緩衝在同一個事件循環中發生的全部數據變化。在緩衝時會去除重複數據,從而避免沒必要要的計算和DOM操做。而後,在一下個事件循環tick中,Vue刷新隊列並執行實際(已去重的)工做。

Vue會根據當前瀏覽器環境優先使用原生的Promise.thenMutationObserver,若是都不支持,就會採用setTimeout代替。

$nextTick就是用來肯定何時DOM更新已經完成

<p data-height="365" data-theme-id="0" data-slug-hash="RJRjgm" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$nextTick" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/RJRjgm/">Vue-$nextTick</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

X-Templates

Vue提供了另外一種定義模板的方式,在<script>標籤中使用text/x-template類型,而且指定一個id,將這個id賦給template

<div id="app">
    <my-component></my-component>
    <script type="text/x-template" id="my-component">
        <div>這是組件的內容</div>
    </script>
</div>

Vue.component('my-component', {
    template: `#my-component`,
});
var app = new Vue({
    el: '#app',
});

手動掛載實例

在一些很是特殊的狀況下,須要動態地建立Vue實例,Vue提供了Vue.extend$mount兩個方法來手動掛載一個實例。

Vue.extend是基礎Vue構造器,建立一個「子類」,參數是一個包含組件選項的對象。

若是Vue實例在實例化時沒有收到el選項,它就處於「未掛載」狀態,沒有關聯的DOM元素。可使用$mount手動地掛載一個未掛載的實例。這個方法返回實例自身,於是能夠鏈式調用其餘實例方法。

<p data-height="265" data-theme-id="0" data-slug-hash="BVzmbL" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$mount" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/BVzmbL/">Vue-$mount</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>

除了以上寫法外,還有兩種寫法:

new MyComponent().$mount("#app");

new MyComponent({
    el: '#app'
})

手動掛載實例(組件)是一種比較極端的高級用法,在業務中幾乎用不到,只在開發一些複雜的獨立組件時可能會使用。

相關文章
相關標籤/搜索