父組件和子組件
我們經常分不清什么是父組件,什么是子組件。現在來簡單總結下:我們將某段代碼封裝成一個組件,而這個組件又在另一個組件中引入,而引入該封裝的組件的文件叫做父組件,被引入的組件叫做子組件。具體代碼如下
<div id="app">
<component2></component2>
</div>
<script>
// 全局注冊
Vue.component("component1", {
template: `
<div>
<h2>hello</h2>
</div>
`
})
const app = new Vue({
el: "#app",
data: {
message: "hello"
},
components: {
// 局部注冊
"component2": {
template: `
<div>
<component1></component1>
<h2>world</h2>
</div>
`,
}
}
})
</script>
1.全局注冊組件component1
2.局部注冊組件component2,component2中又引用了組件component1
最后我們在html中使用組件component-2,模板代碼就是
<div>
<component-1></component-1>
<h2>world</h2>
</div>
又因為組件component1中也有模板,所以程序會自動進行解析,最后component-2的html代碼為
<div>
<div>
<h2>hello</h2>
</div>
<h2>world</h2>
</div>
所以我們在瀏覽器上看到的效果應該是:
hello
world
結果
component1是子組件,component2是父組件
模板分離寫法
上面我們創建組件的時候,都在組件中寫了模板template,但是在編譯器里這樣寫,不僅沒有代碼提示,而且換行也不對齊,寫起來很麻煩,所以這里介紹模板分離寫法
template標簽
我們將原來在組件里寫的template模板抽離出來,放在html中,使用template標簽,並且給他附上id屬性如下:
<template id="component2">
<div>
<component1></component1>
<h2>world</h2>
</div>
</template>
然后在組件中,將原來template標簽的內容換成id,這樣程序就會自動去尋找對應的id模板:
components: {
// 局部注冊
"component2": {
template: `#component2`,
}
}
推薦這種寫法
text/x-template
我們還有另一中寫法,跟上面差不多,上面我們用的template標簽,此寫法只需將template中的內容放到script標簽中,並給與類型type=text/x-template,再給上一個id屬性即可,如下:
<script type="text/x-template" id="component2">
<div>
<component1></component1>
<h2>world</h2>
</div>
</script>
父子組件通信-父傳子
當我們創建了父組件和子組件,如果子組件也想獲取父組件上相同的數據,一種方法是像后台發送接口獲取數據,但是這樣會給服務器造成壓力,所以我們有了第二種方法,通過props屬性來獲取父組件的數據
<div id="app">
<test1 :cmovies="movies"></test1>
</div>
<template id="test1">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ["海賊王", "海爾兄弟", "海王"]
},
components: {
"test1": {
template: `#test1`,
props: ['cmovies'],
data(){
return{}
},
}
}
})
</script>
這里我們將app實例定義為父組件,又定義了子組件test1,此時子組件test1想獲取父組件data中的數據來展示在頁面上,就需要寫入props屬性,這里綁定了變量cmovies,最后我們在html中使用子組件test1時,想傳入父組件data中的數據,就需要綁定屬性,:cmovies="movies",cmovies是props中定義的變量,綁定的值是movies列表,所以上面的代碼<li v-for="item in cmovies">{{item}}</li>中的cmoviess的值其實是列表movies的數據,因為父組件已經向子組件傳遞了值
最后網頁上就能顯示movies中的電影了

以上頁面上顯示的無序列表,我們是使用了子組件,數據是從父組件data中傳入到了子組件,子組件通過props與父組件綁定
Prop 類型
上面的例子我們把props定義成為了一個數組,用於接收來自父組件的數據。我們也可以使用對象作為替代,對象允許配置高級選項,如類型檢測、自定義驗證和設置默認值。
type:可以是下列原生構造函數中的一種:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定義構造函數、或上述內容組成的數組。會檢查一個prop是否是給定的類型,否則拋出警告。Prop類型的更多信息在此。default:any
為該prop指定一個默認值。如果該prop沒有被傳入,則換做用這個值。對象或數組的默認值必須從一個工廠函數返回。required:Boolean
定義該prop是否是必填項。在非生產環境中,如果這個值為truthy且該prop沒有被傳入的,則一個控制台警告將會被拋出。validator:Function
自定義驗證函數會將該prop的值作為唯一的參數代入。在非生產環境下,如果該函數返回一個falsy的值 (也就是驗證失敗),一個控制台警告將會被拋出。你可以在這里查閱更多prop驗證的相關信息。
示例
// 簡單語法
Vue.component('props-demo-simple', {
props: ['size', 'myMessage']
})
// 對象語法,提供驗證
Vue.component('props-demo-advanced', {
props: {
// 檢測類型
height: Number,
// 檢測類型 + 其他驗證
age: {
type: Number,
default: 0,
required: true,
validator: function (value) {
return value >= 0
}
}
}
})
注意:當我們在使用props時,如果我們使用駝峰命名法,比如cMovies,然后我們在HTML中綁定時如果也這么寫,程序是不識別的,我們需要轉成c-movies這種短橫線形式
父子組件通信子傳父
子傳父的場景,通常是子組件傳遞事件給父組件監聽,告訴父組件用戶點擊了哪個按鈕,使用的函數是$emit
vm.$emit( eventName, […args] )
參數:
- eventName:事件名字
- args:不定長的數組
觸發當前實例上的事件。附加參數都會傳給監聽器回調。
示例:
<div id="app">
<test1 @item-click="cpnClick"></test1>
</div>
<template id="test1">
<div>
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello"
},
methods: {
cpnClick(item){
console.log("success", item)
}
},
components: {
// 局部注冊組件test1
"test1": {
data(){
return{
categories: [
{id: "aaa", name: "熱門推薦"},
{id: "bbb", name: "手機數碼"},
{id: "ccc", name: "家用電器"},
{id: "ddd", name: "食品飲料"},
]
}
},
methods: {
btnClick(item){
this.$emit("item-click", item)
}
},
template: `#test1`
}
}
})
</script>
以上代碼定義了test1子組件,並在methods中通過$emit傳遞了事件和額外的參數item,然后父組件通過@item-click="cpnClick"事件綁定,這樣父組件就能收到子組件的點擊事件,並且觸發自己的點擊事件,效果如下

