09-Vue組件

一、什么是組件? 什么是組件化?

組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現(xiàn)為用 is 特性進行了擴展的原生 HTML 元素。

簡單的說: 組件就是把一個很大的界面拆分為多個小的界面, 每一個小的界面就是一個組件
將大界面拆分成小界面就是組件化

組件系統(tǒng)讓我們可以用獨立可復(fù)用的小組件來構(gòu)建大型應(yīng)用,幾乎任意類型的應(yīng)用的界面都可以抽象為一個組件樹:

image

組件化的好處
可以簡化Vue實例的代碼.
可以提高代碼復(fù)用性

二、注冊組件

全局注冊

所有實例都能用全局組件。
1. 創(chuàng)建組件構(gòu)造器
通過 全局API:Vue.extend()
參數(shù):{Object} options
用法:使用基礎(chǔ) Vue 構(gòu)造器,創(chuàng)建一個“子類”。參數(shù)是一個 包含組件選項的對象。

let Profile = Vue.extend({
    // 注意: 在創(chuàng)建組件指定組件的模板的時候, 模板只能有一個根元素
    template: `
        <div>
            <img src="images/fm.jpg" alt="">
            <p>我是描述信息</p>
        </div>
    `
});

2. 注冊已經(jīng)創(chuàng)建好的組件

Vue.component('my-component', Profile);

3. 使用注冊好的組件

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

創(chuàng)建組件的簡化方式

  • 在注冊組件的時候, 除了傳入一個組件構(gòu)造器以外, 還可以直接傳入一個 對象
Vue.component('my-component', {
    template: `
        <div>
            <img src="images/fm.jpg" alt="">
            <p>我是描述信息</p>
        </div>
    `
});
  • 在編寫組件模板的時候, 除了可以在 字符串模板 中編寫以外, 還可以像 art-template 一樣在 script 中編寫
<script id="info" type="text/html">
    <div>
        <img src="images/fm.jpg" alt="">
        <p>我是描述信息</p>
    </div>
</script>
  • 在編寫組件模板的時候, 除了可以在 script 中編寫以外, vue還專門提供了一個編寫模板的標簽 template
<template id="info">
    <div>
        <img src="images/fm.jpg" alt="">
        <p>我是描述信息</p>
    </div>
</template>

上面兩種編寫模板的方式在創(chuàng)建的時候一定要記得加上 id, 在使用的時候也要加上 id名稱

Vue.component('my-component', {
    template: '#info'
});


局部注冊

我們也可以在實例選項中注冊局部組件,這樣組件只能在這個實例中使用.
可以通過某個 Vue 實例/組件的實例選項 components 注冊僅在其作用域中可用的組件:

new Vue({
    // ...
    components: {
        'my-component': {
            template: '#info'
        }
    }
});

自定義全局組件特點:
在任何一個Vue實例控制的區(qū)域中都可以使用
自定義局部組件特點:
只能在自定義的那個Vue實例控制的區(qū)域中才可以使用

三、組件中的data和methods

Vue實例控制的區(qū)域相當(dāng)于一個大的組件, 在大組件中我們可以使用datamethods
而我們自定義的組件也是一個組件, 所以在自定義的組件中也能使用datamethods

1. Vue中使用data和methods

<div id="app">
    <button @click="vueFn">vue-alert</button>
    <p>{{vueMsg}}</p>
</div>

new Vue({
    el: '#app',
    methods: {
        vueFn(){
            alert('vue-Fn');
        }
    },
    data: {
        vueMsg: 'vue-Msg'
    }
});

2. 自定義組件中使用data和methods

在自定義組件中不能像在vue實例中一樣直接使用data,而是必須通過返回函數(shù)的方式來使用data。
<template id="info">
    <div>
        <button @click="myFn">my-alert</button>
        <p>{{myMsg}}</p>
    </div>
</template>

