1. 程式人生 > 前端設計 >從零開始學習Vue(二)

從零開始學習Vue(二)

元件模組化

簡介

如果我們將一個頁面中所有的處理邏輯全部放在一起,處理起來就會變得非常複雜,而且不利於後續的管理以及擴充套件。但如果,我們講一個頁面拆分成一個個小的功能塊,每個功能塊完成屬於自己這部分獨立的功能,那麼之後整個頁面的管理和維護就變得非常容易了。

元件化是Vue.js中的重要思想,它提供了一種抽象,讓我們可以開發出一個個獨立可複用的小元件來構造我們的應用。任何的應用都會被抽象成一顆元件樹

元件化思想的應用:

  • 儘可能將頁面拆分成一個個小的、可複用的元件。
  • 程式碼更方便組織和管理,擴充套件性更強。

元件的使用

元件的使用分成三個步驟:

  • 呼叫Vue.extend()方法建立元件構造器
  • 呼叫Vue.component()方法註冊元件
  • 在Vue例項的作用範圍內使用元件
// HTML
<div id="app">
    <!--步驟三:Vue例項的作用範圍內使用元件-->
    <my-cpn></my-cpn>
</div>
<!--作用範圍外,無法渲染-->
<my-cpn></my-cpn>


<script>
    <!--步驟一:建立元件構造器-->
    <!--傳入template代表我們自定義元件的模板。-->
    <!--這種寫法在Vue2.x的文件中幾乎已經看不到了-->
    const myComponent = Vue.extend({
        template: `
            <div>
                <h2>元件標題</h2>
            </div>
        `
    });
    
    <!--步驟二:註冊元件,並且定義元件標籤的名稱-->
    <!--傳遞兩個引數:1、註冊元件的標籤名   2、元件構造器-->
    Vue.component('my-cpn'
,myComponent); const app = new Vue({ el: '#app' }) </script> 複製程式碼

全域性元件和區域性元件

當我們通過呼叫**Vue.component()**註冊元件時,元件的註冊時全域性的。這意味著該元件可以在任意Vue例項下使用。
如果我們註冊的元件時掛載在某個例項中,那麼就是一個區域性元件。

<div id="app">
    <my-cpn></my-cpn>   // 註冊在#app下的區域性元件,可以渲染
    <my-cpn1></my-cpn1> //全域性元件,可以渲染
</div>
<div id="app1"
> <my-cpn></my-cpn> // 無法渲染 <my-cpn1></my-cpn1> // 可以渲染 </div> <script> const myComponent = Vue.extend({ template: ` <div> <h2>元件標題</h2> </div> ` }); <!--全域性註冊--> Vue.component('my-cpn1',myComponent); const app = new Vue({ el: '#app',components: { 'my-cpn': myComponent } }) const app1 = new Vue({ el: '#app1' }) </script> 複製程式碼

父元件和子元件

元件和元件之間存在層級關係,其中一種非常重要的關係就是父子元件的關係。

<div id="app">
    <parent-cpn></parent-cpn>
    
    <!--錯誤用法,子元件標籤只能在父元件中被識別-->
    <child-cpn></child-cpn>
</div>


<script>
    const parentComponent = Vue.extend({
        template: `
            <div>
                <h2>父元件標題</h2>
                <!--當子元件註冊到父元件的components時,Vue會編譯好父元件的模組,將子元件標籤替換為子元件的模板內容-->
                <child-cpn></child-cpn>
            </div>
        `,components: {
            'child-cpn': childComponent
        }
    });
    const childComponent = Vue.extend({
        template: `
            <div>
                <h2>父元件標題</h2>
            </div>
        `
    });
    
    const app = new Vue({
        el: '#app',components: {
            'parent-cpn': parentComponent
        }
    })
</script>
複製程式碼

註冊語法糖

Vue為了簡化祖冊元件的過程,提供了註冊的語法糖,省去了呼叫VUe.extend()的步驟,而是直接使用一個物件來代替。

