從零開始學習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>
複製程式碼