Vue.component('my-component', {
    template: '#info',
    methods: {
        myFn(){
            alert('my-Fn');
        }
    },
    data: function () {
        return {
            myMsg: 'my-Msg'
        }
    }
});

自定義組件中的data為什么是一個函數(shù)
因為自定義組件可以復(fù)用, 為了保證復(fù)用時每個組件的數(shù)據(jù)都是獨立的, 所以必須是一個函數(shù)
看下面這個例子:

// HTML
<div id="app">
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>
<template id="info">
    <div>
        <button @click="add">增加</button>
        <p>{{counter}}</p>
    </div>
</template>
// JS
Vue.component('my-component', {
    template: '#info',
    data: function () {
        return {
            counter: 0
        }
    },
    methods: {
        add(){
            this.counter++;
        }
    }
});
new Vue({
    el: '#app',
});

運行結(jié)果: 點擊按鈕的時候只有自己按鈕下的數(shù)據(jù)會加1

image

組件中的data如果不是通過函數(shù)返回的, 那么多個組件就會共用一份數(shù)據(jù), 就會導(dǎo)致數(shù)據(jù)混亂。
組件中的data如果通過函數(shù)返回的, 那么每創(chuàng)建一個新的組件, 都會調(diào)用一次這個方法,將這個方法返回的數(shù)據(jù)和當(dāng)前創(chuàng)建的組件綁定在一起, 這樣就有效的避免了數(shù)據(jù)混亂。
如果 Vue 沒有這條規(guī)則,點擊一個按鈕就會像影響到其它所有實例:那么上面的例子中的數(shù)據(jù)就會一起加1;

四、組件切換

1. 通過 v-if / v-else

對于普通的元素我們可以通過v-if來實現(xiàn)切換,對于組件我們也可以通過v-if來實現(xiàn)切換。
因為組件的本質(zhì)就是一個自定義元素。

// HTML
<div id="app">
    <button @click="show=!show">toggle</button>
    <my-component1 v-if="show"></my-component1>
    <my-component2 v-else></my-component2>
</div>

<template id="info1">
    <div>
        <p>我是info1</p>
    </div>
</template>
<template id="info2">
    <div>
        <p>我是info2</p>
    </div>
</template>
<script>
    Vue.component('my-component1', {
        template: '#info1'
    });
    Vue.component('my-component2', {
        template: '#info2'
    });
    new Vue({
        el: '#app',
        data: {
            show: true
        }
    });
</script>

2. 通過動態(tài)組件

通過v-if/v-else-if/v-else確實能夠切換組件,但是在Vue中切換組件還有一種更專業(yè)的方式:

<component v-bind:is="需要顯示組件名稱"></component>

component我們稱之為動態(tài)組件, 也就是你讓我顯示誰我就顯示誰
通過使用 <component> 元素,動態(tài)地綁定到它的 is 特性

<div id="app">
    <button @click="toggle">toggle</button>
    <component :is="name"></component>
</div>

<template id="info1">
    <div>
        <p>我是info1</p>
    </div>
</template>
<template id="info2">
    <div>
        <p>我是info2</p>
    </div>
</template>
<script>
    Vue.component('my-component1', {
        template: '#info1'
    });
    Vue.component('my-component2', {
        template: '#info2'
    });
    new Vue({
        el: '#app',
        data: {
            show: true,
            name: 'my-component1'
        },
        methods: {
            toggle() {
                this.show = !this.show;
                this.name = this.name === 'my-component1' ? 'my-component2' : 'my-component1';
            }
        }
    });
</script>

為什么可以通過v-if切換還要有component
因為component可以配合keep-alive來保存被隱藏組件隱藏之前的狀態(tài), 而v-if會重新渲染頁面, 所以不能保存之前的狀態(tài)
<keep-alive> 包裹動態(tài)組件時,會緩存不活動的組件實例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現(xiàn)在父組件鏈中

<keep-alive>
    <component :is="name"></component>