<!--語法糖註冊全域性元件和區域性元件-->
<!--全域性元件-->
Vue.component('myCpn',{
    template:`
        <div>
            <h2>父元件標題</h2>
        </div>
    `
})
<!--區域性元件-->
const app = new Vue({
    el: "#app",components: {
        'my-cpn': {
            template: `
            <div>
                <h2>父元件標題</h2>
            </div>
        `
        }
    }
})
複製程式碼

模板的分離寫法

通過語法糖簡化了Vue元件的註冊過程,但是也導致了template模組寫法較為麻煩. Vue提供了兩種方案來將其中的HTML分離出來,然後再掛載到對應的元件上,這樣解構會變得非常清晰.

  • 使用<script>標籤
  • 使用<template>標籤
<div id="app">
    <my-cpn></my-cpn>
</div>

<!--方案一: 使用<script>標籤-->
<script type="text/x-template" id="myCpn">
    <div>
        <h2>元件標題</h2>
    </div>
</script>

<!--方案二: 使用<template>標籤-->
<template id="myCpn">
    <div>
        <h2>元件標題</h2>
    </div>
</template>

<script>
    const app = new Vue({
        el: '#app',components: {
            'my-cpn': {
                template: '#myCpn'
            }
        }
    })
</script>
複製程式碼

元件中的資料訪問

元件時一個單獨功能模組的封裝,這個模組有屬於自己的HTML模板,也應該有屬於自己的資料data.元件不能直接訪問Vue例項中的data.

元件物件也有一個data屬性,只是這個data屬性必須時一個函式,而且這個函式返回一個物件,物件內部儲存著資料.

原因: 首先,如果不是一個函式,Vue直接會報錯;其次,原因是在於Vue讓每個元件物件都返回一個新的物件,因為如果是同一個物件的,元件在多次使用後會相互影響.

<div id="app">
    <my-cpn></my-cpn>
</div>
<template id="myCpn">
    <div>
        <h2>{{message}}</h2>
    </div>
</template>

<script>
    const app = new Vue({
        el: '#app',components: {
            'my-cpn': {
                template: '#myCpn',data() {
                    return {
                        message: 'hello world'
                    }
                }
            }
        }
    })
</script>
複製程式碼

父子元件的通訊

父子元件間的通訊:

  • 通過props向子元件傳遞陣列
  • 通過時間向父元件發訊息

父傳子——props基本用法

在元件中,使用選項props來宣告需要從父級接收到的資料.
props的值有兩種方式:

  • 字串陣列,陣列中的字串就是傳遞時的名稱
  • 物件,物件可以設定傳遞時的型別,也可以設定預設值等.
字串陣列用法
<div id="app">
    <my-cpn :message="pMassage"></my-cpn>
</div>
<template id="myCpn">
    <div>
        <h2>{{message}}</h2> // hello world
    </div>
</template>

<script>
    const app = new Vue({
        el: '#app',data: {
            pMassage: 'hello world'
        }
        components: {
            'my-cpn': {
                template: '#myCpn',props: ['message']
            }
        }
    })
</script>
複製程式碼
物件用法

型別驗證支援的型別:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
function Person (fName,lName) {
    this.firstName = fName;
    this.lastName = lName;
}
Vue.component('my-cpn',{
    props: {
        <!--基礎的型別檢查(null匹配任何型別)-->
        propA: Number,author: Person,<!--多個可能的型別-->
        propB: [Number,String]
        <!--必填的字串-->
        propC: {
            type: String,required: true
        },<!--帶有預設值的數字-->
        propD: {
            type: Number,defalut: 100
        },<!--帶有預設值的物件-->
        propE: {
            type: Object,default() {
                return {message: 'hello'}
            }
        },<!--自定義驗證函式-->
        propF: {
            validator(value) {
                return ['success','warning','danger'].indexof(value) !== -1;
            }
        }
    }
})
複製程式碼

