6.組件 ★

1.組件(Component)是Vue.js最核心的功能废登。
2.組件的注冊有全局注冊和局部注冊兩種方式谷市。全局注冊后,任何Vue示例都可以使用狐树。
全局注冊示例:

Vue.component('my-component',{
  //選項
})

my-component 就是注冊的組件自定義標(biāo)簽名稱,推薦使用小寫加減號分割的形式命名三娩。
要在富實例中使用這個組件庵芭,必須要在示例創(chuàng)建前注冊,之后就可以用<my-component></my-component>的形式來使用組件了雀监,示例:

<div id="app">
    <my-component></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'<div>這里是組件的內(nèi)容</div>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

template的DOM結(jié)構(gòu)必須被一個元素飽含喳挑,如果直接寫內(nèi)容,不帶<div></div> 是無法渲染的滔悉。
在Vue實例中,使用components選項可以局部注冊組件单绑,注冊后的組件只有在該實例作用域下有效回官。組件中可以使用components選項來注冊組件,使組件可以嵌套搂橙。示例:

<div id="app">
    <my-component></my-component>
</div>
<script>
    var child = {
        template:'<div>這里是組件的內(nèi)容</div>'
    }
    var app = new Vue({
        el:'#app',
        components:{
            'my-component':child
        }
    })
</script>

3.Vue組件的模版在某些情況下會收到HTML的限制歉提,比如<table>內(nèi)規(guī)定只允許是<tr>、<td>区转、<th>等這些表格元素苔巨,所以在<table>內(nèi)直接使用組件是無效的。在這種情況下废离,可以使用 特殊的is屬性 來掛載組件侄泽,示例:

<div id="app">
    <table>
        <tbody is="my-component"></tbody>
    </table>
</div>
<script>
    Vue.component('my-component',{
        template:'<div>這里是組件的內(nèi)容</div>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

tbody在渲染時,會被替換為組件的內(nèi)容蜻韭。常見的限制元素還有<ul>悼尾、<ol>、<select>肖方。
如果使用的是字符串模版闺魏,是不受限制的,比如.vue單文件用法等俯画。
4.除了template選項外析桥,組件中還可以像Vue實例哪樣使用其他的選項,比如data艰垂、computed泡仗、methods等,但是在使用data時材泄,和實例稍有區(qū)別沮焕,data必須是函數(shù),然后將數(shù)據(jù)return 出去拉宗,例如:

<div id="app">
    <my-component></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'<div>{{message}}</div>',
        data:function(){
            return {
                message:'組件內(nèi)容'
            }
        }
    })
    var app = new Vue({
        el:'#app'
    })
</script>

JavaScript對象是引用關(guān)系峦树,所以如果return出的對象引用了外部的一個對象辣辫,那這個對象就是共享的,任何一方修改都會同步魁巩。例如:

<div id="app">
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>
<script>
    var data = {
        counter:0
    }
    Vue.component('my-component',{
        template:'<button @click="counter++">{{counter}}</button>',
        data:function(){
            return data;
        }
    })
    var app = new Vue({
        el:'#app'
    })
</script>

組件使用了3此急灭,但是點擊任意一個button,3個的數(shù)字都會加1谷遂,那是因為組件的data引用的是外部的對象葬馋,這肯定不是我們期望的效果,所以給組件返回一個新的data對象來獨立肾扰,示例:

<div id="app">
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'<button @click="counter++">{{counter}}</button>',
        data:function(){
            return {
                counter:0
            };
        }
    })
    var app = new Vue({
        el:'#app'
    })
</script>

這樣畴嘶,點擊3個按鈕就互補影響了,達(dá)到了復(fù)用的目的集晚。
5.組件不僅僅是要把模版的內(nèi)容進(jìn)行復(fù)用窗悯,更重要的是組件間要進(jìn)行通信。通常偷拔,父組件的模版中包含子組件蒋院,父組件要正向地向子組件傳遞數(shù)據(jù)或參數(shù),子組件接收后根據(jù)參數(shù)的不同來渲染不同的內(nèi)容或執(zhí)行操作莲绰。這個正向傳遞數(shù)據(jù)的過程就是通過props來實現(xiàn)的欺旧。
6.在組件中,使用選項props來聲明須要從父級接收的數(shù)據(jù)蛤签,props的值可以是兩種辞友,一種是字符串?dāng)?shù)組,一種是對象震肮。
7.我們構(gòu)造一個數(shù)組踏枣,接受一個來自父級的數(shù)據(jù)message,并把它在組件模版中渲染钙蒙,示例代碼:

<div id="app">
    <my-component message="來自父組件的數(shù)據(jù)"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['message'],
        template:'<div>{{message}}</div>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