</keep-alive>
<template id="info1">
    <div>
        <input type="checkbox">
        <p>我是info1</p>
    </div>
</template>

這里可以記住復(fù)選框的選中狀態(tài)

如果我們需要頻繁的切換頁面,每次都是在組件的創(chuàng)建和銷毀的狀態(tài)間切換,這無疑增大了性能的開銷。
這個時候我們也可以使用Vue提供了動態(tài)組件的 緩存。keep-alive 會在切換組件的時候緩存當(dāng)前組件的狀態(tài),等到再次進入這個組件,不需要重新創(chuàng)建組件,只需要從前面的緩存中讀取并渲染。

五、組件動畫

給組件添加動畫和過去給元素添加動畫一樣。
如果是單個組件就使用transition,如果是多個組件就使用transition-group。

// HTML
<div id="app">
    <button @click="toggle">toggle</button>
    <transition>
        <component :is="name"></component>
    </transition>
</div>
<template id="info1">
    <div>
        <p>我是info1</p>
    </div>
</template>
<template id="info2">
    <div>
        <p>我是info2</p>
    </div>
</template>
// CSS
.v-enter{
    opacity: 0;
    margin-left: 500px;
}
.v-enter-to{
    opacity: 1;
}
.v-enter-active{
    transition: all 3s;
}
.v-leave{
    opacity: 1;
}
.v-leave-to{
    opacity: 0;
}
.v-leave-active{
    transition: all 3s;
    margin-left: 500px;
}
// JS
<script>
    Vue.component('my-component1', {
        template: '#info1'
    });
    Vue.component('my-component2', {
        template: '#info2'
    });
    new Vue({
        el: '#app',
        data: {
            show: true,
            name: 'my-component1',
        },
        methods: {
            toggle(){
                this.show = !this.show;
                this.name = this.name === 'my-component1' ? 'my-component2' : 'my-component1';
            }
        }
    });
</script>

效果:

image

在這個案例中可以發(fā)現(xiàn)一個問題: 兩個的話是同時執(zhí)行的.
默認情況下進入動畫和離開動畫是同時執(zhí)行的, 如果想一個做完之后再做另一個, 需要指定動畫的過渡模式.

過渡模式

過渡模式常常配合多個元素或者多個組件切換時使用,有如下兩種模式:

  • in-out:新元素先進行過渡,完成之后當(dāng)前元素過渡離開。(默認為該模式)
  • out-in:當(dāng)前元素先過渡離開,離開完成后新元素過渡進入。

所以可以把上面的代碼改造一下, 就可以讓一個元素先出去, 另一個元素再進來

<transition mode='out-in'>
    <component :is="name"></component>
</transition>

六、父子組件

在一個組件中又定義了其它組件就是父子組件。
其實局部組件就是最簡單的父子組件, 因為我們可以把Vue實例看做是一個大組件。
我們在Vue實例中定義了局部組件, 就相當(dāng)于在大組件里面定義了小組件, 所以局部組件就是最簡單的父子組件

1. 如何定義父子組件

前面講過, 自定義組件中可以使用data, 可以使用methods. 當(dāng)然自定義組件中也可以使用components,
所以我們也可以在自定義組件中再定義其它組件。

  • 在全局父組件中定義子組件
Vue.component('father', {
    template: '#father',
    components: {
        'son': {
            template: '#son'
        }
    }
});
  • 在局部父組件中定義子組件
new Vue({
    el: '#app',
    components: {
        'father': {
            template: '#father',
            components: {
                'son': {
                    template: '#son'
                }
            }
        }
    }
});
  • 父子組件的使用
    把自定義子組件放到自定義父組件中, 把自定義父組件放到Vue組件中
<div id="app">
    <father></father>
</div>

<template id="father">
    <div>
        <p>我是父組件</p>
        <son></son>
    </div>
</template>
<template id="son">
    <div>
        <p>我是子組件</p>
    </div>
