Vue專欄 - 組件(各種傳值方式, 組件生命週期, 插槽等)

Vue專欄 - 組件基礎(各種傳值方式, 組件生命週期, 插槽等)

目錄:

  • 組件的出現背景和基本使用

    • 全局組件註冊及實例

    • 局部組件註冊及實例

  • 組件之間的傳值與通信

    • 父組件向子組件傳值

    • 子組件通過$emit向父組件傳值

    • 兄弟組件之間傳值和跨級進行傳值

    • Vuex提一嘴

  • 組件生命週期

  • slot插槽

    • 匿名slot

    • 具名slot

組件的出現背景和基本使用

組件可以說是Vuejs中設計的亮點之一, 而組件化這個概念只要你不是剛入行的小白, 都會略有耳聞, 那麼到底什麼是真正的組件化, 如果要我說, 我們可以想象一下我們在寫js的時候, 爲什麼要寫函數, 就是爲了把相同功能的代碼抽離出來, 便於複用, 其實不僅僅在js業務中, 我們在其他領域也會遇到這種情況 比如一個按鈕, 我們以前往往需要在很多地方頻繁的寫樣式, 寫行爲, 綁定事件, 那麼可不可以像函數一樣把功能獨立出去呢? 組件化就是來解決這個問題的, 而組件呢實際上就是把某一些業務分離成獨特的業務模塊, 至於怎麼分離學習了以後你自然會知道

我們先來看一個實例, 我們現在要實現一個功能, 需要寫一個表單, 裏面有很多個輸入框, 對應不同的需求, 如下

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-i5F8dt5V-1583475990499)('..')]

像上方這樣的功能如果我們不採用組件化的方式進行書寫的話勢必是如下這樣

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>組件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
        .form-wrapper {
            width: 400px;
            padding: 20px;
            box-sizing: border-box;
            border: 2px solid #ccc;
            margin: 100px auto; 
        }

        .form-wrapper > div {
            width: 100%;
            height: 40px;
            line-height: 40px;
        }
        .form-wrapper > div > span {
            display: inline-block;
            width: 80px;
            height: 100%;
            text-align: right;
            margin-right: 20px;
            font-size: 14px;
            color: #434343;
        }

        .form-wrapper > div > input {
            border: 1px solid #ccc;
            background: none;
            height: 30px;
            font-size: 10px;
            width: 200px;
            padding-left: 10px;
            box-sizing: border-box;
            border-radius: 2px;
        }
    </style>
</head>
<body>
    <div class="form-wrapper">
        <div class="input-group">
            <span>用戶名</span><input type="text" placeholder="請輸入用戶名">
        </div>
        <div class="input-group">
            <span>密碼</span><input type="password" placeholder="請輸入密碼">
        </div>
        <div class="input-group">
            <span>手機號</span><input type="text" placeholder="請輸入手機號">
        </div>
        <div class="input-group">
            <span>性別</span><input type="text" placeholder="請輸入性別">
        </div>
        <div class="input-group">
            <span>地址</span><input type="text" placeholder="請輸入地址">
        </div>
    </div>

    <script>
          
    </script>
</body>
</html>

我們也會發現很多問題把, 不可避免的是我們一直在做copysir, 也就是不斷在複製代碼, 因爲這幾個輸入框除了某一小部分文字不同一樣, 其他地方均是一致的, 從樣式到結構都是一致的, 我們如果順着函數的思維來走的話, 我們要將相同的部分抽離出來封裝好, 不同的部分通過參數進行傳遞, 而這也恰恰是組件化的思維, 那麼我們來試試把

...
<body>
    <div class="form-wrapper">
        <common-input input-name='用戶名' ></common-input>
        <common-input input-name='密碼' input-type='password'></common-input>
        <common-input input-name='手機號' ></common-input>
        <common-input input-name='性別' ></common-input>
        <common-input input-name='地址' ></common-input>
    </div>

    <script>
        // 定義組件模板
        Vue.component('common-input', {
            template: `<div class='input-group'>
                         <span>{{ inputName }}</span><input :type="inputType" :placeholder="placeholder">
                      </div>`,
            // 要接收過來的屬性props   
            props: {
                'inputName': {
                    type: String,
                    default: ''
                },
                'inputType': {
                    type: String,
                    default: 'text'
                }
            },
            // 計算屬性
            computed: {
                placeholder() {
                    return `請輸入${ this.inputName }`
                }
            }          
        })

        const vm = new Vue({
            el: '.form-wrapper',
        })          
    </script>