props中聲明的數(shù)據(jù)與組件data函數(shù)return的數(shù)據(jù)主要區(qū)別就是props的來自父級茵瀑,而data中的是組件自己的數(shù)據(jù),作用域是組件本身躬厌,這兩種數(shù)據(jù)都可以在模版template及計算屬性computed和方法methods中使用马昨。上例的數(shù)據(jù)message就是通過props從父級傳遞過來的,在組件的自定義標(biāo)簽上直接寫該props的名稱扛施,如果要傳遞多個數(shù)據(jù)鸿捧,在props數(shù)組中添加項即可。
8.由于HTML特性不區(qū)分大小寫疙渣,當(dāng)使用DOM模版時匙奴,駝峰命名(camelCase)的props名稱要轉(zhuǎn)為短橫分割命名(kebab-case),如:

<div id="app">
    <my-component warning-text="提示信息"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['warningText'],
        template:'<div>{{warningText}}</div>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

9.有時候妄荔,傳遞的數(shù)據(jù)并不是直接寫死的泼菌,而是來自父級的動態(tài)數(shù)據(jù)谍肤,這時可以使用指令v-bind來動態(tài)綁定props的值,當(dāng)父組件的數(shù)據(jù)變化時哗伯,也會傳遞給子組件荒揣。示例代碼:

<div id="app">
    <input type="text" v-model="parentMessage">
    <my-component :message="parentMessage"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['message'],
        template:'<div>{{message}}</div>'
    })
    var app = new Vue({
        el:'#app',
        data:{
            parentMessage:''
        }
    })
</script>

這里用v-model綁定了父級的數(shù)據(jù)parentMessage,當(dāng)通過輸入框任意輸入時焊刹,子組件接收到的props “message” 也會實時響應(yīng)系任,并更新組件模版。
注意虐块,如果要直接傳遞數(shù)字俩滥、布爾值、數(shù)組贺奠、對象举农,而且不使用v-bind,傳遞的僅僅是字符串敞嗡,嘗試下面的示例來對比:

<div id="app">
    <my-component message="[1,2,3]"></my-component>
    <my-component :message="[1,2,3]"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['message'],
        template:'<div>{{message.length}}</div>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

同一個組件使用了兩次,區(qū)別是第二次使用的是v-bind航背。渲染后的結(jié)果是喉悴,第一個是7,第二個是才是數(shù)組的長度3玖媚。
10.Vue2.x通過props傳遞數(shù)據(jù)是單向的箕肃。也就是父組件數(shù)據(jù)變化時會傳遞給子組件,但是反過來步行今魔。
11.業(yè)務(wù)中經(jīng)常會遇到兩種需要改變prop的情況勺像,一種是父組件傳遞初始值近來,子組件將它作為初始值保存起來错森,在自己的作用域下可以隨意使用和修改吟宦。這種情況可以在組件data內(nèi)再聲明一個數(shù)據(jù),引用父組件的prop涩维,示例代碼:

<div id="app">
    <my-component :init-count="1"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['initCount'],
        template:'<div>{{count}}</div>',
        data:function(){
            return {
                count:this.initCount
            }
        }
    })
    var app = new Vue({
        el:'#app'
    })
</script>

組件中聲明了數(shù)據(jù)count殃姓,它在組件初始化時會獲取來自父組件的initCount,之后就與之無關(guān)了瓦阐,只用維護count蜗侈,這樣就可以避免直接操作initCount。
另一種情況就是prop作為需要被轉(zhuǎn)變的原始值傳入睡蟋。這種情況用計算屬性就可以了踏幻,例如:

<div id="app">
    <my-component :width="100"></my-component>
</div>
<script>
    Vue.component('my-component',{
        props:['width'],
        template:'<div :style="style">組件內(nèi)容</div>',  
        computed:{
            style:function(){
                return {
                    width: this.width + 'px',
                    border: '1px solid black'
                }
            }
        }
    })
    var app = new Vue({
        el:'#app'
    })
</script>

