1. 程式人生 > 實用技巧 >VUE學習筆記—元件

VUE學習筆記—元件

在VUE中,元件是一個很重要的設計思想。為什麼會有元件,為什麼這麼設計,明白了這些才能把元件用好。

1、元件理解

一個應用的功能往往是比較複雜的,需要多個功能點相互配合。如果這些功能都設計在一個VUE物件中,可以想象,這個物件是極其龐大的。為了便於管理,我們可以將功能拆分為不同的小塊,最後再組裝到一起。每一個小塊我們可以理解為一個元件,功能複雜的元件可以繼續分拆為更小的元件。
這樣做的好處是我們只需要關注如何合理拆分以及拆分後的每個小功能即可。更重要的,我們可以在不同的地方重用這些元件,使我們的程式碼更方便組織管理,並且擴充套件性也更強。

2、基本使用過程

元件是如何被創造,然後使用的呢?元件的使用可以分為3個步驟:定義、註冊、使用。

a、元件的定義是通過Vue.extend()方法來實現的:

比如我們定義一個簡單的元件my-cpn:

// 1、定義元件
const  cpnC = Vue.extend({
  template: `
    <div>
      <h2>我是主題</h2>
      <p>我是內容,哈哈哈哈</p>
    </div>
    `
});
// 2、註冊元件
Vue.compent('my-cpn', cpnC);

// 3、使用元件
// 在需要的地方,<my-cpn></my-cpn> 即可引用元件
b、元件的作用域

以上的做法,實際上一般是不會這麼用的,html程式碼解除安裝template中,這使用起來太不方便了,一般我們會使用語法糖的寫法:
上述步驟中,註冊元件Vue.compent()會將元件註冊到vue全域性物件上去,這範圍太大了,實際我們可能只需要註冊到自己的第一層父元件即可。vue物件有components屬性,註冊到這裡,即相當於只註冊了這個vue例項:

const app = new Vue({
    el: '#app',
    data: {},
    components: {
        my-cpn: cpnC;  //key為標籤名,value為元件構造器
    }
    
})

註冊到某個具體的vue例項的元件,即為區域性元件。

c、父子元件

多層元件巢狀,會有什麼現象呢?‘孫子’元件會不會在‘爺爺’元件中仍然可用?

// 1、第一個元件
const  cpn1 = Vue.extend({
  template: `
    <div>
      <h2>我是主題1</h2>
      <p>我是內容,哈哈哈哈11111</p>
    </div>
    `
});

// 2、第二個元件
const  cpn2 = Vue.extend({
  template: `
    <div>
      <h2>我是主題2</h2>
      <p>我是內容,哈哈哈哈22222</p>
    </div>
    `,
    components: {
        myCpn1: cpn1;
    }
});

// 頂層頁面
const app = new Vue({
    el: '#app',
    data: {
        data: 'hello world'
    },
    components: {
        myCpn2: cpn2
    }
});

定義如上,那麼在app所在的頁面中,能否直接使用來呼叫第一個元件呢?答案是不可以。
因為實際上在cpn2中,經過編譯後,會將cpn1的內容整合到template中,而不是以一個單獨的元件的形式出現。

d、語法糖寫法
//全域性
Vue.component('cpn1',{
    template: `
    <div>
      <h2>我是主題1</h2>
      <p>我是內容,哈哈哈哈11111</p>
    </div>`
});

//區域性
const app = new Vue({
    el: '#app',
    data: {
        data: 'hello world'
    },
    components: {
        myCpn2: {
          template: `
            <div>
              <h2>我是主題2</h2>
              <p>我是內容,哈哈哈哈22222</p>
            </div>
            `,
            components: {
                myCpn1: cpn1;
            }
        }
    }
});

額,,,貌似只是方便了一丟丟?能不能把html扔出去,這太難受了。。。當然可以的。

e、元件模板的抽離寫法

Vue提供了兩種方案來定義html模組內容:
1、使用<script>標籤(注意type型別);
2、使用<template>標籤;

<!--script寫法-->
<script type="text/x-template" id="cpn">
    <div>
        <h2>我是標題</h2>
        <p>我是內容,哈哈哈</p>
    </div>
</script>

<!--template寫法-->
<template id="cpn2">
    <div>
        <h2>我是標題</h2>
        <p>我是內容,哈哈哈</p>
    </div>
</template>
Vue.compent('cpn',{
   template: '#cpn' 
});

注意type="text/x-template",且id跟下方的#cpn一致;

f、data為啥必須是函式

以上內容中,元件中的資料是寫死的。現實中,這顯然不能滿足我們的要求。
如果資料是動態的,應該怎麼寫呢?

<template id="cpn">
    <span>{{message}}</span>
</template>
Vue.compent('cpn',{
   template: '#cpn',
   data(){
       return {
           message: "I am compent"
       }
   }
});

整個結構跟vue物件是非常類似的,但data從js物件變成了函式。為什麼這麼設計呢?
這麼寫是為了讓元件的每個例項的資料不相互干擾。
注意:
1、子元件不能直接使用父元件資料;
2、元件為了保證例項資料的隔離,應避免return 共享變數;

g、父子元件通訊

子元件是不能直接訪問父元件的資料的。但開發中,往往一些資料需要從上層傳遞到下層,這就涉及到元件間的資料通訊問題。
父傳子: 通過props
子傳父: 通過事件發射$emit

<div id="app">
    <cpn :cmovies="movies" :cmessage="message"></cpn>
</div>

<template id="cpn">
    <div></div>
</template>
const cpn = {
    template: '#cpn',
    props: ['cmovies', 'cmessage'],
    data(){},
    methods:{}
}