我們可以看到控制台打印的日志中含有子組件的categories的分類
父子組件通信-結合雙向綁定案例
下面這個案例結合了父傳子和子傳父,還有v-model,是個非常全面的案例
基本模板代碼
<div id="app">
<cpn :number1="num1" :number2="num2"></cpn>
</div>
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<h2>{{number2}}</h2>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
num1: 0,
num2: 1,
},
components: {
// 定義子組件cpn
"cpn": {
template: `#cpn`,
props: {
number1: Number,
number2: Number,
}
}
},
})
</script>
代碼做了如下的事情
1.定義了子組件cpn,又定義了2個屬性number1和number2用來接收父組件傳遞的數據
2.在html代碼中引用了子組件cpn,並將app實力中的num1和num2傳遞給子組件props中的屬性
3.最后我們在頁面上顯示的數據number1和number2其實就是data中的num1和num2
最后頁面展示的效果就是
0
1
增加雙向綁定
在上面的模板基礎上,我們新增雙向綁定,新增2個input標簽,並使用v-model與props中的屬性進行綁定
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<input type="text" v-model="number1">
<h2>props:{{number2}}</h2>
<input type="text" v-model="number2">
</div>
</template>
以上代碼就完成了雙向綁定,但是會有報錯警告

當我們在子組件中與props雙向綁定的時候,會出現警告,意思是不要使用props雙向綁定,建議使用data或者compused來雙向綁定,這里修改成與data綁定
<template id="cpn">
<div>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
data(){
return{
dnumber1: this.number1,
dnumber2: this.number2,
}
},
當我們與data進行綁定以后,就不會出現報錯了
反向綁定
接着上面的思路,我們希望input輸入值的時候,改變data中的同時,也同時改變父組件中num1和num2的值,這時就需要反向綁定通過子傳父,下面是完整的代碼
<div id="app">
<cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<label>
<input type="text" :value="dnumber1" @input="num1Input">
</label>
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<label>
<input type="text" :value="dnumber2" @input="num2Input">
</label>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
num1: 0,
num2: 1,
},
methods: {
num1change(value){
this.num1 = parseInt(value)
},
num2change(value){
this.num2 = parseInt(value)
},
},
components: {
// 定義子組件cpn
"cpn": {
template: `#cpn`,
props: {
number1: Number,
number2: Number,
},
data(){
return{
dnumber1: this.number1,
dnumber2: this.number2,
}
},
methods: {
num1Input(event){
// 1.將input中的value賦值到dnumber中
this.dnumber1 = event.target.value
// 2.為了讓父組件可以修改值,需要發出一個事件
this.$emit("num1change", this.dnumber1)
},
num2Input(event){
// 1.將input中的value賦值到dnumber中
this.dnumber2 = event.target.value
// 2.為了讓父組件可以修改值,需要發出一個事件
this.$emit("num2change", this.dnumber2)
}
}
}
},
})
</script>
效果如下

組件訪問父訪問子
當我們父組件中需要使用子組件中的函數或者屬性值,我們可以使用$refs,它返回的類型是Object,先看如下代碼
<div id="app">
<cpn ref="aaa"></cpn>
<button @click="btnClick">按鈕</button>
</div>
<template id="cpn">
<div>
我是子組件
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello"
},
methods: {
btnClick(){
console.log(this.$refs.aaa.name)
this.$refs.aaa.showMessage()
}
},
components: {
"cpn": {
template: `#cpn`,
data(){
return{
name: "我是子組件的name"
}
},
methods: {
showMessage(){
console.log("showMessage")
}
}
}
}
})
</script>
上述代碼干了如下幾件事情
1.創建了組件cpn,組件中定義了一個方法showMessage和屬性name
2.父組件中使用子組件cpn,並綁定了一個屬性ref值為aaa,相當於是唯一標識
3.父組件的方法btnClick需要使用子組件中的方法和屬性,只需要this.$refs.aaa,這里的aaa就是上面綁定的子組件的屬性
4.最后使用this.$refs.aaa.name就代表使用了子組件中的name屬性
