Vue-組件詳解

查看 原文站點,更多擴展內容及更佳閱讀體驗!

組件詳解

組件與複用

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

全局註冊github

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

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

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

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

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

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須要驗證時,須要對象寫法。

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

Vue.component('my-component', {
    props: {
        // 必須是數字
        propA: Number,
        // 必須是字符串或數字類型
        propB: [String, Number],
        // 布爾值,若是沒有定義,默認值是true
        propC: {
            type: Boolean,
            default: true
        },
        // 數字,並且是必傳
        propD: {
            type: Number,
            default: true
        },
        // 若是是數組或對象,默認值必須是一個函數來返回
        propE: {
            type: Array,
            default: function () {
                return []
            }
        },
        // 自定義一個驗證函數
        propF: {
            validator: function (value) {
                return value > 10
            }
        }
    }
});

驗證的type類型能夠是:

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

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

組件通訊

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

自定義事件

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

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

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

  • dispatchEvent
  • addEventListener

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

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

<div id="app">
    <p>總數:{{total}}</p>
    <my-component
            @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>

Vue.component('my-component', {
    template: `
        <div>
            <button @click="handleIncrease">+</button>
            <button @click="handlereduce">-</button>
        </div>
        `,
    data() {
        return {
            counter: 0
        }
    },
    methods: {
        handleIncrease: function () {
            this.counter++;
            this.$emit('increase', this.counter);
        },
        handlereduce: function () {
            this.counter--;
            this.$emit('reduce', this.counter)
        }
    }
});

new Vue({
    el: '#app',
    data: {
        total: 0
    },
    methods: {
        handleGetTotal: function (total) {
            this.total = total;
        }
    }
});

在改變組件的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還能夠用來建立自定義的表單輸入組件,進行數據雙向綁定。

<div id="app">
    <p>總數:{{total}}</p>
    <my-component v-model="total"></my-component>
    <button @click="handleReduce">-</button>
</div>

Vue.component('my-component', {
    props: ['value'],
    template: `<input :value="value" @input="updateValue">`,
    methods: {
        updateValue: function () {
            this.$emit('input', event.target.value)
        }
    }
});

new Vue({
    el: '#app',
    data: {
        total: 10
    },
    methods: {
        handleReduce: function () {
            this.total--;
        }
    }
});

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

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

非父子組件通訊

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

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

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

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

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

Vue.component('my-component', {
    template: `<button @click="handleDispatch">派發事件</button>`,
    methods: {
        handleDispatch: function () {
            this.$dispatch('on-message', '來自內部組件的數據')
        }
    }
});
new Vue({
    el: '#app',
    data: {
        message: ''
    },
    events: {
        'on-message': function (msg) {
            this.message = msg;
        }
    }
});

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

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

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

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

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

var bus = new Vue();

Vue.component('component-a', {
    template: `<button @click="handleEvent">傳遞事件</button>`,
    methods: {
        handleEvent: function () {
            bus.$emit('on-message', '來自組件component-a的內容')
        }
    }
});
var app = new Vue({
    el: '#app',
    data: {
        message: ''
    },
    mounted: function () {
        var _this = this;
        // 在實例初始化時,監聽來自bus實例的事件
        bus.$on('on-message', function (msg) {
            _this.message = msg;
        })
    }
});

首先建立了一個名爲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來爲子組件指定一個索引名稱。

<div id="app">
    <button @click="handleRef">經過ref獲取子組件實例</button>
    <component-a ref="comA"></component-a>
</div>

Vue.component('component-a', {
    template: `<div>子組件</div>`,
    data() {
        return {
            message: '子組件內容'
        }
    },
});
var app = new Vue({
    el: '#app',
    methods: {
        handleRef: function () {
            // 經過$refs來訪問指定的實例
            var msg = this.$refs.comA.message;
            console.log(msg);
        }
    }
});

在父組件模板中,子組件標籤上使用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共存。

<div id="app">
    <child-component>
        <h2 slot="header">標題</h2>
        <p>正文的內容</p>
        <p>更多正文的內容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>

Vue.component('child-component', {
    template: `
<div class="container">
    <div class="header">
        <slot name="header"></slot>
    </div>
    <div class="main">
        <slot></slot>
    </div>
    <div class="footer">
        <slot name="footer"></slot>
    </div>
</div>    
    `,
});
var app = new Vue({
    el: '#app',
});

子組件內聲明瞭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

<div id="app">
    <child-component>
        <h2 slot="header">標題</h2>
        <p>正文的內容</p>
        <p>更多正文的內容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>

Vue.component('child-component', {
    template: `
<div class="container">
    <div class="header">
        <slot name="header"></slot>
    </div>
    <div class="main">
        <slot></slot>
    </div>
    <div class="footer">
        <slot name="footer"></slot>
    </div>
</div>    
    `,
    mounted: function () {
        var header = this.$slots.header;
        var main = this.$slots.default;
        var footer = this.$slots.footer;
        console.log(footer);
        console.log(footer[0].elm.innerHTML);
    }
});
var app = new Vue({
    el: '#app',
});

經過$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特性,組件就會把它的內容當作模板,而不是把它當內容分發,這讓模板更靈活。

<div id="app">
    <child-component inline-template>
        <div>
            <h2>在父組件中定義子組件的模板</h2>
            <p>{{message}}</p>
            <p>{{msg}}</p>
        </div>
    </child-component>
</div>

Vue.component('child-component', {
    data() {
        return {
            msg: '在子組件中聲明的數據'
        }
    }
});
var app = new Vue({
    el: '#app',
    data: {
        message: '在父組件中聲明的數據'
    }
});

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

動態組件

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

<div id="app">
    <button @click="handleChangeView('A')">切換到A</button>
    <button @click="handleChangeView('B')">切換到B</button>
    <button @click="handleChangeView('C')">切換到C</button>
    <component :is="currentView"></component>
</div>

var app = new Vue({
    el: '#app',
    data: {
        currentView: 'comA'
    },
    components: {
        comA: {
            template: `<div>組件A</div>`
        },
        comB: {
            template: `<div>組件B</div>`
        },
        comC: {
            template: `<div>組件C</div>`
        },
    },
    methods: {
        handleChangeView: function (component) {
            this.currentView = 'com' + component
        }
    }
});

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

<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更新已經完成

<div id="app">
    <div id="div" v-if="showDiv">這是一段文本</div>
    <button @click="getText">獲取div內容</button>
</div>

var app = new Vue({
    el: '#app',
    data: {
        showDiv: false
    },
    methods: {
        getText: function () {
            this.showDiv = true;
            this.$nextTick(function () {
                var text = document.getElementById('div');
                console.log(text.innerHTML);
            })
        }
    }
});

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手動地掛載一個未掛載的實例。這個方法返回實例自身,於是能夠鏈式調用其餘實例方法。

<div id="app"></div>

var MyComponent = Vue.extend({
    template: `<div>Hello {{name}}</div>`,
    data() {
        return {
            name: 'Andy'
        }
    }
});
new MyComponent().$mount('#app');

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

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

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

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

相關文章
相關標籤/搜索