</template>

2. 父子組件數(shù)據(jù)傳遞

在Vue中子組件是不能訪問父組件的數(shù)據(jù)的,如果子組件想要訪問父組件的數(shù)據(jù), 必須通過父組件傳遞。

如何傳遞數(shù)據(jù)

  • 在父組件中通過v-bind傳遞數(shù)據(jù)
    傳遞格式: v-bind:自定義接收名稱 = "要傳遞數(shù)據(jù)"
<template id="father">
    <div>
        <p>{{msg}}</p>
        <!--這里將父組件的msg通過parentmsg傳遞給了子組件-->
        <son :parentmsg="msg"></son>
    </div>
</template>
  • 在子組件中通過props接收數(shù)據(jù)
    接收格式: props: ["自定義接收名稱"]
//...
components: {
    'son': {
        template: '#son',
        // 這里通過parentmsg接收了父組件傳遞過來的數(shù)據(jù)
        props: ['parentmsg']
    }
}

如何使用數(shù)據(jù)

  • 子組件使用父組件傳遞的數(shù)據(jù)
<template id="son">
    <div>
        <!--這里通過parentmsg使用了父組件傳遞過來的數(shù)據(jù)-->
        <p>{{parentmsg}}</p>
    </div>
</template>

3. 父子組件方法傳遞

在Vue中子組件是不能訪問父組件的方法的,如果子組件想要訪問父組件的方法, 必須通過父組件傳遞

如何傳遞方法

  • 在父組件中通過 v-on 傳遞方法
    傳遞格式: v-on:自定義接收名稱 = "要傳遞方法"
<template id="father">
    <div>
        <button @click="say">父組件按鈕</button>
        <!--這里通過parentsay將父組件的say方法傳遞給了子組件-->
        <son @parentsay="say"></son>
    </div>
</template>
  • 在子組件中自定義一個方法
<template id="son">
    <div>
        <button @click="sonFn">子組件按鈕</button>
    </div>
</template>
  • 在自定義方法中通過 this.$emit('自定義接收名稱'); 觸發(fā)傳遞過來的方法
components: {
    'son': {
        template: '#son',
        methods: {
            sonFn(){
                this.$emit('parentsay');
            }
        }
    }
}

和傳遞數(shù)據(jù)不同, 如果傳遞的是方法, 那么在子組件中不需要接收。
但是需要在子組件中自定義一個方法, 直接使用自定義的方法.
并且還需要在子組件自定義的方法中通過
this.$emit("自定義接收的名稱")的方法來觸發(fā)父組件傳遞過來的方法

$emit( eventName, […args] ) 觸發(fā)事件

  • {string} eventName 需要調(diào)用的函數(shù)名稱
  • [...args] 給調(diào)用的函數(shù)傳遞的參數(shù)

觸發(fā)當(dāng)前實例上的事件。附加參數(shù)都會傳給監(jiān)聽器回調(diào)。
所以子組件可以通過這個方法給父組件傳遞參數(shù).

components: {
    'son': {
        template: '#son',
        methods: {
            sonFn(){
                this.$emit('parentsay', 'son');
            }
        }
    }
}

父組件接收參數(shù):

methods: {
    say(data){
        console.log(data);
    }
}

4. 數(shù)據(jù)和方法的多級傳遞

在Vue中如果兒子想使用爺爺?shù)臄?shù)據(jù), 必須一層一層往下傳遞
在Vue中如果兒子想使用爺爺?shù)姆椒? 必須一層一層往下傳遞

七、組件中的命名

1. 注冊組件的時候使用了"駝峰命名", 那么在使用時需要轉(zhuǎn)換成"短橫線分隔命名"
例如: 注冊時: myFather -> 使用時: my-father

2. 在傳遞參數(shù)的時候如果想使用"駝峰名稱", 那么就必須寫"短橫線分隔命名"
例如: 傳遞時: parent-msg="msg" -> 接收時: props: ["parentMsg"]

