Vue快速上門(3)-元件與複用

安木夕發表於2022-12-13

image.png

VUE家族系列:

01、component元件

1.1、component基礎知識

元件是可以複用的Vue模組,是一個獨立的,有自己的檢視、樣式CSS、ViewMoel業務邏輯,結構的完整模組。當成自定義元素,可以在任意Vue中、模板、其他元件中使用。一個複雜的頁面、系統可以拆分為多個元件,獨立開發和維護,可複用、更清晰。

image.png

?註冊元件Vue.component( id, [definition] )註冊全域性元件,id為元件的名稱,也作為元素名,引數和Vue引數選項物件基本一致。

  • data 必須是函式:透過函式返回物件,避免不同元件例項共享data資料。
  • template 模板,字串模板,或者<template>選擇器,不能用真實Dom元素。
  • 必須有根元素:元件的模板必須有一個有效的根元素。

?props 引數:透過props定義元件引數(Array<string> | Object ),引數都會作為元件元素的attribute特性使用,透過vm.$props獲取元件引數。

?自定義事件:在子元件中,申明並觸發自定義事件 $emit( event-name, […args] ),透過vm.$listeners 獲取元件的所有監聽事件。(emit /iˈmɪt/ 發射,發出 )

  • 響應事件(父元件):v-on:event-name,這裡的處理函式建議只繫結函式名,便於接收引數。
  • 修飾符.native 繫結元件根元素的原生事件,v-on:change.native="func"
  • 繫結事件監聽器,在元件內特定元素上繫結所有監聽事件:v-on="$listeners"vm.$listeners可獲取元件上的所有監聽事件。

?<slot>插槽</slot>,在元件中設定一個插槽,來接收元件元素的標籤內的內容,透過vm.$slots 獲取元件的插槽。

<div id="app9">
    <uinfo v-for="(u,i) in users" v-bind:u="u" type="鑽石會員" v-on:remove="users.splice(i,1)"></uinfo>
</div>
<script>
    // 註冊一個全域性元件
    Vue.component("uinfo", {
        data: function () {
            return { count: 0 }
        },  //元件的data必須是函式返回物件
        props: ['u', 'type'],
        template: `<div class="uinfo">
                <span>{{u.name}}</span> ?<input v-model.number="u.age" type="number"> <i>{{type}}</i>
                <span>“{{u.summary}}”</span>
                <button v-on:click="$emit('remove')">刪除</button>
        </div>`  //透過模板字元的方式實現多行文字,IE是不支援的。
    });
    let app9 = new Vue({
        el: "#app9",
        data: {
            users: [
                { name: "張三", age: 14, summary: "垂死病中驚坐起,趕快下樓做核酸" },
                { name: "李四", age: 30, summary: "仰天大笑出門去,下樓排隊做核酸" }]
        }
    })
</script>

image.png

?注意元件命名小寫+連字元(kebab-case),包括元件名稱(作為自定元素標籤)、元件引數prop(作為自定義元素的 attribute),以及自定義事件名。

  • 小寫遵循W3C規範,連字元避免和HTML衝突(包括以後新的HTML標籤名)。
  • HTML 中的 attribute 名是大小寫不敏感的,瀏覽器會把所有大寫字元解釋為小寫字元。
    ⚠️ 如果使用字元模板,就不歸瀏覽器管了,就沒有命名的限制問題了!

1.2、註冊元件

  • 全域性註冊Vue.component( id, {選項}),全域性元件,任何地方都可以使用。
  • 區域性註冊components:{id:{選型}}選項註冊元件,只能在當前註冊的Vue環境內使用,不可繼承。
  • 擴充套件Vue類Vue.extend({選項}),建立一個Vue的擴充套件類,透過new()建立元件例項,也是一種元件複用方式。
<div id="app10">
    <div>
        <sexbox v-bind:data="uinfo" age="99" required>性別({{uinfo.sex}}):</sexbox>
        <user-box :data="uinfo"></user-box>
    </div>