</body>    
...

上方的寫法你也不用馬上就看懂它, 混個臉熟, 而接下來我們真正進入組件化的瞭解

Vue.component() ---- 全局註冊組件

Vue.component是我們用來創建組件的方式, 該靜態方法接收兩個參數:

  1. 要創建的組件名

    我們要使用組件, 至少要給組件一個名字, 就像系統內置的div, p, 這些我們都可以看成是組件, 而div, p就是它的名字

    Vue.component('my-button', {
        //...配置項
    })    
    
    

    小提示

    我們的組件名字不能與系統的標籤名重名, 否則失效

  2. 該組件的配置項

    根Vue實例一樣, 我們創建的組件也會經過Vue最終渲染進頁面中, 通過頻繁使用組件從而達到複用的效果, 而這個配置項則是我們要給當前組件搭配的一些特性

    Vue.component('my-button', {
        // 配置項
        template: '<button>Click</button>',
        data() {
            return {
    
            }
        },
        methods: {
    
        }
        ... 
    })
    

    在某種程度上, 組件就是可複用的Vue實例, 但是他們又與根實例不太相同, 它不需要el屬性來告訴它需要接管哪塊區域, 因爲它一定會被用在某塊被Vue根實例接管的區域中, 但是它需要一個template屬性來告訴它 他應該長成啥樣

    根實例: 就是帶el屬性的, 直接接管Vue區域的, 用new Vue創建的實例
    組件: 通過Vue.component創建
    組件實例: 當你通過Vue.component創建了一個組件, 這個時候你每在根組件實例中使用一次這個組件, 實際上等於創建了一個組件實例在使用, 也就是等於new了一個該組件

    但是這些組件實例又會具備自己單獨管理的data, methods, computed等屬性, 就像每個構造函數構造出來對象也會攜帶一些基礎屬性, 這些基礎屬性會在開發中讓每一個組件實例都大致一樣 但又彼此獨立

局部註冊組件

未來我們接觸到腳手架搭建工程以後, 大多使用的都將是局部組件, 反而全局組件用的會比較少

const childComponent = {
    template: `<div>ChildComponent</div>`
}

const vm = new Vue({
    el: '#app',
    components: {
        childComponent // 在components屬性中進行註冊, 註冊完成以後就可以在該Vue實例接管的區域隨意使用了
    }
})

我們可以再來看一個實例, 我們要做一個計數器, 這個計數器點擊加號就可以數字遞增, 點擊減號就可以實現遞減, 同時我要在頁面的多個地方使用這個功能, 小夥伴們可以想想用原生js我們應該怎麼實現這個功能, 功能詳情如下圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yS57Z6QJ-1583475990500)('...')]

我們使用Vue組件的話其實一切都會簡單順暢很多

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>組件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
     <div id="app">
       <my-counter></my-counter>
       <my-counter></my-counter>
       <my-counter></my-counter>
    </div>

    <script>
        const myCounter = {
             // template屬性, 告訴Vue我這個組件長什麼樣
            template: `<div>
                            <button @click='handleClick(1)'>+</button>
                            <span>{{ count }}</span>
                            <button @click='handleClick(-1)'>-</button>
                       </div>`,
            data() {
               return {
                 count: 0
               }
            },
            methods: {
                handleClick(num) {
                    this.count += num;
                }
            }
        }

        const vm = new Vue({
            el: '#app',
            components: {
                myCounter, // 註冊組件
            }
        })          
    </script>
</body>
</html>

注意點

細心的朋友可能已經發現了, 在組件中我們的data並不是直接寫成一個對象形式的, 二是寫成了一個函數形式, 這是爲什麼呢?

JS的對象是一種引用值, 而如果我們在組件中直接寫成一個對象形式的data的話,Vue會將這個data的引用掛載到每一個組件實例中, 當不同的組件實例中的data都來自於同一個引用的話, 那麼就會出問題, 一個組件實例的data中的數據發生了改變, 其他所有組件實例中的data都會進行變化, 就因爲他們拿的是同一個引用, 而我們寫成一個函數的話, 每一次都會返會一個新的對象, 進而使得對象之間互不干擾