因為CSS傳遞寬度要帶單位(px),但是每次都寫太麻煩戳杀,而且數(shù)值計算一般是不帶單位的该面,所以統(tǒng)一在組件內(nèi)使用計算屬性就可以了夭苗。
注意,在JavaScript中對象和數(shù)組是引用類型吆倦,指向同一個內(nèi)存空間听诸,所以props是對象和數(shù)組時,在子組件內(nèi)改變是會影響父組件的蚕泽。
12.上面介紹的props選項的值都是一個數(shù)組晌梨,除了數(shù)組外,還可以是對象须妻,當(dāng)prop需要驗證時仔蝌,就需要對象寫法。
一般當(dāng)你的組件需要提供給別人使用時荒吏,推薦都進(jìn)行數(shù)據(jù)驗證敛惊,比如某個數(shù)據(jù)必須是數(shù)字類型,如果傳入字符串绰更,就會在控制臺彈出警告瞧挤,以下是幾個prop的示例:

 Vue.component('my-component',{
        props:{
            //必須是數(shù)字類型
            propA:Number,
            //必須是字符串或數(shù)字類型
            propB:[String,Number],
            //布爾值,如果沒有定義儡湾,默認(rèn)值就是true
            propC:{
                type:Boolean,
                default:true
            },
            //數(shù)字特恬,而且是必傳
            propD:{
                type:Number,
                required:true
            },
            //如果是數(shù)組或?qū)ο螅J(rèn)值必須是一個函數(shù)來返回
            propE:{
                type:Array,
                default:function(){
                    return [];
                }
            },
            //自定義一個驗證函數(shù)
            propF:{
                validator:function(value){
                    return value > 10;
                }
            }
        }

驗證的type類型可以是:

  • String
  • Number
  • Boolean
  • Object
  • Array
  • Function
    type也可以是一個自定義構(gòu)造器徐钠,使用instanceof檢測癌刽。
    當(dāng)prop驗證失敗時,在開發(fā)版本下回在控制臺拋出一條警告尝丐。
    13.Vue組件的通信場景:父子組件通信显拜、兄弟組件通信、跨級組件通信爹袁。
    14.當(dāng)子組件需要向父組件傳遞數(shù)據(jù)時远荠,就要用到自定義事件。v-on除了監(jiān)聽DOM事件外失息,還可以用于組件之間的自定義事件矮台。
    15.與JavaScript的設(shè)計模式——觀察者模式中的dispatchEvent和addEventListener這兩個方法類似。Vue組件也有一套模式根时,子組件用$emit()來觸發(fā)時間瘦赫,父組件用$on()來監(jiān)聽子組件的事件。
    父組件也可以直接在子組件的自定義標(biāo)簽上使用v-on來監(jiān)聽子組件觸發(fā)的自定義事件蛤迎,示例代碼:
<div id="app">
    <p>總數(shù):{{total}}</p>
    <my-component 
        @increase="handleGetTotal"
        @reduce="handleGetTotal"></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'\
        <div>\
            <button @click="handleIncrease">+1</button>\
            <button @click="handleReduce">-1</button>\
        </div>',  
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            handleIncrease:function(){
                this.counter ++;
                this.$emit('increase',this.counter);
            },
            handleReduce:function(){
                this.counter --;
                this.$emit('reduce',this.counter);
            }
        }
    })
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleGetTotal:function(total){
                this.total = total;
            }
        }
    })
</script>

上面的例子中确虱,子組件有兩個按鈕,分別實現(xiàn)加1和減1的效果替裆,在改變組件的data "counter"后校辩,通過$emit()再把它傳給父組件窘问,父組件用v-on:increase 和 v-on:reduce (示例使用的是語法糖)。$emit方法的第一個參數(shù)是自定義事件的名稱宜咒,例如示例的increase和reduce后面的參數(shù)都是要傳遞的數(shù)據(jù)惠赫,可以不填或填寫多個。
16.除了用 v-on 在組件上監(jiān)聽自定義事件外故黑,也可以監(jiān)聽DOM事件儿咱,這時可以用.native 修飾符表示監(jiān)聽的是一個原生事件,監(jiān)聽的是該組件的根元素场晶,示例如下:

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

17.使用 v-model
Vue2.x可以在自定義組件上使用 v-model指令混埠,示例:

<div id="app">
    <p>總數(shù):{{total}}</p>
    <my-component v-model="total"></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'<button @click="handleClick">+1</button>',  
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            handleClick:function(){
                this.counter ++;
                this.$emit('input',this.counter);
            }
        }
    })
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        }
    })
</script>

仍然是點擊按鈕加1的效果,不過這次組件$emit()的事件名是特殊的input诗轻,在使用組件的父級钳宪,并沒有在<my-component>上使用@input="handler",而是直接用了v-model綁定的一個數(shù)據(jù)total扳炬。這也可以稱作是一個語法糖吏颖,因為上面的示例可以間接地用自定義事件來實現(xiàn):

<div id="app">
    <p>總數(shù):{{total}}</p>
    <my-component @input="handleGetTotal"></my-component>
</div>
<script>
    Vue.component('my-component',{
        template:'<button @click="handleClick">+1</button>',  
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            handleClick:function(){
                this.counter ++;
                this.$emit('input',this.counter);
            }
        }
    })
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleGetTotal:function(total){
                this.total = total;
            }
        }
    })
</script>

v-model還可以用來創(chuàng)建自定義的表單輸入組件,進(jìn)行數(shù)據(jù)雙向綁定恨樟,例如:

<div id="app">
    <p>總數(shù):{{total}}</p>
    <my-component v-model="total"></my-component>
    <button @click="handleReduce">-1</button>
</div>
<script>
    Vue.component('my-component',{
        props:['value'],
        template:'<input :value="value" @input="updateValue">',  
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            updateValue:function(event){
                this.$emit('input',event.target.value);
            }
        }
    })
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleReduce:function(){
                this.total --;
            }
        }
    })
</script>

實現(xiàn)這樣一個具有雙向綁定的v-model組件要滿足下面兩個要求:

  • 接收一個value屬性
  • 在有新的value時觸發(fā)input事件
    18.在Vue.js 2.x中半醉,推薦使用一個空的Vue實例作為中央事件總線(bus),也就是一個中介厌杜。示例代碼:
<div id="app">
    {{ message }}
    <component-a></component-a>
</div>
<script>
    var bus = new Vue();

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