</div>
<template id="userSexTemplate">
    <div>
        <slot></slot>
        <label v-for="(value,name) in dic">
            <!-- v-bind="$attrs" 用來繫結元件上設定的屬性:age="99" required -->
            <input type="radio" v-model="data.sex" name="sex" :value="name" v-bind="$attrs">
            {{value}}
        </label>
    </div>
</template>
<template id="userBoxTemplate">
    <div>姓名:<input type="text" v-model="data.name">
        <sexbox :data="data">性別:</sexbox>
    </div>
</template>

<script>
    // 性別選擇元件
    let comSex = {
        data: function () { return { dic: { male: '男', female: "女", other: '其他' } } },
        props: ['data'],
        template: '#userSexTemplate'
    }
    // 使用者資訊元件,引入了性別元件
    let UserVue = Vue.extend({
        props: ['data'],
        components: { 'sexbox': comSex },
        template: `#userBoxTemplate`,
    })
    //vue 應用
    let app10 = new Vue({
        el: "#app10",
        data: { uinfo: { name: '核算', sex: 'male' } },
        components: { 'user-box': UserVue, 'sexbox': comSex }
    })
    // app10 = new UserVue({el:'#app10'})
</script>

image.png

1.3、is 動態元件

透過is特性設定(或v-bind:is繫結)元件名稱,動態的申明一個元件。

元件使用申明方式:

  • <user-info></user-info>:常規自定義元素方式申明。
  • <component is="user-info"></component>:component元件元素申明,透過is設定元件名稱,:is就可以動態繫結元件了。
  • <tr is="user-info"></tr>:其他HTML元素+is申明元件,該元素會被元件內容替換掉。這主要是為了解決有些HTML元素中只能包含特定的子元素,如<ul><table>
<keep-alive>
    <component is="user-info" v-bind:u="users[0]"></component>
</keep-alive>
<table>
    <tr is="user-info" v-bind:u="users[0]"></tr>
</table>

?keep-alive 快取元件:用<keep-alive>包裹動態元件,配合元件特性:is使用,用來快取失活的動態元件,避免元件失活後狀態丟失。

1.4、Props引數

透過props定義引數(Array<string> | Object ),引數都會作為元件元素的attribute特性使用,透過vm.$props獲取元件引數。

?Prop物件驗證:除了使用陣列設定多個引數,還可以用一個物件來申明多個引數及引數規則,用於引數合法性驗證。⚠️注意驗證的執行是在元件例項建立之前進行的,此時datacomputed都還不可用

  • 每個引數可以指定多個驗證型別。
  • 每個引數可以定義型別type、必填require、預設值default,以及驗證器函式validator
<div id="app11">
    <sex-box v-bind:data="uinfo" type="vip" age="99" required class="form-item">性別:</sex-box>
</div>
<script>
    function User(name = "", sex = "") {
        this.name = name; this.sex = sex;
    }
    // 一個性別選擇元件
    let sexBox = {
        data: function () {
            return {
                dic: { male: '男', female: "女", other: '其他' },
                utype: this.type,  //使用prop引數為初始值
            }
        },
        inheritAttrs: false,  //元件根元素不繼承Attribute(不含style、class)
        props: {
            data: [Object, User], //User為自定義構造器
            type: String,
            age: {
                type: [String, Number],
                required: true,
                default: 18,
                validator: function (value) { return value > 0 && value < 100 }  /* 自定義驗證器 */
            }
        },
        // props: ['data', 'type', 'age'],
        template: `<div><slot></slot>
                    <label v-for="(value,name) in dic">
                            <input type="radio" v-model="data.sex" name="sex" :value="name" v-bind="$attrs">
                    {{value}}</label> //{{type}}</div>`,
    }
    let app11 = new Vue({
        el: "#app11",
        data: {uinfo: new User("張三", 'male')},
        components: {'sex-box': sexBox,}
    })
</script>

image.png

?引數繫結:引數作為自定義元件元素的attribute使用,推薦v-bind:prop繫結賦值,除是非字串值,繫結是支援表示式的。

  • 如果要傳入一個物件的所有 property,直接v-bind="obj"即可。

