Vue 組件化開發

組件化開發

基本概念

   在最開始的時候,已經大概的聊了聊Vue是單頁面開發,用戶老是在一個頁面上進行操做,看到的不一樣內容也是由不一樣組件構成的。html

   經過用戶的操做,Vue將會向用戶展現某些組件,也會隱藏某些組件。vue

   一個Vue的項目就是一個Vue的實例對象。而用戶看到的頁面則是Vue.component的實例對象。es6

   對於一些複用性高的內容,咱們也能夠將它封裝成一個單獨的組件,如導航欄、搜索框、版權信息等等。後端

   因此說組件是Vue的核心、可是本章節不會討論Vue如何實現組件的顯示、隱藏,而是聊一聊如何使用組件。app

   image-20201115114950426

認識組件

根組件

   被掛載管理的元素塊就是一個根組件。在此根組件中能夠嵌套多個子組件,根組件通常來講一個就夠了。異步

  

<body>
    <div id="app">

    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el:"#app",
        })
    </script>
</body>

子組件

   子組件便是被嵌套在根組件中的組件,子組件可複用,而且數據都是相互獨立的。函數

   子組件能夠擁有根組件所擁有的任意的屬性,如data/methods/computed/watch/filter組件化

   須要注意一點:組件名字若是有多個單詞構成,應當寫成駝峯形式(我的推薦)。ui

   而且在模板中進行引用時,當以-進行分割。this

   image-20201115120054672

<body>

<div id="app">
    <!-- 使用全局組件 -->
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<script src="./vue.js"></script>
<script>
    // 定義組件名字與模板
    Vue.component("cpnHeader", {
        template: "<div><span>這是一個頭部組件</span></div>",
    })
    const app = new Vue({
        el: "#app",
    })
</script>
</body>

組件聲明

全局子組件

   全局子組件會自動進行註冊,任何Vue的實例都能進行使用。

   使用Vue.component("name",{})對其進行聲明並註冊。

<body>

<div id="app">
    <!-- 使用全局組件 -->
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<script src="./vue.js"></script>
<script>
    // 定義組件名字與模板
    Vue.component("cpnHeader", {
        template: "<div><span>這是一個頭部組件</span></div>",
    })
    const app = new Vue({
        el: "#app",
    })
</script>
</body>

局部子組件

   局部子組件在Vue實例下使用components進行註冊。它僅供當前實例使用,以下所示:

<body>

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

<script src="./vue.js"></script>
<script>
    // 定義組件
    const cpn = {
        template:"<div><mark>HELLO,WORLD</mark></div>",
    }

    const app = new Vue({
        el:"#app",
        components:{  // 進行註冊
            cpn,  //es6語法
        }
    })
</script>
</body>

組件模板

根標籤

   每個組件對象都應該具備template屬性,它應當是一個HTML字符串。

   而且,要擁有一個根標籤在下面,不然將會拋出警告信息:

   image-20201120200646941

   當發生這樣的警告信息,你應該檢查一下你的子組件模板,並給他套上根標籤,以下所示:

// 定義組件
    const cpn = {
        template: `
        <div>  
            <div>
                HELLO,VUE
            </div>
            <div>
                HELLO,WORLD
            </div>
        </div>
        `,
    }

抽離寫法

   若是在定義組件時在template屬性中寫HTML代碼,是不太友好的,你能夠將模板抽離出來。

  1. 使用script標籤,並添加type="text/x-template"的屬性
  2. 使用template標籤

   以下所示,使用<template>標籤配合id屬性將其做爲子組件模板:

<body>

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

<template id="cpn">
    <div>
        <div>
            HELLO,VUE
        </div>
        <div>
            HELLO,WORLD
        </div>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const cpn = {
        template: "#cpn",
    }

    const app = new Vue({
        el: "#app",
        components: { 
            cpn,
        }
    })
</script>
</body>

錯誤示範

   若是你書寫了一個DOM可識別的標籤,則是錯誤的操做,以下所示我使用了main標籤來定義組件模板,很顯然DOM認識它,就會本身先一步渲染它再交由Vue進行處理,這會致使咱們的組件模板會多一次渲染。

   image-20201115122413269

<body>

<div id="app">
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<!--子組件模板-->
<main id="cpn-header-template">
    <div>
    	<span>這是一個頭部組件</span>
    </div>
</main>

