推薦我的vue教程:VUE系列教程目錄
上篇講解了vue-router路由入門,現在講講關于vue組件的內容。
如果你們有使用過element組件的話,他就是以vue組件的形式進行封裝的,在講解組件之前我們需要知道vue是數據驅動的,它的一切依賴于數據,我們應該根據數據的不同來進行相關的處理,在這一前提下才能形成vue框架的思考模式。
在了解這一模式的前提下我們來看看vue組件是個什么東西。
什么是VUE組件?
在github上,各位請使用git拉一下項目:vuetemplate。不會使用git拉文件的請去GitHub上下載壓縮包。
在/src/page/components
下是有關組件的代碼
我們打開vue官網的組件API,可以簡單瀏覽,對于新手來說這個API的閱讀有時很晦澀,或者跟實際應用有些許差別。于是我的講解是建立在對這個API的補充說明與簡單化的,所以你們最好還是看看這個。
VUE組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,VUE組件是自定義元素, Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴展。
注冊組件
在注冊組件時我們有兩種方式,第一種注冊全局組件,第二種是局部使用組件,對于那些應用多的組件來說全局無疑是最好的選擇,對于變數太大,應用不多且在統一目錄下的局部使用是我們想要的。
全局注冊:
要注冊一個全局組件,你可以使用 Vue.component(tagName, options)。
// 模板
Vue.component('my-component', {
// 選項
})
一個模板并不能說明什么,實例才能讓人看的更明白:
// html
<my-component></my-component>
// 注冊
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
組件的簡單使用就是這個樣子,即<my-component></my-component>
最后變成了<div>A custom component!</div>
。可是,實際操作中我們并不會這么弱智的使用,組件的復雜度遠遠不是這個樣子的。
局部注冊
要注冊一個局部組件,你可以使用 Vue的components屬性。
<script>
export default {
components: {},
data () {
return {
}
}
}
</script>
例子才是真理:
// html
<my-component></my-component>
<script>
var vuecomponent = {
template: '<div>A custom component!</div>'
}
export default {
components: {
'my-component': vuecomponent
},
data () {
return {
}
}
}
</script>
DOM渲染的局限
在html中我們知道有些標簽的孩子是固定的比如<ul> ,<ol>,<table> ,<select>
限制了能被它包裹的元素,例如ul里面只能包裹li。同時,一些像 <option> 這樣的元素只能出現在某些其它元素內部。
在自定義組件中使用這些受限制的元素時會導致一些問題,例如:
<table>
<my-row>...</my-row>
</table>
自定義組件 <my-row> 被認為是無效的內容,因此在渲染的時候會導致錯誤。那我們怎么解決呢?變通的方案是使用特殊的 is 屬性:
<table>
<tr is="my-row"></tr>
</table>
data 必須是函數
組件中必須是函數,通過Vue構造器傳入的各種選項大多數都可以在組件里用。 data 是一個例外,它必須是函數。
// html
<simple-counter></simple-counter>
<script>
export default {
components: {
'simple-counter': {
template: '<el-button size="small" v-on:click="counter += 1">{{ counter }}</el-button>',
// 技術上 data 的確是一個函數了,因此 Vue 不會警告,
// 但是我們返回給每個組件的實例的卻引用了同一個data對象
data: function () {
return {
counter: 0
}
}
}
},
data () {
return {
}
}
}
</script>
這里有很多人看蒙了,問起初不是這樣寫嗎???
data: {
counter: 0
}
這里別問,我起初也沒看懂,你可以這樣想:把所有關于data的數據引入變為data () {}
就可以了。
為何我會這樣說?我曾經在上一篇文章里說:vue路由的本質是根據url的不同來進行組件的各種切換罷了。而組件的data必須是數據,所以你看我寫的代碼里關于.vue
文件的都使用的是這種結構:
<template>
<div>
</div>
</template>
<script>
export default {
data () {
return {
}
}
}
</script>
如果不明白你可以看上面的<simple-counter>
組件的例子,其最終的本質變成了:
<template>
<div>
<el-button size="small" v-on:click="counter += 1">{{ counter }}</el-button>
</div>
</template>
<script>
export default {
data () {
return {
counter: 0
}
}
}
</script>
其實你只要使用vue-router路由你的.vue
文件的結構只能變成這樣:
<template>
<div>
</div>
</template>
<script>
export default {
data () {
return {
// 這里寫基礎數據
}
}
}
</script>
構成組件
組件意味著協同工作,引用組件的地方叫父組件,如<simple-counter></simple-counter>
,組件的內容則被成為子組件。
通常父子組件會是這樣的關系:組件 A 在它的模版中使用了組件 B 。它們之間必然需要相互通信:父組件要給子組件傳遞數據,子組件需要將它內部發生的事情告知給父組件。然而,在一個良好定義的接口中盡可能將父子組件解耦是很重要的。這保證了每個組件可以在相對隔離的環境中書寫和理解,也大幅提高了組件的可維護性和可重用性。
在 Vue.js 中,父子組件的關系可以總結為 props down, events up 。父組件通過 props 向下傳遞數據給子組件,子組件通過 events 給父組件發送消息。看看它們是怎么工作的?如圖:
父子組件的交互的原理這個具體呢?
父子組件的交互
props的單向流
組件實例的作用域是孤立的。這意味著不能(也不應該)在子組件的模板內直接引用父組件的數據。要讓子組件使用父組件的數據,我們需要通過子組件的props選項。
我們在開發中,一個單文件組件的.vue
文件的基本構成是這樣的:
<template>
<div>
</div>
</template>
<script>
export default {
// el: '',
props: {}, // 父到子傳參
components: {}, // 組件接收
mixins: [], // 混合
data () { // 基礎數據
return {
//
}
},
created () {}, // 創建周期
watch: {}, // 狀態過渡
methods: {}, // 方法存放的地方
computed: {}, // 計算屬性存放的地方
filters: {}, // 過濾
directives: {} // 指令
}
</script>
可是常用的很少,父組件給子組件傳值使用的就是props選項
。
props選項可以接受兩種模式的參數:
第一種固定的屬性:如這樣
message="hello!
傳一個普通的字符;
第二種動態屬性:如這樣v-bind:myMessage="this.message"
傳一個變量,其可以簡化為:myMessage="this.message"
例子如下:(父子組件在同一目錄下,子組件-child.vue)
// 父組件
// HTML
<child message="HELLO!" :my-message="this.message"></child>
// script
<script>
import child from './child.vue'
export default {
components: {
child: child
}
},
data () {
return {
message: '你猜'
}
}
}
</script>
// 子組件
<template>
<div>
<div>{{message}}</div>
<div v-text="myMessage"></div>
</div>
</template>
<script>
export default {
props: {
message: null,
myMessage: null
}, // 父到子傳參
data () { // 基礎數據
return {
//
}
},
created () {}, // 創建周期
watch: {}, // 狀態過渡
methods: {} // 方法存放的地方
}
</script>
prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態。
另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應該在子組件內部改變 prop 。如果你這么做了,Vue 會在控制臺給出警告。
但是有時我們就是需要修改,怎么辦?(老子需求)
一般情況下我們修改通常是這兩種原因:
- prop 作為初始值傳入后,子組件想把它當作局部數據來用;
- prop 作為初始值傳入,由子組件處理成其它數據輸出。
對于第一種情況我們可以李代桃僵,即用另外一個變量替代它,把它的值賦給那個變量:
data () { // 基礎數據
return {
//
counter: this.message
}
}
第二種情況可以定義一個計算屬性,處理 prop 的值并返回:
computed: {
messagetoLowerCase: function () {
return this.message.trim().toLowerCase()
}
}
注意:使用字面量語法傳遞數值時,必須使用動態props,即如這樣`v-bind:number="1"`
props驗證
我們可以為組件的 props 指定驗證規格。如果傳入的數據不符合規格,Vue 會發出警告。當組件給其他人使用時,這很有用。
要指定驗證規格,需要用對象的形式,而不能用字符串數組:(修改上面的例子)
// html
<child message="HELLO!" :my-message="this.message" :number="11"></child>
// 子組件props
props: {
message: String,
myMessage: {
type: String,
required: true
},
number: {
validator: function (value) {
return value > 10
}
}
}
驗證規格模板:
props: {
// 基礎類型檢測 (`null` 意思是任何類型都可以)
propA: Number,
// 多種類型
propB: [String, Number],
// 必傳且是字符串
propC: {
type: String,
required: true
},
// 數字,有默認值,如果你沒有傳則以默認為準
propD: {
type: Number,
default: 100
},
// 數組或對象的默認值應當由一個工廠函數返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
return value > 10
}
}
}
自定義事件向父組件傳值
事件$on與$emit
我們知道,父組件是使用 props 傳遞數據給子組件,但如果子組件要把數據傳遞回去,應該怎樣做?那就是自定義事件!
每個 Vue 實例都實現了事件接口(Events interface),即:
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件
注意:Vue的事件系統分離自瀏覽器的EventTarget API。盡管它們的運行類似,但是$on 和 $emit 不是addEventListener 和 dispatchEvent 的別名。
你們一定很奇怪怎么用事件監聽來向父元素傳遞?
其實原理很簡單就是我們在父組件上通過v-on
監聽子組件的事件,而子組件通過$emit(eventName) 觸發事件。例子如下:
// 父組件
<template>
<div>
<child v-on:onchild="inparent"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: {
child: child
},
data () {
return {
}
},
methods: {
inparent () {
alert('父組件響應了')
}
}
}
</script>
// 子組件
<template>
<div>
<el-button size="small" v-on:click="onparent">父組件響應吧!!!</el-button>
</div>
</template>
<script>
export default {
props: {},
data () { // 基礎數據
return {
}
},
methods: {
onparent () {
this.$emit('onchild')
}
}
}
</script>
這個例子中,子組件給父組件傳值通過$emit('onchild')
,觸發父組件的v-on:onchild
,v-on:onchild
響應后執行inparent
函數。但是就達到我們的目的了???
傳值,傳值,傳值,值呢?這個API里可沒說,那怎么辦呢?
很簡單按照程序工程師的思路來想,值肯定是這種模式:
this.$emit('onchild', 需要的值)
// 多個呢?
this.$emit('onchild', 需要的值1,需要的值2)
那接值呢?
onparent (需要的值1, 需要的值2) {
}
所以完整的模式應該是這樣的:
// 父組件
<template>
<div>
<child v-on:onchild="inparent"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: {
child: child
},
data () {
return {
}
},
methods: {
inparent (childs, childrens) {
alert('父組件響應了')
console.log(childs)
console.log(childrens)
}
}
}
</script>
// 子組件
<template>
<div>
<el-button size="small" v-on:click="onparent">父組件響應吧!!!</el-button>
</div>
</template>
<script>
export default {
props: {},
data () { // 基礎數據
return {
childs: '我是孩子的值',
childrens: '我是孩子的另一個值'
}
},
methods: {
onparent () {
this.$emit('onchild', this.childs, this.childrens)
}
}
}
</script>
sync-修飾符
在一些情況下,我們可能會需要對一個 prop 進行『雙向綁定』。當一個子組件改變了一個 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會導致問題,因為它破壞了『單向數據流』的假設。由于子組件改變 prop 的代碼和普通的狀態改動代碼毫無區別,當光看子組件的代碼時,你完全不知道它何時悄悄地改變了父組件的狀態。這在 debug 復雜結構的應用時會帶來很高的維護成本。
事實上,這正是 Vue 1.x 中的 .sync修飾符所提供的功能。但是VUE在 2.0 中移除了 .sync。后來在 2.3 VUE又重新引入了 .sync 修飾符。
// 父組件
<template>
<div>
<child :foo.sync="bar"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: {
child: child
},
data () {
return {
bar: 1
}
},
watch: {
bar: function () {
console.log(this.bar)
}
}
}
</script>
// 子組件
<template>
<div>
<div>{{foo}}</div>
<el-button size="small" v-on:click="onsync">改變foo</el-button>
</div>
</template>
<script>
export default {
props: {
foo: null
},
data () { // 基礎數據
return {}
},
methods: {
onsync () {
this.$emit('update:foo', this.foo + 1)
}
}
}
</script>
其實本質上VUE做到的只是:需要做的只是讓子組件改變父組件狀態的代碼更容易被區分。
即把<comp :foo="bar" @update:foo="val => bar = val"></comp>
簡寫為<child :foo.sync="bar"></child>
,不讓使用者在父元素上進行事件監聽了而已其他都是一樣的,它通過子組件傳值改變父組件,依賴props傳值把修改的父組件元素再傳回子組件而已。
小結:
父組件向子組件傳值通過props;子組件向父組件傳值,我們在父組件上通過v-on
監聽子組件的事件,而子組件通過$emit(eventName) 觸發事件。
至此組件的基本知識就結束了,高深的組件有關的,下一節再說。
提示:在最近幾天我會慢慢附上VUE系列教程的其他后續篇幅,后面還有精彩敬請期待,請大家關注我的專題:web前端。如有意見可以進行評論,每一條評論我都會認真對待。