?引數的單向傳遞:引數的值是單向的向下傳遞的,子元件不可更改。

  • 父元件的資料變更會觸發子元件所有引數prop的重新整理。
  • 如果傳入的是一個引用型別(陣列、物件),這個是共享的,會影響父元件狀態。

?非Props引數的Attribute:對於非Prop引數的Attribute,預設會都應用到元件根元素上,如上面示例中的age="99" required class="form-item"

  • 替換:除了classstyle會合並,其他如果衝突會替換掉元件內部的Attribute
  • inheritAttrs 不繼承:可在元件選項裡設定inheritAttrs:false取消根元素的Attribute繼承。
  • 主動繼承:元件內部其他元素可以用 v-bind="$attrs",來主動繼承非Prop的Attribute特性(不含class、style)。

1.5、#slot插槽

插槽可以用來插入任何內容,包括文字、HTML、其他元件。結合具名插槽(name)、prop繫結,可以實現更為開放、靈活的元件。

  • 申明插槽:在元件內部透過<slot></slot>申明一個插槽,整個<slot></slot>會被替換為元件元素標籤的內容(InnerHTML)。
  • 後備內容/預設值<slot>預設顯示內容</slot>,在沒有提供內容時使用後備內容。
<div id="app13">
    <com-search-box>
        <p>{{search.title[0]}}</p>
        <template v-slot:default>  <!--效果同上-->
            <p>{{search.title[0]}}</p>
        </template>
    </com-search-box>
</div>
<script>
    let comSearchBox = {
        template: `<div><slot>搜尋</slot> <input><button><b>?</b><span>搜尋</span></button></div>`,
    }
    let app13 = new Vue({
        el: "#app13",
        data: { search: { title: ['百度搜尋', '學術搜尋', '圖片搜尋'] } },
        components: { 'com-search-box': comSearchBox }
    })
</script>

?具名插槽,有名字的VIP插槽,name特性取名,用於需要多個插槽的場景。沒命名的插槽<slot>預設名稱為"default"--預設插槽。

  • 必須透過一個模板<template>來使用,用slot引數v-slot:name = #name 指定插槽的名字。無<template>、不指定名字的內容用於預設插槽。
  • 縮寫# = v-slot:,只能用於帶引數的v-slotv-slot:default
  • 插槽名可以用動態引數,[data-name]<template v-slot:['default']>

?作用域插槽:插槽內容訪問子元件內部資料

父級不能訪問子元件內部的資料,為了可以讓插槽內容可以訪問到子元件內部的資料,於是有了作用域插槽,主要就是2個步驟。

❶ 元件內部把資料繫結在插槽<slot>的特性Attribute上 <slot v-bind:data="btnKey">,稱為插槽的Prop,在父級作用域可以訪問。

❷ 在元件<template>上引用插槽prop :<template v-slot="soldData">,可取一個新名字。當然這裡也可以用當前作用域的繫結資料。

<div id="app13">
    <com-search-box>
        <template #header v-slot:header> <p>{{search.title[1]}}</p> </template>
        <template v-slot="soldData">
            <b>?</b><span>{{soldData.data[1]}}</span>
        </template>
    </com-search-box>
</div>
<script>
    let comSearchBox = {
        data: function () { return { 'btnKey': ['搜尋', 'search'] } },
        template: `<div>
                        <slot name="header"></slot> <input>
                        <button>  <slot v-bind:data="btnKey">{{btnKey[0]}}</slot> </button>
                    </div>`,
    }
    let app13 = new Vue({
        el: "#app13",
        data: { search: { title: ['百度搜尋', '學術搜尋', '圖片搜尋'] } },
        components: { 'com-search-box': comSearchBox }
    })
</script>

image.png

?todo-list(代辦列表)的示例:馬上掘金 | codepen


1.6、元件樹關係

