在進入這個話題之前,首先我們先來想一下在vue里,如何寫一個父子組件。為了簡單起見,下面的代碼我都沒用腳手架來構建項目,直接在html文件里引入vue.js來作為例子。父子組件的寫法如下:
<div id="app">
<parent></parent>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
let childNode = {
template: `<div>childNode</div>`
}
let parentNode = {
template: `
<div>
<child></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在這個代碼里,template表示的是模板,components表示的是組件。子組件先要在父組件里顯示就要通過components把組件對應的模板引擎(childNode對象)給掛載到父組件的components上去。然后子組件就會在父組件上得以顯示。
在父子組件中,如果想要父組件的變量傳遞到子組件中,則需要通過props屬性來進行傳遞。props有靜態的也有動態的,下面來介紹靜態的寫法:
<script>
let childNode = {
template: `<div>{{message}}</div>`,
props: ['message']
}
let parentNode = {
template: `
<div>
<child message="child"></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在這里,子組件通過在props里寫入想要獲取的父組件的某個屬性值,在父組件里,通過父組件在子組件里的占位符添加屬性的方式來傳值。在這里有一個命名規范,在子組件里,如果props里的參數由多個詞組成則應該用駝峰命名的寫法;而在父組件中則用橫線做分隔符的寫法。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: ['myMessage']
}
let parentNode = {
template: `
<div>
<child my-message="child"></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
而動態props的用法是將占位符進行綁定,將父組件的數據綁定到子組件的props中,可以實現父組件的數據發生改變的時候子組件的數據也會發生改變的動態效果。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: ['myMessage']
}
let parentNode = {
template: `
<div>
<input type="text" v-model="myData">
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data(){
return {
child: '',
myData: ''
}
},
watch: {
myData(newValue, oldValue){
this.child = newValue
}
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在這里,我通過利用v-model和watch來實現輸入框發生變化的時候父組件數據動態修改后子組件的數據跟着改變。為什么這里不用computed而用watch呢?這里其實也涉及到watch和computed的區別,在vue里,這兩個方法都能監聽數據的變化,不同的是computed是只能讀取不能寫入,而watch可以讀取也可以寫入。當父組件的myData這個值發生改變的時候就將myData的值不斷賦值給child,由於對占位符進行了綁定,所以子組件能夠接收到父組件的改變。
動態props和靜態props除了上面的區別以外還有一個區別,就是傳遞參數的時候數據類型發生變化。如下面的靜態props里
<script>
let childNode = {
template: `<div>{{myMessage}}的類型是{{type}}</div>`,
props: ['myMessage'],
computed: {
type() {
return typeof this.myMessage
}
}
}
let parentNode = {
template: `
<div>
<div>
<child my-message="1"></child>
</div>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
我們傳的1在子組件里卻是string類型,而如果是加入了綁定變成動態props:
<div>
<child :my-message="1"></child>
</div>
則會變成nunber,這種情況除了在數字類型發生以外,其他類型也會發生,需要注意。
在props里,我們可以驗證父組件傳過來的參數是否有問題,下面的例子主要是驗證父組件傳過來的數據是否是Number類型,如果不是的話則會在控制台里看到報錯。在用這個方法的時候不要引用vue.min.js而要引用vue.js,因為vue.min.js會省略掉相應的報錯提示,不利於開發的時候查看:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: Number
}
}
let parentNode = {
template: `
<div>
<input type="text" v-model="myData">
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123,
myData: ''
}
},
watch: {
myData(newValue, oldValue) {
if (/^[0-9]*$/.test(newValue)) {
this.child = parseInt(newValue)
} else {
this.child = '輸入的數據不是number類型'
}
}
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
可以看到我是在props里添加了一個對象,該對象里的myMessage屬性里有屬性值Number,表示驗證數字類型,當myMessage的值不是數字類型的時候就會報錯。除了Number的驗證規則以外還有String、Boolean、Function、Object、Array和Symbol等驗證規則,同時也可以寫成這樣來驗證多種類型:
props: {
myMessage: [Number, String]
}
或者可以通過一個自定義一個工廠函數來進行匹配,如:
props: {
myMessage: {
validator: function (value) {
return value > 10
}
}
}
當然,在props對象里面,還有另外三個屬性,一個是default屬性,表示的是父組件傳入值的時候子組件默認的值,另一個是require,表示的是該值是否要傳入,而type則表示的是驗證規則。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: {
type: Number,
// required: true,
default: 321
}
}
};
let parentNode = {
template: `
<div>
<div>
<child></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
一般來說,當props里的require表示為true的時候,則父組件要加上占位符,因為父組件有傳值過來,所以default的值被覆蓋了,如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: {
type: Number,
required: true,
default: 321
}
}
};
let parentNode = {
template: `
<div>
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
由於props是單向數據流,所以父組件傳過來的數據子組件修改了也不會修改到父組件的數據,而父組件一修改了則會修改到子組件的數據。所以想要反過來將子組件的數據傳到給父組件則要換一種方法。用的比較多的是自定義一個事件,如:
<script>
let childNode = {
template: `
<div>
<button @click="toParent">子傳父</button>
</div>
`,
methods: {
toParent() {
this.$emit('myParent', 'hello')
}
}
};
let parentNode = {
template: `
<child @myParent="change" :message="message"></child>
`,
components: {
'child': childNode
},
methods: {
change(data) {
console.log(data)
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在這里,子組件通過比如點擊事件來觸發一個自定義事件,該事件里有this.$emit的處理函數,第一個參數表示的是父組件里負責監聽的函數名,第二個參數表示的傳遞的數值。在父組件里,通過在子組件占位符綁定一個子組件this.$emit定義的方法名即可監聽得到傳入的參數。