Vue 元件化開發

雲崖先生發表於2020-11-20

元件化開發

基本概念

   在最開始的時候,已經大概的聊了聊Vue是單頁面開發,使用者總是在一個頁面上進行操作,看到的不同內容也是由不同元件構成的。

   通過使用者的操作,Vue將會向使用者展示某些元件,也會隱藏某些元件。

   一個Vue的專案就是一個Vue的例項物件。而使用者看到的頁面則是Vue.component的例項物件。

   對於一些複用性高的內容,我們也可以將它封裝成一個單獨的元件,如導航欄、搜尋框、版權資訊等等。

   所以說元件是Vue的核心、但是本章節不會討論Vue如何實現元件的顯示、隱藏,而是聊一聊如何使用元件。

   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

   需要注意一點:元件名字如果有多個單詞構成,應當寫成駝峰形式(個人推薦)。

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

   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>

相關文章