<script src="./vue.js"></script>
<script>
    var cpnHeader = {
        template: "#cpn-header-template",
    }
    const app = new Vue({
        el: "#app",
        components: {  // Vue實例內部進行註冊
            cpnHeader,
        }
    })
</script>
</body>

子組件的data

   上面已經說過,子組件能夠擁有data/methods/computed/watch/filter等對象。

   可是須要注意的是子組件的data必須是一個函數,且必須返回一個Object

   這是由於每一個子組件是相互獨立的,若是data是一個對象,那麼獲取全部的子組件數據都會引用同一個Object

   因此子組件中的data必須是一個函數,由於函數調用時會從新申請內存,返回一個全新的Object

<body>

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

<template id="cpn">
    <div>
        <p>{{childrenMessage}}</p>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const cpn = {
        template: "#cpn",
        data() {
            return {
                childrenMessage: "子組件數據",
            }
        }
    }

    const app = new Vue({
        el: "#app",
        components: {
            cpn,
        }
    })
</script>
</body>

組件通訊

通訊意義

   組件之間的通訊是十分有必要的,例如當Vue項目啓動後,父組件獲取到了一些數據,它將如何把這些數據分發到子組件上。

   再好比,子組件上產生了一個新數據,它將如何把該數據交由父組件?

props

   父組件向子組件傳遞數據時,則經過props進行傳遞。

   接收值在父組件模板中採用 - 分割命名,在props中採用駝峯式,在子組件模板中進行使用時採用與props中一樣的命名方式

   這是由於 - 分割命名方式不是Js的合法變量名,若是props中採用 - 來進行命名,子組件模板在使用時將查詢不到該變量名

   具體操做步驟以下所示:

   image-20201120204132757

<body>

<!--根組件模板-->
<div id="app">
    <father></father>
</div>

<!--子組件模板-->
<template id="son">
    <div>
<!--        ④ 子組件如今已經能夠正常渲染出該值了,使用接收的變量名便可-->
        <p>{{childerRecv}}</p>
    </div>
</template>

<!--父組件模板-->
<template id="father">
    <div>
<!--② 子組件經過v-bind,將父組件中的值存儲到props中,須要注意的是再模板中應該使用 - 進行多單詞分割-->
        <son :childer-recv="fatherMessage"></son>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const son = {
        template: "#son",
        // ③ 如今子組件中已經用childerRecv這個變量名接收到父組件中傳遞過來的值了,須要注意的是接收時使用駝峯式命名進行接收,不然模板中不會渲染
        props: ["childerRecv",]
    }

    const father = {
        template: "#father",
        components: {
            son,
        },
        data() {
            return {
                // ① 父組件的做用域內可以接收到該值了
                fatherMessage: {id: 1, name: "yunya", age: 18},
            }
        }
    }


    const app = new Vue({
        el: "#app",
        components: {
            father,
        }
    })
</script>
</body>

   上面這個是三層組件嵌套,可能看起來有點繞。我這裏有個雙層嵌套的,看起來比較簡單:

<body>
<div id="app">
    <!-- 接收: - 分割命名 -->
    <cpn :recv-msg="sendMsg"></cpn>
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <!-- 使用: 與props一樣的命名方式 -->
    <div><span>接收到的信息:{{recvMsg}}</span></div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        // props:使用駝峯式命名,爲了子組件模板中可以使用而不產生報錯
        props: ["recvMsg",],
        template: "#cpn-template",
        data: function () {
            return {}
        }
    }
    const app = new Vue({
        el: "#app",
        data: {
            sendMsg: {
                id: 1,
                name: "admin",
            }
        },
        components: {  // Vue實例內部進行註冊
            cpn,
        }
    })
</script>
</body>

   畫張圖,讓你更容易理解:

   image-20201120204754847

props數據驗證

   在上述例子中,父組件從後端獲取的數據傳遞到子組件時沒由進行任何驗證就直接渲染了,這可能致使子組件渲染錯誤。

   因此在子組件接收父組件數據時進行驗證是十分必要的流程。

   咱們能夠發現,上述例子的props接收是一個array,若是要使用驗證,則接收要用Object,以下示例:

驗證項目 描述
type 一個Array,容許的類型
required 一個Boolen,是否必須傳遞
default 任意類型
validator 一個Function,返回須要驗證的數據字段
<body>

<div id="app">
    <cpn :recv-msg="sendMsg"></cpn>
</div>