首先創(chuàng)建了一個名為bus的空Vue實例计螺。然后全局定義了組件component-a夯尽;最后創(chuàng)建Vue實例app,在app初始化時登馒,也就是在生命周期mounted鉤子函數(shù)里監(jiān)聽了來自bus的事件on-message匙握,而在組件component-a中,點擊按鈕會通過bus把事件on-message發(fā)出去陈轿,此時app就會接收到來自bus的事件圈纺,進(jìn)而在回調(diào)里完成自己的業(yè)務(wù)邏輯。
這種方法巧妙而輕量地實現(xiàn)了任何組件間的通信麦射,包括父子蛾娶、兄弟、跨級潜秋。如果深入使用蛔琅,可以擴展bus實例,給它添加data峻呛、methods罗售、computed等選項辜窑,這些都是可以公用的,在業(yè)務(wù)中寨躁,尤其是協(xié)同開發(fā)時非常有用穆碎,因為經(jīng)常需要共享一些通用的信息,比如用戶登錄的昵稱职恳、性別所禀、郵箱等,還有用戶的授權(quán)token等话肖。只需在初始化時讓bus獲取一次北秽,任何時間,任何組件就可以從中直接使用了最筒,在單頁面富應(yīng)用(SPA)中會很實用贺氓。項目比較大時,可以選擇更好的狀態(tài)慣例解決方案vuex床蜘。
19.除了中央事件總線bus外辙培,還有兩種方法可以實現(xiàn)組件間通信:父鏈 和子組件索引。
20.在子組件中邢锯,使用this.$parent 可以直接訪問該組件的父實例或組件扬蕊,父組件也可以通過this.$children 訪問它的所有的子組件,而且可以遞歸向上或向下無限訪問丹擎,直到根實例或最內(nèi)層的組件尾抑。示例:

<div id="app">
    {{ message }}
    <component-a></component-a>
</div>
<script>
    var bus = new Vue();

    Vue.component('component-a',{
        template:'<button @click="handleEvent">通過父鏈直接修改數(shù)據(jù)</button>',  
        methods:{
            handleEvent:function(){
                //訪問到父鏈后,可以做任何操作蒂培,比如直接修改數(shù)據(jù)
                this.$parent.message = '來自組件component-a的內(nèi)容';
            }
        }
    })
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })
</script>

盡管Vue允許這樣操作再愈,但在業(yè)務(wù)中,子組件應(yīng)該盡可能地避免依賴父組件的數(shù)據(jù)护戳,更不應(yīng)該去主動修改它的數(shù)據(jù)翎冲,因為這樣使得父組件緊耦合,只看父組件媳荒,很難理解父組件的狀態(tài)抗悍,因為它可能被任意組件修改,理想情況下钳枕,只有組件自己能修改它的狀態(tài)缴渊。父子組件最好還是通過props和$emit來通信。
21.當(dāng)子組件較多時鱼炒,通過this.$children 來一一遍歷出我們需要的一個組件實例是比較困難的疟暖,尤其是組件動態(tài)渲染時,它們的序列是不固定的。
Vue提供了子組件索引的方法俐巴,用特殊的屬性ref來為子組件指定一個索引名稱骨望,示例:

<div id="app">
    <button @click="handleRef">通過ref獲取子組件實例</button>
    <component-a ref="comA"></component-a>
</div>
<script>
    Vue.component('component-a',{
        template:'<div>子組件</div>',  
        data:function(){
            return {
                message:'子組件內(nèi)容'
            }
        }
    })
    var app = new Vue({
        el:'#app',
        methods:{
            handleRef:function(){
                //通過$refs來訪問指定的實例
                var msg = this.$refs.comA.message;
                console.log(msg);
            }
        }
    })
</script>

在父組件模版中,子組件標(biāo)簽上使用ref指定一個名稱欣舵,并在父組件內(nèi)通過this.$refs 來訪問指定名稱的子組件擎鸠。$refs只在組件渲染完成后才填充,并且它是非響應(yīng)式的缘圈。它僅僅作為一個直接訪問子組件的應(yīng)急方案劣光,應(yīng)當(dāng)避免在模板或計算屬性中使用$refs。
22.當(dāng)需要讓組件組合使用糟把,混合父組件的內(nèi)容和子組件的模板時绢涡,就會用到slot,這個過程叫作內(nèi)容分發(fā)(transclusion)遣疯。以<app>為例雄可,它有兩個特點:

  • <app>組件不知道它的掛載點會有什么內(nèi)容。掛載點是由<app>的父組件決定的缠犀。
  • <app>組件很可能有它自己的模板数苫。
    props傳遞數(shù)據(jù)、events觸發(fā)事件和slot內(nèi)容分發(fā)就構(gòu)成了Vue組件的3個API來源辨液,再復(fù)雜的組件也是由這3部分構(gòu)成的虐急。
  1. slot。如下圖滔迈,一個比較常規(guī)的網(wǎng)站布局:


    image.png