3. 在傳遞參數(shù)的時候如果想使用"駝峰名稱", 那么就必須寫"短橫線分隔命名"
例如: @parent-say="say" -> this.$emit("parent-say");

八、插槽

默認情況下使用子組件時,在子組件中編寫的元素是不會被渲染的
如果子組件中有部分內(nèi)容是使用時才確定的, 那么我們就可以使用插槽
插槽就是在子組件中放一個"坑", 以后由父組件來"填"。

比如在下面這個例子中, 沒有使用插槽的話父組件在<son></son>內(nèi)編寫的內(nèi)容是無效的

<template id="father">
    <div>
        <son>
            <!--如果沒有插槽, 父組件在這里編寫的內(nèi)容是無效的-->
        </son>
    </div>
</template>
默認情況下是不能在使用子組件的時候, 給子組件動態(tài)的添加內(nèi)容的  
如果想在使用子組件的時候, 給子組件動態(tài)的添加內(nèi)容, 那么就必須使用插槽

1. 匿名插槽

沒有名字的插槽, 會利用使用時指定的內(nèi)容替換整個插槽

這里的slot標簽就是插槽, 插槽其實就是一個坑。只要有了這個坑, 那么以后使用者就可以根據(jù)自己的需要來填這個坑。

<template id="son">
    <div>
        <div>我是頭部</div>
        <slot>我是默認的內(nèi)容</slot>
        <div>我是底部</div>
    </div>
</template>

插槽可以指定默認數(shù)據(jù), 如果使用者沒有填這個坑, 那么就會顯示默認數(shù)據(jù)。
如果使用者填了這個坑, 那么就會利用使用者坑的內(nèi)容替換整個插槽。

例如: 這個father組件在使用son組件的時候填了這個坑,那么就會用父組件坑的內(nèi)容覆蓋掉整個插槽.
所以最后的效果是:

image
<template id="father">
    <div>
        <!--需求: 在使用子組件的時候給子組件動態(tài)的添加一些內(nèi)容-->
        <son>
            <div>我是追加的內(nèi)容</div>
        </son>
    </div>
</template>

匿名插槽的特點:
有多少個匿名插槽, 填充的數(shù)據(jù)就會被拷貝幾份

<template id="son">
    <div>
        <div>我是頭部</div>
        <slot>我是默認的內(nèi)容</slot>
        <slot>我是默認的內(nèi)容</slot>
        <div>我是底部</div>
    </div>
</template>

效果圖:

image

雖然我們可以指定多個匿名插槽, 但是推薦只寫一個匿名插槽

2. 具名插槽

默認情況下有多少個匿名插槽, 我們填充的數(shù)據(jù)就會被拷貝多少份,這導(dǎo)致了所有插槽中填充的內(nèi)容都是一樣的。
那么如果我們想給不同的插槽中填充不同的內(nèi)容怎么辦呢?
這個時候就可以使用具名插槽

具名插槽的使用

  • 通過插槽的name屬性給插槽指定名稱
<template id="son">
    <div>
        <div>我是頭部</div>
        <slot name="one">我是one默認的內(nèi)容</slot>
        <slot name="two">我是two默認的內(nèi)容</slot>
        <div>我是底部</div>
    </div>
</template>
  • 在使用時可以通過 slot="name" 方式, 指定當(dāng)前內(nèi)容用于替換哪一個插槽
    默認情況下填充的內(nèi)容是不會被填充到具名插槽中的,
    只有給填充的內(nèi)容指定了要填充到哪一個具名插槽之后,
    才會將填充的內(nèi)容填充到具名插槽中
<template id="father">
    <div>
        <son>
            <!--這里通過slot屬性告訴Vue,當(dāng)前的內(nèi)容是要填充到哪一個插槽中的-->
            <div slot="one">我是追加的內(nèi)容one</div>
            <!--如果沒有指定要替換哪個插槽中的內(nèi)容, 則不會被替換-->
            <div>我是追加的內(nèi)容two</div>
        </son>
    </div>