子傳父——自定義事件

當子元件需要向父元件傳遞資料時,需要自定義事件,可以通過v-on監聽元件間的自定義事件。

自定義事件的流程:

  • 在子元件中,通過$emit()來觸發事件。
  • 在父元件中,通過v-on來監聽子元件事件。
<div id="app">
    <child-cpn @increment="changeTotal" @decrement="changeTotal"></child-cpn>
    <h2>{{total}}</h2>
</div>

<template id="childCpn">
    <div>
        <button @click = "increment">+1</button>
        <button @click = "decrement">-1</button>
    </div>
</template>

<script>
    const app = new Vue({
        el: '#app',data: {
            total: 0
        },methods: {
          changeTotal(value) {
              this.total = value;
          }  
        },components: {
            'child-cpn': {
                template: '#childCpn',data() {
                  counter: 0  
                },methods: {
                    increment() {
                        this.counter++;
                        this.$emit('increment',this.counter);
                    },decrement() {
                        this.counter--;
                        this.$emit('decrement',this.counter);
                    }
                }
            }
        }
    })
</script>
複製程式碼

父子元件的訪問

有時候我們需要父元件直接訪問子元件,子元件直接訪問父元件,或者子元件訪問根元件。
父元件訪問子元件:使用$children或$refs
子元件訪問父元件:使用$parent
子元件訪問根元件:使用$root

父元件訪問子元件

$children

this.$children是一個數組型別,它包含所有子元件物件.
缺陷: 通過$children訪問子元件時,訪問其中的子元件必須通過索引值,但是當子元件過多時,往往不能確定它的索引值,甚至可能發生變化.

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

<!--父元件template-->
<template id="parentCpn">
    <child-cpn1></child-cpn1>
    <child-cpn2></child-cpn2>
    <button @click="showChildCpn">顯示所有子元件資訊</button>
</template>

<!--子元件1 template-->
<template id="childCpn1">
    <h2>我是子元件1</h2>
</template>

<!--子元件2 template-->
<template id="childCpn2">
    <h2>我是子元件2</h2>
</template>

<script>
    Vue.component('parent-cpn',{
        template: '#parentCpn',methods: {
            showChildCpn() {
                console.log(this.$children); // [VueComponent,VueConponent]
            }
        },components: {
            'child-cpn1': '#childCpn1','child-cpn2': '#childCpn2'
        }
    })
    
    const app = new Vue({
        el: '#app'
    })
</script>
複製程式碼
$refs(推薦使用)

使用:

  • $refs和ref指令通常是一起使用的.
  • 通過ref給某一個子元件繫結一個特定的ID
  • 通過this.$ref.ID就可以訪問到該元件了
<child-cpn ref="child1"></child-cpn>
<child-cpn ref="child2"></child-cpn>
<button @click="showRefsCpn">通過refs訪問子元件</button>


showRefsCpn() {
    console.log(this.$refs.child1);
    console.log(this.$refs.child2);
}
複製程式碼

子元件訪問父元件($parent)

注意事項:

  • 儘管在Vue開發中,我們允許通過$parent來訪問父元件,但是在真是開發中儘量不要這麼做.
  • 子元件應該儘量避免直接訪問父元件的資料,因為這樣耦合度太高了.
  • 如果我們將子元件放在另外一個元件之內,很可能該父元件沒有對應的屬性,往往會引起問題.
  • 另外,更不好做的事通過$parent直接修改父元件的狀態,那麼父元件中的狀態將變得飄忽不定,不利於除錯和維護.
<div id="app">
    <parent-cpn></parent-cpn>
</div>

<!--父元件template-->
<template id="parentCpn">
    <child-cpn1></child-cpn>
</template>

<!--子元件1 template-->
<template id="childCpn">
    <button @click="showParent">顯示父元件資訊</button>
</template>