<!-- 模板 -->
<template id="cpn-template">
    <div><span>接收到的信息:{{recvMsg}}</span></div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        props: {  // props是一個Object
            recvMsg: {  // 參數驗證是一個Object
                // 容許的類型
                type: [Object, Array],
                // 是不是必須傳遞
                required: true,
                // 若是沒有傳遞的默認值
                default() {
                    return "默認值";
                },
                // 驗證,當驗證失敗後,會在調試臺顯示錯誤
                validator(v) {
                    // v就是父組件傳遞過來的數據
                    return v.id;
                },
            },
        },
        template: "#cpn-template",
        data: function () {
            return {}
        }
    }
    const app = new Vue({
        el: "#app",
        data: {
            sendMsg: {
                // id: 1,
                name: "admin",
            }
        },
        components: {  // Vue實例內部進行註冊
            cpn,
        }
    })
</script>
</body>

   因爲父組件中傳遞的數據不具備id字段,因此控制檯拋出異常,可是不會影響正常渲染:

   image-20201115155932001

$emit

   當子組件中發生某一個事件,咱們可使用父組件對其進行處理。

   使用$emit進行自定義事件,由父組件進行處理,示例以下:

   咱們使用子組件定義了一個加法的運算,可是結果倒是在父組件中顯示,須要子組件將計算結果發送給父組件。

   若是自定義事件的單詞有多個,則在Js中採用駝峯形式,在html中採用 - 分割形式

   image-20201115155119333

<body>

<div id="app">
<!--    父組件監聽add事件,而且交由fatherAdd進行處理-->
    <cpn @add="fatherAdd"></cpn>
    結果:{{showResult}}
    <!-- 結果顯示在父組件 可是計算確是在子組件 -->
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <div>
        <input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2">
        <button @click="sum">計算</button>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        template: "#cpn-template",
        data() {
            return {
                n1: 0,
                n2: 0,
            }
        },
        methods: {
            sum() {
                let sonResult = this.n1 + this.n2;
                // 自定義了一個add事件,由父組件進行監聽。而且傳遞了一個值
                this.$emit("add", sonResult);
            }
        }
    }
    const app = new Vue({
        el: "#app",
        components: {  // Vue實例內部進行註冊
            cpn,
        },
        data: {
            showResult: 0,
        },
        methods: {
            fatherAdd(v) {
                this.showResult = v;
            }
        }
    })
</script>
</body>

   仍是畫一張圖梳理一下流程,這其實都是固定的用法:

   image-20201120205801581

.sync

   若是子組件的數據來自於父組件,當子組件中的數據發生改變時咱們也想讓父組件中的數據發生一樣的改變。

   則可使用.sync修飾符(儘可能少用,會破壞單一性),以下所示:

   Vue的組件通訊.sync

<body>

<div id="app">
    <span>父組件的值:{{num}}</span>
    <cpn :son-num.sync="num"></cpn>
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <div>
        <span>子組件的值:{{sonNum}}</span>
        <p><input type="text" @keyup="changeValue" v-model="newValue" placeholder="輸入新的值"></p>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        props: ["sonNum",],
        template: "#cpn-template",
        data: function () {
            return {
                newValue: this.sonNum,
            }
        },
        methods: {
            changeValue() {
                this.$emit("update:sonNum", this.newValue)
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        data: {
            num: 100,
        },
        components: {  // Vue實例內部進行註冊
            cpn,
        },
    })
</script>
</body>

   流程圖以下:

   image-20201115162925120

組件訪問

$children

   有的時候咱們想直接經過父組件拿到子組件這個對象,調用其下面的某一個方法,可使用$children屬性完成操做。

   以下所示,父組件想調用子組件中的show()方法:

   一個父組件可能有多個子組件,因此該屬性$children是一個Array

   Vue的組件訪問$children

<body>

<div id="app">
    <button @click="btn">父組件調用子組件方法</button>
    <cpn></cpn>
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <div>
        <span>{{msg}}</span>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {
                msg: "子組件show未調用",
            }
        },
        methods: {
            show() {
                this.msg = "子組件show已調用";
            }
        }
    }

    const app = new Vue({
        el: "#app",
        components: {  // Vue實例內部進行註冊
            cpn,
        },
        methods: {
            btn() {
                // 取出第0個子組件,進行調用其下方法
                this.$children[0].show();
            }
        }
    })
