推薦我的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 特性擴展。
注冊組件
在注冊組件時我們有兩種方式挽荡,第一種注冊全局組件藐石,第二種是局部使用組件,對于那些應用多的組件來說全局無疑是最好的選擇定拟,對于變數太大于微,應用不多且在統(tǒng)一目錄下的局部使用是我們想要的。
全局注冊:
要注冊一個全局組件青自,你可以使用 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的數據引入變?yōu)?code>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>
構成組件
組件意味著協(xié)同工作,引用組件的地方叫父組件杰妓,如<simple-counter></simple-counter>
藻治,組件的內容則被成為子組件。
通常父子組件會是這樣的關系:組件 A 在它的模版中使用了組件 B 巷挥。它們之間必然需要相互通信:父組件要給子組件傳遞數據桩卵,子組件需要將它內部發(fā)生的事情告知給父組件。然而倍宾,在一個良好定義的接口中盡可能將父子組件解耦是很重要的吸占。這保證了每個組件可以在相對隔離的環(huán)境中書寫和理解,也大幅提高了組件的可維護性和可重用性凿宾。
在 Vue.js 中矾屯,父子組件的關系可以總結為 props down, events up 。父組件通過 props 向下傳遞數據給子組件初厚,子組件通過 events 給父組件發(fā)送消息件蚕〔蹋看看它們是怎么工作的排作?如圖:
父子組件的交互的原理這個具體呢?
父子組件的交互
props的單向流
組件實例的作用域是孤立的。這意味著不能(也不應該)在子組件的模板內直接引用父組件的數據。要讓子組件使用父組件的數據毡惜,我們需要通過子組件的props選項橱乱。
我們在開發(fā)中瞒大,一個單文件組件的.vue
文件的基本構成是這樣的:
<template>
<div>
</div>
</template>
<script>
export default {
// el: '',
props: {}, // 父到子傳參
components: {}, // 組件接收
mixins: [], // 混合
data () { // 基礎數據
return {
//
}
},
created () {}, // 創(chuàng)建周期
watch: {}, // 狀態(tài)過渡
methods: {}, // 方法存放的地方
computed: {}, // 計算屬性存放的地方
filters: {}, // 過濾
directives: {} // 指令
}
</script>
可是常用的很少,父組件給子組件傳值使用的就是props選項
搪桂。
props選項可以接受兩種模式的參數:
第一種固定的屬性:如這樣
message="hello!
傳一個普通的字符透敌;
第二種動態(tài)屬性:如這樣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 () {}, // 創(chuàng)建周期
watch: {}, // 狀態(tài)過渡
methods: {} // 方法存放的地方
}
</script>
prop 是單向綁定的:當父組件的屬性變化時酗电,將傳導給子組件,但是不會反過來内列。這是為了防止子組件無意修改了父組件的狀態(tài)撵术。
另外,每次父組件更新時话瞧,子組件的所有 prop 都會更新為最新值嫩与。這意味著你不應該在子組件內部改變 prop 。如果你這么做了交排,Vue 會在控制臺給出警告蕴纳。
但是有時我們就是需要修改,怎么辦个粱?(老子需求)
一般情況下我們修改通常是這兩種原因:
- prop 作為初始值傳入后古毛,子組件想把它當作局部數據來用;
- prop 作為初始值傳入,由子組件處理成其它數據輸出稻薇。
對于第一種情況我們可以李代桃僵嫂冻,即用另外一個變量替代它,把它的值賦給那個變量:
data () { // 基礎數據
return {
//
counter: this.message
}
}
第二種情況可以定義一個計算屬性塞椎,處理 prop 的值并返回:
computed: {
messagetoLowerCase: function () {
return this.message.trim().toLowerCase()
}
}
注意:使用字面量語法傳遞數值時桨仿,必須使用動態(tài)props,即如這樣`v-bind:number="1"`
props驗證
我們可以為組件的 props 指定驗證規(guī)格案狠。如果傳入的數據不符合規(guī)格服傍,Vue 會發(fā)出警告。當組件給其他人使用時骂铁,這很有用吹零。
要指定驗證規(guī)格,需要用對象的形式拉庵,而不能用字符串數組:(修改上面的例子)
// 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
}
}
}
驗證規(guī)格模板:
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) 監(jiān)聽事件
使用 $emit(eventName) 觸發(fā)事件
注意:Vue的事件系統(tǒng)分離自瀏覽器的EventTarget API撼嗓。盡管它們的運行類似柬采,但是$on 和 $emit 不是addEventListener 和 dispatchEvent 的別名。
你們一定很奇怪怎么用事件監(jiān)聽來向父元素傳遞静稻?
其實原理很簡單就是我們在父組件上通過v-on
監(jiān)聽子組件的事件警没,而子組件通過$emit(eventName) 觸發(fā)事件。例子如下:
// 父組件
<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">父組件響應吧U裢濉I奔!!</el-button>
</div>
</template>
<script>
export default {
props: {},
data () { // 基礎數據
return {
}
},
methods: {
onparent () {
this.$emit('onchild')
}
}
}
</script>
這個例子中押搪,子組件給父組件傳值通過$emit('onchild')
树酪,觸發(fā)父組件的v-on:onchild
,v-on:onchild
響應后執(zhí)行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">父組件響應吧2篮K荨!</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 的代碼和普通的狀態(tài)改動代碼毫無區(qū)別,當光看子組件的代碼時荐开,你完全不知道它何時悄悄地改變了父組件的狀態(tài)付翁。這在 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做到的只是:需要做的只是讓子組件改變父組件狀態(tài)的代碼更容易被區(qū)分佣渴。
即把<comp :foo="bar" @update:foo="val => bar = val"></comp>
簡寫為<child :foo.sync="bar"></child>
,不讓使用者在父元素上進行事件監(jiān)聽了而已其他都是一樣的初斑,它通過子組件傳值改變父組件辛润,依賴props傳值把修改的父組件元素再傳回子組件而已。
小結:
父組件向子組件傳值通過props见秤;子組件向父組件傳值砂竖,我們在父組件上通過v-on
監(jiān)聽子組件的事件,而子組件通過$emit(eventName) 觸發(fā)事件鹃答。
至此組件的基本知識就結束了乎澄,高深的組件有關的,下一節(jié)再說测摔。
提示:在最近幾天我會慢慢附上VUE系列教程的其他后續(xù)篇幅置济,后面還有精彩敬請期待,請大家關注我的專題:web前端。如有意見可以進行評論浙于,每一條評論我都會認真對待修噪。