屬性 描述
$refs 透過過 refattribute 註冊的所有 DOM 元素和元件例項的一個引用物件。
當 ref 和 v-for 一起使用的時候,其引用為一個陣列
$parent 父例項,根例項的 $parent 為null
$root 組建樹的根例項,根例項的 $root 為自己
$children 子元件例項陣列Array<Vue instance>

image

當我們構建一顆樹形結構(如檔案目錄樹)的元件時,會遞迴迴圈引用,造成元件的迴圈依賴。

  • 全域性註冊元件。
  • beforeCreate手動注入子元件。
  • 設定webpack非同步import引入元件。

1.7、總結:元件通訊

image

透過元件關係樹也是可以的,不過不推薦,耦合性太高。


02、可複用性 & 組合

2.1、[mixins]混入(CtrlV)元件程式碼

混入(mixins)可以靈活複用元件中的程式碼,可惜只能用於組建內,也僅支援Vue的選項。步驟:

  1. 定義混入物件:let cmixin={}
  2. 元件中使用:mixins:[cmixin],混入物件中的內容。

?程式碼混入(合併)規則:

  • 選項合併:如果混入時存在程式碼衝突,則會先遞迴合併,然後本地優先的策略。
  • 混入的鉤子,合併為一個陣列,依次呼叫。
  • 全域性混入Vue.mixin( mixin ),應用於後面所有的Vue物件。
  • 混入優先順序:原生程式碼優先 > 區域性混入 全域性混入。
  • 自定義混入合併策略:Vue.config.optionMergeStrategies
let cmixin = {
    data: { message: "hello world!" },
    methods: { say: function () { console.log(this.message) } }
}
let app1 = new Vue({
    el: "#app1",
    data:{name:'sam'},
    mixins: [cmixin],
    //...cmixin,  //不支援合併,會覆蓋已有屬性值
    created: function () { this.say() }
})

?用ES6的展開運算子...,也能達到類似的複用目的,不過不支援合併策略。

2.2、v-自定義指令

指令是對Dom元素的擴充套件,具有一定的行為特徵(鉤子函式)。註冊的自定義指令,需要用指令的鉤子函式來觸發指令行為,一個指令支援多個鉤子函式,不同鉤子函式(觸發點)可設定不同的行為。如果不指定鉤子函式,就是全都要,都會觸發呼叫。

?註冊方式:全域性、區域性

  • 全域性註冊Vue.directive( id, [definition] ),注意順序,先定義後使用。
  • 區域性註冊-選項directives:{ id: { } }

?指令命名:參考HTML命名規範,小寫+連字元,正式指令名稱會加上v-

?指令函式引數(el, binding, vnode, oldVnode)

  • el:繫結的Dom元素。
  • binding:指令物件,包含指令名稱name、引數arg、修飾符modifiers、屬性值value、上一次的屬性值oldValue、指令表示式 expression。這裡的值可用來做變化判斷,以最佳化指令效能。
  • vnode:虛擬Dom元素
  • oldVnode:上一個虛擬Dom元素

?使用<input v-id:arg.ky='value'>

構子函式 描述
bind 只呼叫一次,指令第一次繫結到元素時呼叫。
inserted 被繫結元素插入父節點時呼叫
update 所在元件的 VNode 更新時呼叫
componentUpdated 所在元件的 VNode 更新後呼叫
unbind 只呼叫一次,指令與元素解綁時呼叫

?自定義指令-驗證器表單輸入:值為需要驗證的資料,其他驗證引數check-reg、check-error用自定屬性申明,指令用在表單元素後面的一個用於提示錯誤的元素上。

<div id="app1">
    <p>
        <label>使用者名稱:<input type="text" v-model="userName" maxlength="40" size="40" placeholder="請輸入使用者名稱"></label>
        <span v-check.required="userName" check-reg="^\w{4,6}$" check-error="使用者名稱必須是4-6位的字母數字"></span>
    </p>
    <p>
        <label>使用者名稱:<input type="text" v-model="email" maxlength="40" size="40" placeholder="請輸入郵箱"></label>
        <span v-check="email" check-reg="^\w+@\w+\.\w+$" check-error="郵箱格式不合法"></span>
    </p>