這個網(wǎng)站由一級導(dǎo)航止吁、二級導(dǎo)航、左側(cè)列表燎悍、正文以及底部版權(quán)信息5個模塊組成敬惦,如果要將它們都組件化,這個結(jié)構(gòu)可能會是:

<app>
    <menu-main></menu-main>
    <menu-sub></menu-sub>
    <div class="container">
        <menu-left></menu-left>
        <container></container>
    </div>
</app>

當(dāng)需要讓組件組合使用间涵,混合父組件的內(nèi)容與子組件的模板時仁热,就會用到slot榜揖,這個過程叫做內(nèi)容分發(fā)(transclusion)勾哩。
以 <app>為例,它有兩個特點:

  • <app>組件不知道它的掛載點會有什么內(nèi)容举哟。掛載點的內(nèi)容是由<app>的父組件決定的思劳。
  • <app>組件很可能有它自己的模板
    props傳遞數(shù)據(jù)、events觸發(fā)事件 和 slot 內(nèi)容分發(fā)就構(gòu)成了Vue組件的3個API來源妨猩,再復(fù)雜的組件也是由這3部分構(gòu)成的潜叛。
    編譯的作用域。比如父組件有如下模板:
<child-component>
  {{ message }}
</child-component>

這里的message就是一個slot,但是它綁定的是父組件的數(shù)據(jù)威兜,而不是組件<child-component>的數(shù)據(jù)销斟。
父組件模板的內(nèi)容是在父組件作用域內(nèi)編譯,子組件模板的內(nèi)容是在子組件作用域內(nèi)編譯椒舵。例如下面的示例:

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

這里的狀態(tài)showChild綁定的是父組件的數(shù)據(jù)蚂踊,如果想在子組件上綁定,那應(yīng)該是:

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

因此笔宿,slot分發(fā)的內(nèi)容犁钟,作用域是在父組件上的。
單個slot泼橘。在子組件內(nèi)使用特殊的<slot>元素就可以為這個子組件開啟一個slot(插槽)涝动,在父組件模板里,插入在子組件標(biāo)簽內(nèi)的所有內(nèi)容將替代子組件的<slot>標(biāo)簽及它的內(nèi)容炬灭。示例代碼:

<div id="app">
    <child-component>
        <p>分發(fā)的內(nèi)容</p>
        <p>更多分發(fā)的內(nèi)容</p>
    </child-component>
</div>
<script>
    Vue.component('child-component',{
        template:'\
        <div>\
            <slot>\
                <p>如果父組件沒有插入內(nèi)容醋粟,我將作為默認(rèn)出現(xiàn)</p>\
            </solt>\
        </div>'
    });
    var app = new Vue({
        el:'#app'
    })
</script>

子組件child-component的模板內(nèi)定義了一個<slot>元素,并且用一個<p>作為默認(rèn)的內(nèi)容担败,在父組件沒有使用slot時昔穴,會渲染這段默認(rèn)的文本;如果寫入了slot提前,那就會替換整個<slot>吗货。所以上例渲染后的結(jié)果為:

<div id="app">
    <div>
        <p>分發(fā)的內(nèi)容</p>
        <p>更多分發(fā)的內(nèi)容</p>
    </div>
</div>

注意,子組件<slot>內(nèi)的備用內(nèi)容狈网,它的作用域是子組件本身宙搬。
具名slot。給<slot>元素指定一個name后可以分發(fā)多個內(nèi)容拓哺,具名slot可以與單個slot共存勇垛,例如:

<div id="app">
    <child-component>
        <h2 slot="header">標(biāo)題</h2>
        <p>正文內(nèi)容</p>
        <p>更多的正文內(nèi)容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>
<script>
    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'
    })
</script>

子組件內(nèi)聲明了3個<slot>元素,其中在<div class="main">內(nèi)的<slot>沒有使用name特性士鸥,它將作為默認(rèn)slot出現(xiàn)闲孤,父組件沒有使用slot特性的元素與內(nèi)容都將出現(xiàn)在這里。如果沒有指定默認(rèn)的匿名slot烤礁,父組件內(nèi)多余的內(nèi)容片段都將被拋棄讼积。上例最終渲染結(jié)果為:

<div id="app">
  <div class="container">
    <div class="header">
      <h2>標(biāo)題</h2>
    </div> 
    <div class="main"> 
      <p>正文內(nèi)容</p> 
      <p>更多的正文內(nèi)容</p> 
    </div> 
    <div class="footer">
      <div>底部信息</div>
    </div>
  </div>
</div>

在組合使用組件時,內(nèi)容分發(fā)API至關(guān)重要脚仔。
作用域插槽勤众。是一種特殊的slot据沈。使用一個可以復(fù)用的模板替換已渲染元素匀借。概念比較難理解窒篱,簡單示例:

<div id="app">
    <child-component>
        <template scope="props">
            <p>來自父組件的內(nèi)容</p>
            <p>{{props.msg}}</p>
        </template>
    </child-component>
</div>
<script>
    Vue.component('child-component',{
        template:'\
        <div class="container">\
                <slot msg="來自子組件的內(nèi)容"></slot>\
        </div>'
    });
    var app = new Vue({
        el:'#app'
    })