組件之間的傳值與通信

我們知道在函數中, 我們封裝了一個函數, 它能夠得到多次複用的精髓和靈魂其實是因爲函數的參數, 參數可以讓我們使用函數變得更加的靈活和隨心

而在組件中, 如果沒有一些動態的類似於函數參數的玩意供我們驅使的話, 那麼組件也肯定是如死水一般的, 所以我們來到了組件之間的傳值與通信, 互相傳值與通信使得我們的組件擁有了類似於傳參的能力, 下面我們一個一個來看看

  • 父組件向子組件傳值

    父組件: 就是一個組件和另一個組件產生了嵌套關係, 父組件是嵌套外層
    子組件: 如上, 子組件是嵌套內層

    就像我們上面寫的在根實例中運用組件, 那麼根實例實際上就是那些組件的父組件, 跑不掉的, 不管怎樣組件實例都會有父組件

    父組件向子組件的傳值我們一般使用組件屬性和props搭配使用直接看個實例

    ...
    <body>
         <div id="app">
         <!-- 這個地方進行傳值 -->
        <my-article article-title='Everything will be ok'></my-article>
    </div>
    
    <script>
        const myArticle = {
            template: `<div>
                            <h1>{{ articleTitle }}</h1>
                       </div>`,
            // 這個props作爲數組存在, 其內部子元素代表父組件要給我傳遞過來的值的變量名           
            props: ['articleTitle'], 
        }
        const vm = new Vue({
            el: '#app',
            components: {
                // 註冊局部組件
                myArticle
            }
        })    
    </script>
    
    </body>
    ...
    

    效果如下

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3snAnn7R-1583475990501)('...')]

    其實我們發現, 作爲父組件我們只需要將要傳遞給子組件的值當作屬性卸載子組件身上, 而在子組件內部我們使用``props```這個屬性來接收傳過來的值就行

    關於props

    上方我們寫的props爲數組形式,實際上它還可以寫的更加詳細一點, 能夠表達的意思也將更加的全面

    props可以寫成對象形式, 對象中有三個配置項

    • type: 父組件傳遞過來的屬性必須是什麼類型, 如果類型不匹配則報警告

    • default: 如果父組件沒有傳遞該屬性過來, 我們可以給他一個默認值進行展示

    • required: 是否必須傳遞, 父組件是否必須傳遞該值, 如果填寫爲true, 則父組件沒有傳遞會報警告

    ...
    <body>
        <div id="app">
            <!-- 這裏沒有進行message的傳值, 所以會報警告 -->
            <child-component></child-component>
            <child-component message='u r hero'></child-component>
        </div>
    
        <script>
            const childComponent = {
                template: `<p>{{ message }}</p>`,
                props: {
                    'message': {
                        type: String,
                        default: 'hello',
                        required: true,
                    }
                }
            }
    
            let vms = new Vue({
                el: '#app',
                components: {
                    childComponent
                }
            })
        </script>
        
    </body>    
    ...
    
    

    輸出結果如下

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g2X7DDey-1583475990502)('...')]

    Vue跟React一樣遵循一個單向數據流原則, 即數據從頂而下的流動, 所以不存在子組件直接向父組件傳值的操作, 這樣會導致數據變得暴亂不可控, 無法感知數據到底是在哪裏被更改的, 誰擁有數據, 誰改變數據, 我們也不要在子組件中強行的改變父組件的數據, 我看到那樣的代碼就頭皮發麻, 因爲這樣一操作你的數據來源就不在純淨了, 可能在未來某一天你某個地方出現了bug, 但是你根本無法感知這個bug出現在哪,因爲產生bug的數據再任何一個組件中都有可能更改它, 這樣是極度危險的, 再次提醒: 數據屬於哪個組件, 哪個組件纔有權更改他

  • 子組件通過$emit向父組件傳值

    上方說了, 因爲Vue遵行單向數據流的原則, 所以子組件不能向父組件進行傳值, 但是某些情況下, 我們可能必須要告訴父組件一些信息, 比如子組件是一個計時器, 倒計時十秒以後告訴父組件倒計時完畢, 父組件進行後續的處理, 那麼再某些特殊情況下, 子組件如何在不打破單向數據流平衡的情況下優雅的向父組件傳值呢?

    如果你閱讀過JavaScript設計模式這本書的話, 你一定知道觀察者模式, 如果沒有閱讀過, 也沒關係, 跟着筆者一步一步來, 你學完這個子組件向父組件傳值, 那你對觀察者模式也會有一定的瞭解了

    $emit

    Vue給我們提供一個$emit來讓子組件觸發父組件的某些事件, 從而讓父組件感知一些子組件數據的變化, 或者傳遞子組件的值給父組件, 而且這種方式不會造成數據逆流, 具體情況看了實例我相信你會明白

    ...
    <body>
        <div id="app">
            <child-component @getcount='getCount' message='u r hero'></child-component>
            <div>展示子組件傳遞過來的值: {{ showSonValue }}</div>
        </div>
    
        <script>
            const childComponent = {
                template: `<div>
                            <h1>我是父組件傳遞過來的值: {{ message }}</h1>
                            <div @mouseover='handleOver'>觸摸我給父組件傳值啦</div>
                        </div>`,
                data() {
                    return {
                        sonMsg: '我是子組件data中數據'
                    }
                },
                methods: {
                    // 當子組件自身的鼠標觸摸事件觸發的時候, 子組件直接驅動父組件綁定在
                    // 子組件身上的自定義事件getCount, 並且將自己的songMsg傳遞過去
                    handleOver() {
                        this.$emit('getcount', this.sonMsg);
                    }
                },
                props: {
                    'message': {
                        type: String,
                        default: 'hello',
                        required: true,
                    }
                }
            }
    
            let vms = new Vue({
                el: '#app',
                components: {
                    childComponent
                },
                data: {
                    showSonValue: ''
                },
                methods: {
                    // 父組件的getcount事件一旦被觸發, 則會走這個函數,
                    // 函數參數的value爲子組件傳遞過來的值
                    getCount(value) {
                        console.log(value);
                        this.showSonValue = value;
                    }
                }
            })
        </script>
    </body>    
    ...
    

    結果如下

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-h7Moafr3-1583475990502)('...')]

    整個傳值的過程就好像是一種發佈訂閱操作, 父組件丟一個自定義事件在子組件身上, 子組件可以在任何時候任何地方通過$emit觸發這個自定義事件, 並且傳遞任何值進該事件的對應執行方法, 這就是子組件向父組件傳值的精髓

    小提示

    在Vue2.x以後, 我們不僅可以使用自定義事件傳遞數據, 還可以通過v-model, Vue2.x以後支持在組件中使用v-model, 而v-model其實是@input和:value的語法糖, 我們在子組件中只要通過$emit觸發父組件的input事件, 並將相應的值傳遞進去,父組件會通過v-model感知到並且修改綁定的值

  • 兄弟組件之間傳值和跨級組件進行傳值

    在我們的日常業務場景中, 不可能就只有父子組件傳值這麼簡單, 有時候兄弟組件之間也需要進行傳值, 當然我們可以通過子組件向父組件傳值, 父組件在向另一個子組件傳值, 但是這總顯得有點不倫不類和多此一舉, 又或者我們需要跨級傳值呢, 這就顯得有點雞肋了

    事件總線, Event bus

    於是Vue在2.x中推出了事件總線, Event Bus, 在講這個事件總線之前, 大家必須瞭解$on這個哥們, 我們知道#emit是用來觸發事件的, 而這哥們是用來綁定事件的, 和v-on一樣, 只是這哥們可以直接在方法中給dom元素綁定事件, v-on就只能在dom元素身上了

    瞭解了$on, 你可能還有點迷, 直接上代碼, 說不定就不迷了

    ...
    <body>
        <div id='app'>
            <!-- 在這裏面我放置兩個組件, 這兩個組件呈兄弟關係 -->
            <cmp-a></cmp-a> 
            <cmp-b></cmp-b>
        </div>
    
        <script>
            
            const bus = new Vue(); // 這哥們是創建的一個用來傳遞事件的車車
    
            // 組件A
            const cmpA = {
                template: `我是組件A, 我目前接收到的兄弟B傳過來的值爲: {{ msg }}`,
                data() {
                    return {
                        msg: '', // 用來綁定b傳遞的值, 初始化爲空
                    }
                },
                // 生命週期函數mounted, 在頁面掛載完畢自動觸發
                mounted() {
                    // 我們之前創建了一個bus的Vue實例,這個Vue實例可以觸發事件, 也可以綁定事件
                    // 那麼我們給這個bus實例綁定一個事件, 這個$on就是v-on一樣, 等於我們在bus身上
                    // 綁定了一個自定義事件getCmpB, 只是bus實例沒有任何dom元素, 也沒有接管任何
                    // 區域, 所以想要觸發bus上的這個事件, 只有$emit
                    bus.$on('getCmpB', (value) => this.msg = value);
                }
            }
    
    
            // 組件B
            const cmpB = {
                template: `<p @mouseover='handleOver'>我是組件B觸摸我我就要開始傳值給兄弟組件A拉</p>`,
                data() {
                    return {
                         msgB: '我是B組件傳遞的值'
                    }
                },
                methods: {
                    handleOver() {
                        // 當觸摸事件一觸發, 我們直接觸發bus身上的getCmpB事件
                        bus.$emit('getCmpB', this.msgB); 
                    }
                }
            }
    
            // 真正渲染的Vue實例
            const vm = new Vue({
                el: '#app',
                components: {
                    cmpA,
                    cmpB
                }
            })
        </script>
    </body>
    ...
    

    效果如下

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-E8n3eT9h-1583475990503)('..')]

    事件總線用一個比方來說就是, 你打電話給中介說你要買房, 中介說有合適的房馬上聯繫你, 然後某一天中介打電話告訴你說有個人要賣房, 把這個例子應用到上面的實例中, 你就是A, 中介就是bus, 那個人就是B

    事件總線對於兄弟組件傳值和跨級傳值相當有效和舒服, 對父子間通信也能做到遊刃有餘

    小提示

    除去事件總線以外, 我們還有一種方式可以進行各種組件的通信, 但是這種方式及其不推薦使用, 因爲它是反模式的, 那就是父鏈和子鏈, 每個組件身上都會有一個parentparent和children屬性, 我們可以從組件A的parentA,parent找到組件A的父級, 拿到以後再從這個父級的children找到B, 一個流程訪問到b也就是cmpA.parent.parent.children.cmpB, 這樣可以取到b組件實例上的任意value值, 但是這個方法是極度不推薦而且繁瑣的, 僅僅用來提一嘴, 也不會有人使用這個方法進行傳值

  • Vuex

    ok, 到了Vuex, Vuex是套用來管理Vue中數據的工具, 這裏我也是給你們混個臉熟, 它相對來說比較複雜, 但是任何企業級的開發中都會用到Vuex, 因爲它相當的出色, 上述的這些傳值方式再Vuex面前就是不值一提, 關公面前耍大刀的感覺, 後面的博客會着重寫寫Vuex, 有興趣的朋友也可以自己去看看

組件的生命週期

好的, 來到了生命週期, Vue作爲一種顛覆式開發方式, 在生命週期中, 你將會再次感受到它的強大, 忍不住讚揚它一句: Powerful

生命週期 - lifecycle, 每個組件從創建到銷燬會有一系列的運作過程, 就像我們人一樣, 從出生到死亡也是一個生命從無到有的生命週期, 生老病死是我們每個人的生命週期, 而創建到銷燬則是一個組件的生命週期, 我們人到了18歲自動成年64歲自動退休, 而組件到了不同的階段也會觸發不同的事件

比如我們創造了一個組件, 當組件被加載的時候, 我們寫的綁定事件是什麼時候開始被綁定的? 我們書寫的變量是什麼時候被真正加載完畢可以讀取的? 這些都是一個組件在不同的階段做的不同的事情, 而這些生命週期方法的運行時間點就解答了我們的問題, 同時生命週期方法也給了我們在不同階段添加自己的代碼的機會

beforeCreate - 除非組件重新創建, 否則只會被調用一次

第一個組件生命週期方法beforeCreate,它會在組件實例初始化以後被系統自動調用, 當在這個生命週期函數被調用的時候, 系統是在告訴我們 它已經把組件事件和其他生命週期都已經初始化完畢了,比如什麼點擊事件, 什麼鼠標觸摸事件這裏面都給你初始化好了

created - 除非組件重新創建, 否則只會被調用一次

緊隨其後的叫做created, 這個方法會在數據被注入以後調用, 當這個方法被系統調用的時候, 它實際上是在告訴我們, data中的數據已經初始化好了, 我們已經可以訪問到data中的數據了, 也可以訪問我們寫的方法了

beforeMount - 除非組件重新創建, 否則只會被調用一次

beforeMount, 這個函數開始跑的時候, 表示template中的模板已經被放進render函數中進行渲染開始生成虛擬dom了, render函數的概念在後面寫源碼的時候會講到, 當下你只要理解這個函數會在虛擬dom映射真實dom也就是頁面正式掛載前觸發, 掛載就是你能不能在dom樹中看到你寫的這些html元素, 也意味着你能不能操控dom

mounted - 除非組件重新創建, 否則只會被調用一次

mounted, 代表整個頁面已經掛載完畢, 我們可以進行任意的dom操作了, 真實dom已經生成, 自此頁面進入了活躍狀態, 等待頁面更新或者銷燬

beforeUpdate - 只要頁面需要更新他就會被觸發

這哥們就是在頁面更新之前會運行, 告訴你, 哥們你的數據要更新了, 你看看在更新前你還有沒有什麼想做的沒, 沒有的話我就更新了

updated

哥們, 我這已經更新完畢了, 又再等待下一次更新了, 你看看合不合你心意

beforeDestroy

組件被銷燬前的時候調用, 啥是銷燬, 就是要死之前, 比如你要講將這個組件從dom樹種移除了, 這哥們再被移除之前會觸發這個鉤子函數

destroyed

已經死了, 組件已經被銷燬了, 一切都歸於塵埃, 在這裏面我們可以卸載一些組件之前的依賴比如計時器之類的, 就像人死了 家裏人就會把他的衣服一起燒了這種感覺

Vue組件生命週期總共8個, 各自在不同的時間點觸發, 下面我們來看看實例

...
<body>
       <div id="app">
        <cmp-a></cmp-a>
    </div>

    <script>
        const cmpA = {
            template: `<div>
                        <p> {{ msg }} </p>
                        <button @click='handleClick'>ClickMe Update</button>
                       </div>`,
            data() {
                return {
                    msg: '你們看看多久可以訪問到我'
                }
            },
            methods: {
                printHello() {
                    console.log('hello, 看看多久可以訪問到methods中的方法呢? ')
                },
                handleClick() {
                    this.msg = '我更新拉';
                }
            },
            beforeCreate() {
                console.log('beforeCreate觸發, 代表着組件實例初始化完畢');
                console.log('beforeCreate中的方法',this.printHello);
                console.log('beforeCreate中的el有沒有被初始化呢', this.$el)
                console.log('beforeCreate中的data', this.$data, this.msg);
                console.log('------------------------------------------');
            },
            created() {
                console.log('created方法創建, 這個時候代表數據也已經注入完畢了, 可訪問data了');
                console.log('created中的方法',this.printHello);
                console.log('created中的el有沒有被初始化呢', this.$el)
                console.log('created中的data', this.$data, this.msg);
                console.log('------------------------------------------');
            },
            beforeMount() {
                console.log('beforeMount觸發, 代表這個時候已經在將模板放進render函數編譯, 在生成虛擬dom了');
                console.log('beforeMount中的方法',this.printHello);
                console.log(document.querySelector('button'));
                console.log('beforeMount中的el', this.$el)
                console.log('beforeMount中的data', this.$data, this.msg);
                console.log('------------------------------------------');
            },
            mounted() {
                console.log('mounted頁面掛載完畢, 所有dom均可正常訪問和操控');
                console.log('mounted中的方法',this.printHello);
                console.log(document.querySelector('button'));
                console.log('mounted中的el', this.$el)
                console.log('mounted中的data', this.$data, this.msg);
                console.log('------------------------------------------');
            },
            beforeUpdate()  {
                console.log('beforeUpdate一來, 頁面要更新拉');
                console.log('beforeUpdate中的方法',this.printHello);
                console.log(document.querySelector('#app'));
                console.log('beforeUpdate中的el', this.$el)
                console.log('beforeUpdate中的data', this.$data, this.msg);
                console.log('------------------------------------------');
            },
            updated() {
                console.log('updated來嘍, 頁面更新完畢');
                console.log('------------------------------------------');
            },
            beforeDestroy() {
                console.log('beforeDestroy, 組件銷燬之前');
                console.log('------------------------------------------');
            },
            destroyed() {
                console.log('destroyed, 組件真的涼涼了');
                console.log('------------------------------------------');
            }

        }

        const vm = new Vue({
            el: '#app',
            components: {
                cmpA
            }
        })
    </script>
</body>
...

輸出結果如下, 我們可以根據輸出結果來個小總結

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gEu8iRL8-1583475990504)('...')]

  • beforeCreate只代表初始化組件實例比如初始化事件這些, 其他啥都不幹

  • created的時候數據和你寫的方法就已經注入完畢了, 我們可以訪問了, 但是虛擬dom都沒有生成

  • beforeMount的時候代表組件的template已經被丟進render函數編譯了

  • mounted編譯完成虛擬dom和真實dom映射完畢, 訪問this.$el有值

其他的其實不那麼常用, 沒啥可總結的也比較簡單一看就會

生命週期這玩意你不用馬上就很明白, 對Vue瞭解的越來越深刻, 生命週期也就玩的越來越嗨了, 而且這只是組件生命週期, Vue的一套生命週期還不止於此, 我給大家貼個Vue官網生命週期的圖解, 在以後學習源碼和render函數之後, 這個圖你也會有更加透徹的理解

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-in6IlZLR-1583475990504)('...')]

slot插槽

我們現在有個需求, 我們想在自定義組件中書寫一些html標籤或者界面, 怎麼能夠做到呢? 有的小夥伴可能會說我把html模板通過屬性傳遞過去, 然後使用v-html實現, 沒錯你這樣也行, 但是顯得不太優雅, 而Vue也考慮到了這種業務需求, 給我們提供了slot插槽, 具體是什麼意思我相信你一看實例就懂

...
<body>
    <div id="app">
        <cmp-a>
            <!-- 下面這段p元素是我想要放進A組件中的html文本 -->
            <p>這是我通過插槽插進去的內容</p>
        </cmp-a>
    </div>

    <script>
        const cmpA = {
            // 通過slot插槽標籤, 就可以輕鬆的辦到,  如果不寫slot標籤, 上方的p標籤是不會被渲染出來的
            template: `<div>
                            <slot></slot>
                       </div>`,
            data() {
                return {
                    
                }
            }
        }

        const vm = new Vue({
            el: '#app',
            components: {
                cmpA
            }
        })
    </script>
</body>
...

實現效果如下, 頁面中出現了那一句話, 而如果我們不添加slot標籤, 這個p標籤絕對是無法渲染進來

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H7x9q9gW-1583475990505)('...')]

具名插槽

有時候我們的組件結構已經寫好了, 希望父組件只給我們傳遞一些響應的簡單元素, 比如下方這種場景

我有一個文章組件, 其中header區域, article區域我都定義好了, 我希望父元素傳遞給我一些html文本, 有的html文本被渲染進header區域中, 有的html文本被渲染進article區域中, 這個時候我們就要使用到具名插槽

...
<body>
    <div id="app">
        <cmp-a>
            <!-- 我們通過給每個html元素添加上相應的屬性slot, 屬性值爲不同的值, 當Vue來解析他們的時候會將他們與子組件中的slot的name值相比對, 從而進行匹配插入 -->
            <h1 slot='header'>我是要在header中顯示的文本</h1>
            <p slot='article'>我是要在article中顯示的文本</p>
        </cmp-a>
    </div>

    <script>
        const cmpA = {
            // 通過給不同的插槽加上name屬性來使得Vue可以對應得上父組件傳遞過來的html元素身上攜帶的slot屬性
            template: `<div>
                            <header>
                                <slot name='header'></slot>    
                            </header>
                            <article>
                                <slot name='article'></slot>   
                            </article>
                       </div>`,
            data() {
                return {
                    
                }
            }
        }

        const vm = new Vue({
            el: '#app',
            components: {
                cmpA
            }
        })
    </script>
</body>
...

實現結果如下, slot這個概念還是挺簡單的

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-epmf8mK2-1583475990506)('...')]

具名slot和匿名slot就是我們日常開發中經常會使用的東西了, 至於作用域slot和$slots其實用到的不多, 有興趣的朋友可以自己去看看

至此關於組件的基本使用和操作已經寫完畢, 希望可以幫助到你, 下節書寫高階組件HOC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章