const app = new Vue({
    el: '#app',
    data: {
        message: '雷吼啊',
        movies: ['火影',海賊王']
    },
    components: {
        cpn
    }
})

子元件要用v-bind將兩個變數進行繫結。props有兩種寫法:
1、字串陣列,陣列中的字元就是傳遞的變數名。
2、物件,物件可以設定傳遞時的型別,也可以設定預設值等。
示例:

Vue.componment('my-component',{
    props: {
        propA: Number,
        propB: [String, Number], //型別為string或者number
        propC: {
            type: String,
            required: true  //該欄位必須,缺失將報錯
        },
        propD: {
            type: Number,
            default: 100
        },
        propE: {
            type: Object,
            default(){   //物件跟陣列的預設值,需要用函式實現--變數隔離防止相互汙染
                return {message: 'hello'}
            }
        },
        propF: {
            validator(value){  //資料校驗
                return ['success','warning','danger'].indexOf(value) != -1;
            }
        }
    }

})

當然,除了js預設的資料型別,vue元件也支援自定義型別,比如我們可以自定義一個類Persion,然後指定type為Person也是可以的。

注意:
vue標籤跟html標籤一樣,不支援駝峰命名。而props中的變數名是要在元件使用時在標籤書寫的,這會導致變數名稱不匹配的問題。vue的解決辦法是標籤中的駝峰用短橫線代替,比如myChildName,在vue標籤中應寫作my-child-name。在其它地方,比如{{}}中則不受此限制,仍然可以使用駝峰。

<div id="app">
    <cpn @itemclick="cpnclick"></cpn>  <!--響應自定義itemclick事件-->
</div>

<template id="cpn">
    <div>
        <button v-for="item in categories" @click="btnClick(item)">
            {{item.name}}
        </button>
    </div>
</template>
const cpn = {
    template: '#cpn',
    data(){
        return {
            categories:[
                {id:'aaa', name:'熱門推薦'},
                {id:'bbb', name:'手機數碼'},
                {id:'ccc', name:'家用家電'},
                {id:'ddd', name:'電腦辦公'},
                {id:'eee', name:'休閒戶外'},
            ]
        }
    },
    methods:{
        btnClick(item){
            thiss.$emit("itemclick", item); //發射事件(把自定義事件傳出去)
        }
    }
}

const app = new Vue({
    el: '#app',
    componments:{cpn},
    methods:{
        cpnclick(item){
            console.log(item);
        }
    }
});

自定義事件,父元件中預設第一個引數是子元件傳遞的資料,而不是event。瀏覽器自帶的事件,預設第一個引數是event。
注意,自定義事件一樣有駝峰命名不支援的問題。但腳手架書寫的vue是可以的,因為腳手架會重新編譯我們寫的程式碼。

父子元件的資料雙向繫結,怎麼做?
直接在子元件繫結父元件傳過來的資料,控制檯會報錯,而且只能在子元件資料繫結,無法繫結父元件。
正確做法是在data中定義新變數,初始化為父元件傳過來的值,然後定義事件,將變化傳遞給父元件,再在父元件進行相應的操作。

h、父子元件物件的直接訪問

父訪問子:$children或者$refs,注意$children是陣列,$refs是物件,需要ref屬性配合;
子訪問父:$parent

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

<template id="cpn">
    <span>子元件</span>
</template>
const app = new Vue({
    el: '#app',
    data: {
        message: '你好'
    },
    methods: {
        myclick(){
            //children方式
            console.log(this.$children[0].name);
            //refs方式
            console.log(this.$refs.aaa.name);
        }
    },
    components: {
        cpn: {
            template: '#cpn',
            data(){
                return {
                    name: "子元件的name"
                }
            }
        }
    }
});

$refs可以跟標籤配合,精準拿到指定的子元件物件,相對來說,比$children好用,因為很多時候元件的下標是不固定的。父元件可以直接$parent呼叫,是一個物件,根元件可以通$root呼叫。
$parent相對來說用的很少(其實這幾個都不常用),因為vue拆分元件的目的就是程式碼的可複用性,而一旦有了$parent這樣的程式碼,實際是將當前元件跟父元件實現了強耦合,這是非常不推薦的

3、slot 插槽

元件中的有些內容可能並不是固定的,有些內容需要根據外部父元件的不同需求進行調整,這局需要我們在子元件中預留位置,這個預留的位置就是slot(插槽)。
一個元件可以擁有多個slot。
父元件給slot填充的內容中的資料可能來自slot,這就會有資料傳輸的問題,這就是作用域插槽的功能了。

<div id = "app">
    <cpn1>
        <span slot='aaa'>1111</span> <!--注意slot跟name值一致-->
        <span slot='bbb'>222</span>
    </cpn1>

    <cpn2>
        <template  slot-scope="slot"> 
            <p v-for="item in slot.mymovies">{{item}} </p> <!--注意mymovies跟:mymovies一致-->
        </template>
    </cpn2>
</div>

<!--元件1-->
<template id = "cpn1">
    <div>
        <slot name = "aaa"></slot>   
        <slot name = "bbb"></slot>
    </div>
</template>

<!--元件2-->
<template id = "cpn2">
    <div>
        <slot :mymovies="movies" >
            <ul>
                <li v-for="item in movies">{{item}} </li>
            </ul>
        </slot>
    </div>
</template>
const cpn1 = Vue.extend({
    template: '#cpn1',
});

const cpn2 = Vue.extend({
    template: '#cpn2',
    data(){
        return {movies: ['魔戒再現','雙塔奇兵','王者歸來']}
    }
});

const app = new Vue({
    el: '#app',
    data: {msg:"message"},
    components: {
        cpn1, cpn2
    }
    
});

元件部分,over