</script>

觀察子組件的模板,在<slot>元素上有一個類似props傳遞數(shù)據(jù)給組件的寫法 msg = "xxx"拂蝎,將數(shù)據(jù)傳到了插槽账阻。父組件中使用了<template>元素火焰,而且擁有一個scope="props" 的特性今布,這里的props只是一個臨時變量,就像v-for="item in items"里面的item 一樣阻问。template 內(nèi)可以通過臨時變量props訪問來自子組件插槽的數(shù)據(jù)msg茅坛。

<div id="app">
  <div class="container">
    <p>來自父組件的內(nèi)容</p> 
    <p>來自子組件的內(nèi)容</p>
  </div>
</div>

作用域插槽更具代表性的用例是列表組件,允許組件自定義應(yīng)該如何渲染列表每一項则拷。示例代碼:

<div id="app">
    <my-list :books="books">
        <!--作用域插槽也可以是具名的slot-->
        <template slot="book" scope="props">
            <li>{{ props.bookName }}</li>
        </template>
    </my-list>
</div>
<script>
    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">\
                <!--這里也可以寫默認(rèn)slot內(nèi)容-->\
            </slot>\
            </ul>\
        '
    });
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                { name:'《Vue.js實戰(zhàn)》'},
                { name:'《JavaScript 語言精粹》'},
                { name:'《JavaScript 高級程序設(shè)計》'}
            ]
        }
    })
</script>

子組件my-list接收一個來自父級的prop數(shù)組books贡蓖,并且將它在name為book的slot上使用v-for指令循環(huán),同時暴露一個變量bookName煌茬。
作用域插槽的使用場景就是既可以復(fù)用子組件的slot斥铺,又可以使slot內(nèi)容不一致。如果上例還在其他組件內(nèi)使用坛善,<li>的內(nèi)容渲染權(quán)是由使用者掌握的晾蜘,而數(shù)據(jù)卻可以通過臨時變量(比如props)從子組件內(nèi)獲取。
訪問slot眠屎。Vue.js 2.x提供了用來訪問被slot分發(fā)的內(nèi)容的方法 $slots剔交,示例:

<div id="app">
    <child-component>
        <h2 slot="header">標(biāo)題</h2>
        <p>正文內(nèi)容</p>
        <p>更多的正文內(nèi)容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>
<script>
    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'
    })
</script>

通過slots可以訪問某個具名slot,this.slots.default包括了所有沒有被包含在具名slot中的節(jié)點改衩。$slots在業(yè)務(wù)中幾乎用不到岖常,在用render函數(shù)創(chuàng)建組件時會比較有用,但主要還是用于獨立組件開發(fā)中葫督。

24.遞歸組件竭鞍。組件在它的模板內(nèi)可以遞歸地調(diào)用自己,只要給組件設(shè)置name的選項就可以了橄镜。示例代碼:

<div id="app">
    <child-component :count="1"></child-component>
</div>
<script>
    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>'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

設(shè)置name后偎快,在組件模板內(nèi)就可以遞歸使用了,不過需要注意洽胶,必須給一個條件來限制遞歸數(shù)量晒夹,否則會拋出錯誤:max stack size exceeded。
組件遞歸使用可以用來開發(fā)一些具有未知層級關(guān)系的獨立組件姊氓,比如級聯(lián)選擇器和樹形控件等丐怯。
25.內(nèi)聯(lián)模板。組件的模板一般都是在template選項內(nèi)定義的他膳。Vue提供了一個內(nèi)聯(lián)模板的功能响逢,在使用組件時绒窑,給組件標(biāo)簽使用inline-template特性棕孙,組件就會把它的內(nèi)容當(dāng)作模板,而不是把它當(dāng)內(nèi)容分發(fā),這讓模板更靈活蟀俊。示例:

<div id="app">
   <child-component inline-template>
        <div>
            <h2>在父組件中定義子組件的模板</h2>
            <p>{{message}}</p>
            <p>{{msg}}</p>
        </div>
   </child-component>