</template>

3. v-slot指令

v-slot指令是Vue2.6中用于替代slot屬性的一個指令
在Vue2.6之前, 我們通過slot屬性告訴Vue當(dāng)前內(nèi)容填充到哪一個具名插槽
從Vue2.6開始, 我們通過v-slot指令告訴Vue當(dāng)前內(nèi)容填充到哪一個具名插槽

格式: v-slot:插槽名稱
簡寫: #插槽名稱

注意: v-slot指令只能用在template標簽上

例如:

<son>
    <template v-slot:one>
        <div>我是追加的內(nèi)容one</div>
        <div>我是追加的內(nèi)容one</div>
    </template>
    <template #two>
        <div>我是追加的內(nèi)容two</div>
        <div>我是追加的內(nèi)容two</div>
    </template>
</son>

4. 作用域插槽

作用域插槽就是帶數(shù)據(jù)的插槽, 就是讓父組件在填充子組件插槽內(nèi)容時也能使用子組件的數(shù)據(jù)

如何使用作用域插槽

  • slot中通過 v-bind:數(shù)據(jù)名稱="數(shù)據(jù)名稱" 方式暴露數(shù)據(jù)
<template id="son">
    <div>
        <div>我是頭部</div>
        <!--v-bind:names="names"作用: 將當(dāng)前子組件的names數(shù)據(jù)暴露給父組件-->
        <slot v-bind:names="names"></slot>
        <div>我是底部</div>
    </div>
</template>
  • 在父組件中通過 <template slot-scope="作用域名稱"> 接收數(shù)據(jù)
<template id="father">
    <div>
        <son>
            <!--slot-scope="sonMsg"作用: 接收子組件插槽暴露的數(shù)據(jù)-->
            <template slot-scope="sonMsg">
            </template>
        </son>
    </div>
</template>
  • 在父組件的 <template></template> 中通過 作用域名稱.數(shù)據(jù)名稱 方式使用數(shù)據(jù)
<template slot-scope="sonMsg">
    <div>{{sonMsg.names}}</div>
</template>

作用域插槽的應(yīng)用場景:
子組件提供數(shù)據(jù), 父組件決定如何渲染

5. v-slot 指令代替 slot-scope

在 2.6.0 中,我們?yōu)榫呙宀酆妥饔糜虿宀垡肓艘粋€新的統(tǒng)一的語法 (即 v-slot 指令)。
它取代了 slot 和 slot-scope

也就是說我們除了可以通過 v-slot 指令告訴Vue內(nèi)容要填充到哪一個具名插槽中,還可以通過v-slot指令告訴Vue如何接收作用域插槽暴露的數(shù)據(jù)

格式: v-slot:插槽名稱="作用域名稱"
簡寫: #插槽名稱="作用域名稱"

<!--匿名插槽的默認名稱是default-->
<template v-slot:default="sonMsg">
    <div>{{sonMsg.names}}</div>
</template>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是組件? 組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝...
    youins閱讀 9,705評論 0 13
  • 此文基于官方文檔,里面部分例子有改動,加上了一些自己的理解 什么是組件? 組件(Component)是 Vue.j...
    陸志均閱讀 3,945評論 5 14
  • 三、組件 組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML元素,封裝可重用...
    小山居閱讀 706評論 0 1
  • 【2019-3-4更新】Vue 2.6+修改了部分語法,對插槽的使用有了較多的更新。在本文中筆者在相應(yīng)位置給出了更...
    果汁涼茶丶閱讀 10,499評論 2 36
  • Vue 實例 屬性和方法 每個 Vue 實例都會代理其 data 對象里所有的屬性:var data = { a:...
    云之外閱讀 2,368評論 0 6

友情鏈接更多精彩內(nèi)容