</script>
</body>

$refs

   上述的訪問方法並不經常使用,由於父組件很是依賴索引值來訪問子組件。

   使用$refs來訪問子組件就方便的多,咱們須要給子組件取一個名字,再用父組件進行調用,這個是很是經常使用的手段。

   Vue的組件訪問$children

<body>

<div id="app">
    <button @click="btn">父組件調用子組件方法</button>
    <!-- 取名字 -->
    <cpn ref="nbcpn"></cpn>
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <div>
        <span>{{msg}}</span>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {
                msg: "子組件show未調用",
            }
        },
        methods: {
            show() {
                this.msg = "子組件show已調用";
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        components: {  // Vue實例內部進行註冊
            cpn,
        },
        methods: {
            btn() {
                // 根據取的名字進行調用
                this.$refs.nbcpn.show();
            }
        }
    })
</script>
</body>

$parent

   若是在子組件中想拿到父組件的對象,使用$parent便可,若是存在多層嵌套,它只會拿本身上一層。

   一個子組件,在一塊被掛載區域中只有一個父組件

   Vue的組件訪問$parent

<body>

<div id="app">
    {{msg}}
    <cpn></cpn>
</div>

<!-- 子組件模板 -->
<template id="cpn-template">
    <div>
        <button @click="btn">子組件調用父組件方法</button>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {}
        },
        methods: {
            btn() {
                this.$parent.show();
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        data: {
            msg: "父組件show未調用",
        },
        components: {  // Vue實例內部進行註冊
            cpn,
        },
        methods: {
            show() {
                // 取出本身的父組件,進行調用其下方法
                this.msg = "父組件show已調用";
            }
        }
    })
</script>
</body>

$root

   若是存在三級或以上嵌套,能夠直接使用$root來訪問根組件。與$parent使用相同,可是它是具體的一個對象,而並不是Array,因此這裏再也不作演示。

組件的動態切換

   使用:is屬性,可讓咱們動態使用不一樣的組件。

   以下,定義了一個input框和文本域兩個組件,當咱們點擊不一樣按鈕,它就會選擇不一樣的組件。

   Vue的動態改變組件

<body>

<div id="app">
    <div :is="choice"></div>
    <input type="radio" v-model="choice" value="inputCPN">文本框
    <input type="radio" v-model="choice" value="textareaCPN">文本域
</div>

<script src="./vue.js"></script>
<script>

    const inputCPN = {
        template: "<div><input type='text'/></div>",
    }

    const textareaCPN = {
        template: "<div><textarea></textarea></div>",
    }

    const app = new Vue({
        el: "#app",
        data: {
            choice: "inputCPN",
        },
        components: {
            inputCPN, textareaCPN,
        },
    })
</script>
</body>

組件的鉤子函數

   一個Vue的項目就是由不一樣的組件構成,不論是局部註冊也好,全局註冊也罷,Vue官方都給你提供了一些鉤子函數供你調用,以下圖所示:

   生命週期圖.png

鉤子函數 描述
beforeCreate 建立Vue實例以前調用
created 建立Vue實例成功後調用(能夠在此處發送異步請求後端數據)
beforeMount 渲染DOM以前調用
mounted 渲染DOM以後調用
beforeUpdate 從新渲染以前調用(數據更新等操做時,控制DOM從新渲染)
updated 從新渲染完成以後調用
beforeDestroy 銷燬以前調用
destroyed 銷燬以後調用

   以下所示,定義這幾個鉤子函數:

   vue組件鉤子函數

<div id="app">
    <p>{{msg}}</p>
</div>

<script src="./vue.js"></script>
<script>

    let app = new Vue({
        el: "#app",
        data:{
            msg:"HELLO,VUE",
        },
        beforeCreate() {
            console.log("建立Vue實例以前...");
        },
        created() {
            console.log("建立Vue實例成功...");
        },
        beforeMount() {
            console.log("準備渲染DOM...");
        },
        mounted() {
            console.log("渲染DOM完成...");
        },
        beforeUpdate() {
            console.log("準備從新渲染DOM...");
        },
        updated() {
            console.log("從新渲染DOM完成");
        },
        // 後兩個暫時體會不到
        beforeDestroy() {
            console.log("準備銷燬當前實例");
        },
        destroyed() {
            console.log("當前實例銷燬完成...");
        }
    })
</script>
</body>
相關文章
相關標籤/搜索