</div>
<script>
    Vue.component('child-component',{
        data:function(){
            return {
                msg:'在子組件聲明的數(shù)據(jù)'
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            message:'在父組件聲明的數(shù)據(jù)'
        }
    })
</script>

渲染后的結(jié)果為:

<div id="app">
  <div>
    <h2>在父組件中定義子組件的模板</h2> 
    <p>在父組件聲明的數(shù)據(jù)</p> 
    <p>在子組件聲明的數(shù)據(jù)</p>
  </div>
</div>

在父組件中聲明的數(shù)據(jù)message和子組件中聲明的數(shù)據(jù)msg钦铺,兩個都可以渲染(如果同名,優(yōu)先使用子組件的數(shù)據(jù))肢预。這反而是內(nèi)聯(lián)模板的缺點矛洞,就是作用域比較難理解,如果不是非常特殊的場景烫映,建議不要輕易使用內(nèi)聯(lián)模板沼本。
26.動態(tài)組件。Vue.js提供了一個特殊元素<component>用來動態(tài)地掛在不同的組件锭沟,使用is特性來選擇要掛載的組件抽兆。示例:

<div id="app">
   <component :is="currentView"></component>
   <button @click="handleChangeView('A')">切換到A</button>
   <button @click="handleChangeView('B')">切換到B</button>
   <button @click="handleChangeView('C')">切換到C</button>
</div>
<script>
    
    var app = new Vue({
        el:'#app',
        components:{
            comA:{
                template:'<div>組件A</div>'
            },
            comB:{
                template:'<div>組件B</div>'
            },
            comC:{
                template:'<div>組件C</div>'
            }
        },
        data:{
            currentView:'comA'
        },
        methods:{
            handleChangeView:function(component){
                this.currentView = 'com' + component;
            }
        }
    })
</script>

動態(tài)地改變currentView的值就可以動態(tài)掛載組件了。也可以直接綁定在組件對象上:

<div id="app">
  <component :is="currentView"></component>
</div>
<script>
  var Home = {
    template:'<p>Welcome home ! </p>'
  }
var app = new Vue({
  el:'#app',
  data:{
    currentView: Home
  }
})
</script>

27.異步組件族淮。當(dāng)你的工程足夠大辫红,使用的組件足夠多時,是時候考慮下性能問題了祝辣,因為一開始把所有的組件都加載是沒有必要的一筆開銷贴妻。好在Vue.js允許將組件定義為一個工廠函數(shù),動態(tài)地解析組件蝙斜。Vue.js只在組件需要渲染時觸發(fā)工廠函數(shù)名惩,并且把結(jié)果緩存起來,用于后面的再次渲染孕荠。例如:

<div id="app">
    <child-component></child-component>
</div>
<script>
    Vue.component('child-component',function(resolve,reject){
        window.setTimeout(function(){
            resolve({
                template:'<div>我是異步渲染的</div>'
            });
        },2000);
    });
    var app = new Vue({
        el:'#app'
    })
</script>

工廠函數(shù)接收一個resolve回調(diào)绢片,在收到從服務(wù)器下載的組件定義時調(diào)用。也可以調(diào)用reject(reason)指示加載失敗岛琼。這里setTimeout 指示為了演示異步底循,具體的下載邏輯可以自己決定,比如把組件配置攜程一個對象配置槐瑞,通過Ajax來請求熙涤,然后調(diào)用resolve傳入配置選項。
28.$nextTick困檩。場景描述:有一個div祠挫,默認(rèn)用v-if將它隱藏,點擊一個按鈕后悼沿,改變v-if的值等舔,讓它顯示出來,同時拿到這個div的文本內(nèi)容糟趾。如果v-if的值是false慌植,直接去獲取div的內(nèi)容是獲取不到的甚牲,因為此時div還沒有被創(chuàng)建出來,那么應(yīng)該在點擊按鈕后蝶柿,改變v-if的值為true丈钙,div才會被創(chuàng)建,此時再去獲取交汤,示例代碼如下:

<div id="app">
    <div id="div" v-if="showDiv">這是一段文本</div>
    <button @click="getText">獲取div內(nèi)容</button>
</div>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            showDiv:false
        },
        methods:{
            getText:function(){
                this.showDiv = true;
                var text = document.getElementById('div').innerHTML;
                console.log(text)
            }
        }
    })
</script>

這段代碼并不難理解雏赦,但是運行后在控制臺會拋出一個錯誤:cannot read property 'innerHTML' of null, 意思是獲取不到div元素。這就涉及Vue一個重要概念:異步更新隊列芙扎。
Vue在觀察到數(shù)據(jù)變化時并不是直接更新DOM星岗,而是開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變戒洼。在緩沖時會去除重復(fù)數(shù)據(jù)伍茄,從而避免不必要的計算和DOM操作。然后施逾,在下一個事件循環(huán)tick中敷矫,Vue刷新隊列并執(zhí)行實際(已去重的)工作。所以如果你用一個for循環(huán)來動態(tài)改變是數(shù)據(jù)100次汉额,其實它只會應(yīng)用最后一次改變曹仗,如果沒有這種機制,DOM就要重繪100次蠕搜,這固然是一個很大的開銷怎茫。
Vue會根據(jù)當(dāng)前瀏覽器環(huán)境優(yōu)先使用原生的Promise.then和MutationObserver,如果都不支持妓灌,就會采用setTimeout代替轨蛤。
知道了Vue異步更新DOM的原理,上面的示例的報錯也就不難理解了虫埂。
事實上祥山,在執(zhí)行this.showDiv=true;時,div仍然還是沒有被創(chuàng)建出來掉伏,知道下一個Vue事件循環(huán)時缝呕,才開始創(chuàng)建。$nextTick就是用來知道什么時候DOM更新完成的斧散,上面的示例改為:

<div id="app">
    <div id="div" v-if="showDiv">這是一段文本</div>
    <button @click="getText">獲取div內(nèi)容</button>