<script>
    Vue.component('parent-cpn',data() {
          return {}  
        },components: {
            'child-cpn': '#childCpn',methods: {
                showParent() {
                    console.log(this.$parent);  //訪問父元件
                    console.log(this.$root);    //訪問根元件
                }
            }
        }
    })
    
    const app = new Vue({
        el: '#app'
    })
</script>
複製程式碼

編譯作用域

父元件模板的所有東西都會在父級作用域內編譯;子元件模板的所有東西都會在子級作用域內編譯.

<div id="app">
    <!--渲染成功-->
    <parent-cpn v-show="isShow"></parent-cpn>
</div>

<template id="parentCpn">
    <h2>顯示元件內容</h2>
</template>


<script>
    Vue.component('parent-cpn',data() {
          return {
              isShow: false
          }  
        }
    })
    
    const app = new Vue({
        el: '#app',data: {
            isShow: true
        }
    })
複製程式碼

插槽slot

元件的插槽可以讓我們封裝的元件更加具有擴充套件性.
最好的封裝方式就是將共性抽取到元件中,將不同暴露為插槽,由使用者根據自己的需求決定插槽中插入什麼內容.

基本使用

<div id="app">
    <!--無替換元素,顯示插槽預設內容-->
    <my-cpn></my-cpn>
    
    <!--替換插槽的內容-->
    <my-cpn>
        <h2>我是替換內容</h2>
        <p>我也是替換內容</p>
    </my-cpn>
</div>

<template id="myCpn">
    <div>
        <slot>我是一個插槽中的預設內容</slot>
    </div>
</template>

<script>
    Vue.component('my-cpn',{
        template: '#myCpn'
    })
    
    let app = new Vue({
        el: '#app'
    })
</script>
複製程式碼

具名插槽slot

當子元件的功能複雜時,子元件的插槽可能並非一個,這時候我們就需要使用具名插槽(給slot元素一個name屬性)來對插槽進行區分,從而能在指定位置替換內容.

<div id="app">
    <!--無替換元素,顯示插槽預設內容-->
    <my-cpn></my-cpn>
    
    <!--替換無名插槽的內容-->
    <my-cpn>
        <h2>我是替換內容</h2>
        <p>我也是替換內容</p>
    </my-cpn>
    
    <!--替換指定插槽的內容-->
    <my-cpn>
        <h2 slot="left">替換左邊插槽</h2>
    </my-cpn>
    
    <my-cpn>
        <h2 slot="left">替換左邊插槽</h2>
        <h2 slot="mid">替換中間插槽</h2>
        <h2 slot="right">替換右邊插槽</h2>
    </my-cpn>
</div>

<template id="myCpn">
    <div>
        <slot name="left">左邊插槽</slot>
        <slot name="mid">中間插槽</slot>
        <slot name="right">右邊插槽</slot>
        <slot>無名插槽</slot>
    </div>
</template>

<script>
    Vue.component('my-cpn',{
        template: '#myCpn'
    })
    
    let app = new Vue({
        el: '#app'
    })
</script>
複製程式碼

作用域插槽

父元件替換插槽的標籤,但是內容由子元件來提供

<div id="app">
    <!--1. 列表形式展示-->
    <my-cpn>
        <!--獲取到slotProps屬性-->
        <template slot-scope="slotProps">
            <ul>
                <li v-for="item in slotProps.data">{{item}}</li>
            </ul>
        </template>
    </my-cpn>
    
    <!--2. 水平展示-->
    <my-cpn>
        <!--獲取到slotProps屬性-->
        <template slot-scope="slotProps">
            <span v-for="item in slotProps.data">{{item}} </span>
        </template>
    </my-cpn>
</div>

<template id="myCpn">
    <div>
        <slot :data="pLanguage"></slot>
    </div>
</template>

<script>
    Vue.component('my-cpn',{
        template: '#myCpn',data() {
            pLanguage: ['JS','HTML','CSS']
        }
    })
    
    let app = new Vue({
        el: '#app'
    })
</script>
複製程式碼