</div>
<script>
    // 驗證器(必填修飾符),正則,錯誤資訊,check,check-reg,check-error
    let mixinDirectivesValidator = {
        directives: {
            check: {    //check指令:v-check
                //繫結指令時觸發
                bind(el, bind) {
                    //先讀取check的配置屬性資訊,暫存備用
                    el.text = el.getAttribute('check-error');
                    el.reg = new RegExp(el.getAttribute('check-reg'));
                    el.style.color = 'red';
                    el.style.fontSize = '0.8em';
                    if (bind.modifiers.required) el.innerText = ' * 必填';
                },
                //更新檢視時觸發
                update: function (el, bind) {
                    if (bind.value === bind.oldValue) return;
                    el.innerText = '';
                    if (bind.modifiers.required) el.innerText = ' * 必填';
                    if (bind.value && !el.reg.test(bind.value)) el.innerText = el.text;
                }
            }
        }
    }
    let app1 = new Vue({
        el: "#app1",
        data: { userName: '', email: '' },
        mixins: [mixinDirectivesValidator]
    })
</script>

1.gif

2.3、| 插值過濾器

過濾器就是一個函式,在{{文字插值}}v-bind="表示式"繫結表示式後面使用,對繫結的值進行過濾(再加工)處理。支援鏈式的過濾使用,支援引數,常用於格式化顯示。在V2、V3中,過濾器被逐漸弱化,更推薦用表示式或計算屬性。

  • 全域性過濾器Vue.filter( id, func(value,...arg ),第一個引數為管道符號前面的繫結值。
  • 區域性過濾器-filters{id:func(value,...arg)}選項,(❗多了個s
  • 使用:用管道符號連線,{{文字插值 | filter1 | filter2(arg)}}
<style>
    #app2 *{
        font-family:'Courier New', Courier, monospace;
    }
</style>
<div id="app2">
    <ul>
        <li v-for="a in arr">{{a | fixedLength(3,'&nbsp;')}}: {{a|fixedLength(4)| money|money|money}}</li>
    </ul>
</div>
<script>
    //全域性過濾器,數字固定長度
    Vue.filter("fixedLength", function (value, length,char='0') {
        return (Array(length).join(char) + value).slice(-length);
    })
    let app2 = new Vue({
        el: "#app2",
        data: { arr: [1, 2, 3, 44, 55, '5K'] },
        //具備過濾器定義
        filters: {
            money: function (v) { return '¥' + v; }
        }
    })
</script>

image.png

2.4、Vue外掛開發

外掛就是一個函式或者包含install()方法的物件,目的是實現封裝複用。透過Vue.use() 來安裝,給Vue提供全域性的擴充套件能力,如新增全域性的指令、方法、元件,混入選項等,很多Vue的UI元件都是這麼處理的。

  • install(Vue,...args):外掛應該提供的安裝函式,第一個引數就是Vue構造器,就可以呼叫Vue的靜態方法做一些處理。後面的引數就是外掛自己用的引數了。
  • Vue.use(外掛, ...args)註冊外掛,第一個引數為外掛,後面就是外掛需要的引數選項了。
// 封裝外掛
export let SuperPlugin = {
    install(Vue, ...args) {
        console.log('安裝外掛SuperPlugin', args);
        //安裝一個UI元件
        Vue.component('super-btn', { template: '<button>Super<slot></slot></button>' });
        //新增全域性靜態方法
        Vue.$get = function (url) { /*ajax get*/ };
        Vue.$post = function (url, data) { /*ajax post*/ };
        //新增例項方法
        Vue.prototype.$myMethod = function () { };
        //混入全域性的選項
        Vue.mixin({ created: function () {console.log('建立元件') } });
    }
}
// 註冊外掛
import { SuperPlugin } from './super-plugin.js'
Vue.use(SuperPlugin, 1, 2, 3);

©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

相關文章