</div>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            showDiv:false
        },
        methods:{
            getText:function(){
                this.showDiv = true;
                this.$nextTick(function(){
                    var text = document.getElementById('div').innerHTML;
                    console.log(text)
                })
            }
        }
    })
</script>

這時在點擊按鈕供常,控制臺就打印出div的內(nèi)容“這是一段文本”了。
理論上鸡捐,我們應(yīng)該不用去主動操作DOM栈暇,因為Vue的核心思想就是數(shù)據(jù)驅(qū)動DOM,但在很多業(yè)務(wù)里箍镜,我們避免不了會使用一些第三方庫源祈,比如popper.js煎源、swiper等,這些基于原生javascript的庫都有創(chuàng)建和更新及銷毀的完整生命周期新博,與Vue配合使用時,就要利用好$nextTick脚草。

  1. X-Template赫悄。如果你沒有使用webpack、gulp等工具馏慨,試想一下你的組件template的內(nèi)容很冗長埂淮、復(fù)雜,如果都在JavaScript里拼接字符串写隶,效率很低倔撞。Vue提供了另一種定義模板的方式,在<script>標(biāo)簽里使用 text/x-template類型慕趴,并且指定一個id痪蝇,將這個id賦給template。示例:
<div id="app">
    <my-component></my-component>
    <script type="text/x-template" id="my-component">
        <div> 這是組件的內(nèi)容 </div>
    </script>
</div>
<script>
    Vue.component('my-component',{
        template:'#my-component'
    })
    var app = new Vue({
        el:'#app'
    })
</script>

在<script>標(biāo)簽里冕房,可以愉快地寫HTML代碼躏啰,不用考慮換行等問題。
Vue的初衷并不是濫用它耙册,因為它將模板和組件的其他定義隔離了给僵。
我們后續(xù)會使用webpack來編譯.vue的單文件,從而優(yōu)雅地解決HTML書寫的問題详拙。
30.手動掛在實例帝际。我們現(xiàn)在所創(chuàng)建的實例都是通過new Vue()的形式創(chuàng)建出來的。在一些非常特殊的情況下饶辙,我們需要動態(tài)地去創(chuàng)建Vue實例蹲诀,Vue提供了Vue.extend 和 mount 兩個方法來手動掛載一個實例。 Vue.extend 是基礎(chǔ)Vue 構(gòu)造器弃揽,創(chuàng)建一個“子類”侧甫,參數(shù)是一個包含組件選項的對象。 如果Vue實例在實例化時沒有收到el選項蹋宦,它就處于“未掛載”狀態(tài)披粟,沒有關(guān)聯(lián)的DOM元素±淙撸可以使用mount()手動地掛載一個未掛載的實例守屉。
這個方法返回實例自身,因而可以鏈?zhǔn)秸{(diào)用其他實例方法蒿辙。示例:

<div id="mount-div">
</div>
<script>
    var MyComponent = Vue.extend({
        template:'<div>Hello:{{name}}</div>',
        data:function(){
            return {
                name:'Aresn'
            }
        }
    });
    new MyComponent().$mount('#mount-div')
</script>

運行后拇泛,id為mount-div的div元素會被替換為組件MyComponent的template的內(nèi)容:

<div>Hello:Aresn</div>

除了這種寫法外滨巴,以下兩種寫法也是可以的:

new MyComponent({
  el:'#mount-div'
});
//或者 ,在文檔之外渲染并且隨后掛載
var component = new MyComponent().$mount();
document.getElementById('mount-div').appendChild(component.$el);

手動掛載實例(組件)是一種比較極端的高級用法俺叭,在業(yè)務(wù)中幾乎用不到恭取,只在開發(fā)一些復(fù)雜的獨立組件時可能會使用,所以只做了解就好熄守。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜈垮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子裕照,更是在濱河造成了極大的恐慌攒发,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晋南,死亡現(xiàn)場離奇詭異惠猿,居然都是意外死亡,警方通過查閱死者的電腦和手機负间,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門偶妖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人政溃,你說我怎么就攤上這事餐屎。” “怎么了玩祟?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵腹缩,是天一觀的道長。 經(jīng)常有香客問我空扎,道長藏鹊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任转锈,我火速辦了婚禮盘寡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撮慨。我一直安慰自己竿痰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布砌溺。 她就那樣靜靜地躺著影涉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪规伐。 梳的紋絲不亂的頭發(fā)上蟹倾,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼鲜棠。 笑死肌厨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豁陆。 我是一名探鬼主播柑爸,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盒音!你這毒婦竟也來了表鳍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤里逆,失蹤者是張志新(化名)和其女友劉穎进胯,沒想到半個月后用爪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體原押,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年偎血,在試婚紗的時候發(fā)現(xiàn)自己被綠了诸衔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡颇玷,死狀恐怖笨农,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帖渠,我是刑警寧澤谒亦,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站空郊,受9級特大地震影響份招,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狞甚,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一锁摔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哼审,春花似錦谐腰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至春霍,卻和暖如春桦踊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背终畅。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工籍胯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竟闪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓杖狼,卻偏偏與公主長得像炼蛤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝶涩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355