父元件和子元件
我們經常分不清什麼是父元件,什麼是子元件。現在來簡單總結下:我們將某段程式碼封裝成一個元件,而這個元件又在另一個元件中引入,而引入該封裝的元件的檔案叫做父元件,被引入的元件叫做子元件。具體程式碼如下
<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
屬性