1. 組件嵌套
1.1 組件的嵌套使用
之前有說過,Vue組件跟Vue實例是一樣的,因此在Vue中一個組件中也可以定義并使用自己的局部組件,這就是組件的嵌套使用
例如:示例代碼如下:
<div id="app">
<!-- 3. 使用組件 -->
<my-component></my-component>
</div>
<script>
// 子組件選項
let sonComponent = {
template:`
<span>我是子組件</span>
`,
}
// 父組件選項對象
let MyComponent = {
template: `
<div>
<h2>我是父組件</h2>
<my-son></my-son>
<my-son />
</div>
`,
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 2. 注冊局部組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
顯示結果:
通過示例我們就了解到,組件可以嵌套使用,那么我們就不得不思考一個問題,他們各自之間的數據關系如何?能否相互使用對方的數據呢?
1.2 組件間的數據關系
組件實例的作用域是孤立的惠勒。這意味著不能 (也不應該) 在子組件的模板內直接引用父組件的數據。
示例代碼如下
<div id="app">
<!-- 3. 使用組件 -->
<my-component></my-component>
</div>
<script>
// 子組件選項
let sonComponent = {
template:`
<span>我是子組件{{msg}}</span>
`,
}
// 父組件選項對象
let MyComponent = {
template: `
<div>
<h2>我是父組件</h2>
<h3>父組件中使用子組件</h3>
<my-son></my-son>
<my-son />
<h3>父組件中使用父組件數據</h3>
{{ msg }}
</div>
`,
data(){
return {
msg: "哈哈,我是父組件數據"
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 2. 注冊局部組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
顯示結果:
如果在子組件中強行使用父組件的數據 ,就會報錯,
那么子組件如何才能獲取父組件的數據呢?
2. props 屬性
父組件可以使用 props 把數據傳給子組件。
2.1 props基本使用
父組件在使用子組件時, 可以將父組件的數據綁定到使用子組件的自定義標簽上,
子組件在選項中添加一個props屬性來接收數據
示例代碼如下:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<div>
<table border="1">
<tr colspan="3">子組件數據</tr>
<tr>
<td>my name</td>
<td>{{ myName }}</td>
</tr>
<tr>
<td>my age</td>
<td>{{ myAge }}</td>
</tr>
</table>
</div>
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<h2>顯示父組件傳給子組件的數據</h2>
<!-- 通過兩個組件的契合點,父組件通過自定義屬性將數據傳遞給子組件 -->
<!-- 在屬性傳輸數據的時候使用連字符語法 -->
<my-son :my-name="name" :my-age="age"></my-son>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
// 數組里放父組件中自定義屬性的名字
// props 里面使用駝峰寫法
props:["myName","myAge"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
name:"wuwei",
age:18
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
顯示結果:
2.2 數據傳遞分類
都知道在JS中數據分為兩大類,基本數據類型和引用數據類型
因此在父組件向子組件傳遞數據時也分為兩種:為傳值和傳引用
傳值:String Number Boolean
傳引用: Array Object
那么接下來我們好好研究研究兩者的不同
2.2.1 傳遞的是基本類型
基本類型,顧名思義,就是傳遞JS基本數據類型的數據
示例代碼如下:
<div id="app">
<conter :count="3"></conter>
<conter :count="4"></conter>
</div>
<script>
var conter = {
props: ['count'],
template:'<div @click="handleClick">{{count}}</div>',
methods:{
handleClick(){
// console.log(this.count)
this.count++
}
}
}
var app = new Vue({
el:'#app',
components:{
conter
}
})
</script>
其實這個時候我們發(fā)現在使用子組件傳遞數據時并沒有使用父組件data中的數據,但仍然使用了v-bind
動態(tài)綁定指令, Why 為什么呢? 不用v-bind
指令就不能傳遞數據了嗎?
答案當然不是啦, 之前有講過, 屬性如果不使用v-bind
指令綁定,那么屬性值將是字符串,如果使用v-bind
指令綁定, 屬性值雖然還是引號,但是引號中確實JavaScript表達式
因此上面的示例中我們希望的是傳遞數據過去,那么引號中的3 只有在JavaScript表達式中才表示數字的字面量, 因此需要v-bind
指令
我們也可以嘗試一下,不是用v-bind的情況
<div id="app">
<!-- 不使用v-bind 指令-->
<conter count="3"></conter>
<conter count="4"></conter>
</div>
<script>
var conter = {
props: ['count'],
template:'<div @click="handleClick">{{count}}</div>',
methods:{
handleClick(){
// console.log(this.count)
this.count++
}
}
}
var app = new Vue({
el:'#app',
components:{
conter
}
})
</script>
顯示結果:
此時通過兩個案例的對比,我們就可以發(fā)現, 使用和不使用v-bind
指令的區(qū)別
也就是說不使用指令, 傳遞給子組件的數據將永遠是字符串, 不管你寫成什么樣子
使用v-bind
指令, 屬性值引號中的內容將變成表達式,那么你就可以傳遞任何JS數據類型的數據
這既是Vue官網中關于props 靜態(tài)傳輸和動態(tài)傳輸.
解析來我們在來看看父組件給字符串傳遞引用值的情況
2.2.2 傳遞的是引用類型的值
傳引用就是傳遞引用類型的數據,
其實我們最想關注的是,傳遞引用類型的數據是數據的拷貝,還是內存地址的拷貝
因為這涉及到在子組件中是否可以通過props
修改父組件中的數據
示例代碼如下:
<div id="app">
<child1 :aa="obj"></child1>
<child2 :bb="obj"></child2>
</div>
<script>
var child1 = {
props:['aa'],
template:`<div>{{aa}}</div>`,
mounted(){
console.log(this.aa)
}
}
var child2 = {
props:['bb'],
template:`
<div>
<div>{{this.bb}}</div>
<button @click="handleClick">點擊</button>
</div>
`,
methods: {
handleClick(){
this.bb.name="haha"
}
}
}
new Vue({
el:'#app',
data: {
obj:{
name: 'wuwei'
}
},
components: {
child1,child2
}
})
</script>
通過示例我們發(fā)現父組件向兩個不同的子組件傳遞了同一個引用數據類型,
兩個子組件都拿到了相同的數據,也顯示相同的內容,
可是在child2
子組件中定義了一個按鈕用于改變child2
這個子組件通過props
從父組件獲取過來的數據
var child2 = {
props:['bb'],
template:`
<div>
<div>{{this.bb}}</div>
<button @click="handleClick">點擊</button>
</div>
`,
methods: {
handleClick(){
this.bb.name="haha"
}
}
}
點擊后顯示的結果:
通過示例我們就了解到,父組件向子組件傳遞數據是,子組件通過props
屬性接受,
但請不要隨意的更改props
屬性中的數據, 因為可能會有你意向不到的問題,
那么怎么解決這類問題呢, 接著往下看.
2.3 單向數據流
vue默認是單向數據流,所謂的單向數據流,就是數據傳遞是單向的
既然父組件將數據傳遞給了子組件,那么子組件中如果對于數據進行改變就有可能影響其他使用這數據的組件
注意:
這個只限傳遞基本數據類型的值,傳遞引用數據類型的問題上例中已經處理過
示例代碼如下:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<table border="1">
<tr>
<th colspan="3">子組件數據</th>
</tr>
<tr>
<td>my name</td>
<td>{{ myName }}</td>
<td>
<input type="text" v-model="myName">
</td>
</tr>
<tr>
<td>my age</td>
<td>{{ myAge }}</td>
<td>
<input type="text" v-model="myAge">
</td>
</tr>
</table>
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<table border="1">
<tr>
<th colspan="3">父組件數據</th>
</tr>
<tr>
<td>name</td>
<td>{{ name }}</td>
<td>
<input type="text" v-model="name">
</td>
</tr>
<tr>
<td>age</td>
<td>{{ age }}</td>
<td>
<input type="text" v-model="age">
</td>
</tr>
</table>
<!-- 通過兩個組件的契合點阱飘,父組件通過自定義屬性將數據傳遞給子組件 -->
<!-- 在屬性傳輸數據的時候使用連字符語法 -->
<my-son :my-name="name" :my-age="age"></my-son>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
// 數組里放父組件中自定義屬性的名字
// props 里面使用駝峰寫法
props:["myName","myAge"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
name:"wuwei",
age:18
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
修改父組件數據:
修改子組件中數據,
通過示例測試,我們發(fā)現
- 當父組件數據更新時,傳遞給子組件的props也會更新,
- 當子組件修改了props的數據, 父組件不會有任何變化(基本數據類型報錯,引用類型直接替換引用地址也報錯, 但是直接修改屬性不報錯,但是不建議這么用)
這就是所謂的單項下行數據流
這么做的目的是為了防止子組件無意修改了父組件的狀態(tài)
總結:
- 子組件不能(也不要)直接修改父組件傳遞過來的值,否則就會報錯
- 修改父組件傳遞過來引入類型的屬性不會報錯,但是會導致,其他使用了這個數據的子組件里面的數據也會發(fā)生變化(這也是很大的entity)
2.4 prop響應式
通過上一個例子,我們發(fā)現父組件的數據發(fā)生變化, 子組件也會隨著發(fā)生變化, 也就是,父組件在使用子組件時,給子組件prop
傳遞的數組無論何時發(fā)生改變, 在子組件內任何使用該prop
的地方都會發(fā)生更新
這就是props
響應式
示例代碼如下:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<div>
被點擊了{{ num }}次
</div>
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<my-son :num="num"></my-son>
<!--
每次點擊更改父組件data中的數據, 子組件prop關聯的數據也會跟隨發(fā)生改變
-->
<button @click="handleClick">點擊+1</button>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
props:["num"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
num: 1
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
},
methods:{
handleClick(){
this.num++
}
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
點擊結果
3. 如何修改數據
上一小節(jié)學習我們已經知道了:
- 父組件可以向子組件傳遞基本數據類型和引用數據類型的數據
- 子組件如果直接修改
props
中的基本數據類型數據和引用類型數據的內存地址就報錯 - 如果修改引用類型數據里的值雖然不會報錯,但是這樣非常不友好
- 因此不推薦在子組件直接修改
props
數據中的數據
可是有的是需要修改傳遞過來的數據,那么如何解決這樣的需求呢?
其實我們可以將父組件傳遞過來的值賦值給組件自己的數據,這樣我們就可以修改自己的數據了
3.1 子組件修改基本數據
示例代碼如下:
var conter = {
props: ['count'],
data(){
return {
num: this.count
}
},
template:'<div @click="handleClick">{{num}}</div>',
methods:{
handleClick(){
// console.log(this.num)
this.num++
}
}
}
3.2 子組件修改引用類型
如果父組件向子組件傳遞的引用數據類型的數據,我們按照基本數據類型那種直接將數據賦值給子組件自己的數據就行不同了,因為賦值給自己的數據也是內存地址的賦值,因此直接賦值,修改還是會改變父組件的數據. 那么要怎么辦嗯?
其實我們可以在子組件內的data屬性中淺拷貝一份父組件傳遞過來的引用數據類型,子組件如果想修改自己的顯示,就修改自己data中的數據
將上面例子中child2
子組件修改為如下代碼
var child2 = {
props:['bb'],
// 在自己的data數據中淺拷貝一份父組件傳過來的引用數據
data(){
return {
cc: JSON.parse(JSON.stringify(this.bb))
}
},
// 在自己的視圖中顯示自己的數據
template:`
<div>
<div>{{cc}}</div>
<button @click="handleClick">點擊</button>
</div>
`,
// 如果有修改就修改自己data中的數據
methods: {
handleClick(){
this.cc.name="haha"
}
}
}
點擊結果:
這樣就可以在子組件中使用自己的數據,修改自己的數據, 進而解決問題
4. Prop 驗證
我們可以為組件的 prop 指定驗證規(guī)則。如果傳入的數據不符合要求,Vue 會發(fā)出警告剖煌。這對于開發(fā)給他人使用的組件非常有用。
要指定驗證規(guī)則逝淹,需要用對象的形式來定義 prop耕姊,而不能用字符串數組:
4.1 驗證父組件傳遞過來的數據類型
// 子組件選項
let sonComponent = {
// props 驗證數據類型
props:{
myName:String,
myAge: Number
},
template:`#son`,
}
如果數據類型驗證通過, 則正常運行,如果驗證不通過,則報錯
4.2 允許數據傳遞多種數據類型
如果一個數據可以接受多種數據類型,則使用數組將所有允許的類型羅列在一起
// 子組件選項
let sonComponent = {
// props 驗證數據類型
props:{
myName:String,
myAge: [Number,String]
},
template:`#son`,
}
這樣myAge 在接受的數據是Number 類型或String 都不會報錯
注意:
這兩種驗證數據的類型,只驗證父組件傳遞過來數據的類型是否符合, 并不關心用戶是否傳數據過來, 不傳也不會報錯,
那么type
屬性都可以指定哪些類型呢?
4.3 type類型
驗證類型的type屬性的值就是原生的構造器(構造函數)
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
4.4 必須傳遞的數據
有的時候我們需要指定一些數據為必須傳遞的, 如果不傳遞就會報錯, 這個時候,數據的只是一個對象
對象就是對于props傳遞數據的配置對象
驗證的配置對象中
- type: 驗證數據類型
- required: 驗證數據是否為必須傳遞,true,是必須傳遞,不傳就報錯
// 子組件選項
let sonComponent = {
props:{
myName:String,
myAge: {
type:Number, // type為驗證數據類型
required: true // required為數據是否必須傳遞,true是false 否
}
},
template:`#son`,
}
4.5 指定默認值
如果父組件未傳遞數據過來,則使用默認值
注意:
配置對象中required 必傳選項 和 default 默認值選項,不能同時使用
默認是就是父組件在未傳遞數據的時候使用, 如果你還需要父組件必須傳遞數據, 默認值就不沒有意義了嗎
// 子組件選項
let sonComponent = {
props:{
myName:{
type: String, // 驗證類型
default: '默認姓名' // 默認值
},
myAge: {
type:Number, // type為驗證數據類型
required: true // required為數據是否必須傳遞,true是false 否
}
},
template:`#son`,
}
如果傳遞過來的是是引用類型, 那么在定義默認值的時候必須是一個函數,函數返回引用類型的數據
為什么是一個函數就不用在說吧,和組件數據data
是函數同一個意思, 保存每次傳遞都是第一無二的數據
示例代碼如下
let sonComponent = {
props:{
myName:{
type: String, // 驗證類型
default: '默認姓名' // 可以賦予默認值,如果父組件沒有傳值,使用默認值
},
myAge: {
type:Number, // type為驗證數據類型
required: true // required為數據是否必須傳遞,true是false 否
// 此屬性表示必須傳值,但是不能跟default同用
},
myLike:{
type:Array, // 限定的數據類型是引用類型的數組
default: function(){ //如果傳遞過來的是一個引用類型的值,默認值是函數
return []
}
}
},
template:`#son`,
}
4.6 自定義驗證規(guī)則
自定義驗證是一個函數,返回true則驗證通過,返回false則驗證不通過
示例代碼如下:
let sonComponent = {
// 子組件通過props接受數據并使用
// 數組里放父組件中自定義屬性的名字
// props 里面使用駝峰寫法
props:{
myName:{
type: String, // 驗證類型
default: '默認姓名' // 默認值
},
myAge: {
validator:function(value){ // 自定義驗證器
return value > 16 // 返回true 驗證通過, 返回false 驗證不通過報錯
}
}
},
template:`#son`,
}
注意
props 會在組件實例創(chuàng)建之前進行校驗,
所以在
default
或validator
函數里栅葡,諸如data
、computed
或methods
等實例屬性都還無法使用。
props
特性就是父組件通過屬性傳值,子組件有對應的props
接受,那么這個屬性不會出現在網頁的標簽屬性上
5. 非Props 特性
5.1 非prop 屬性的了解
盡管為組件定義明確的 props
是推薦的傳參方式签杈,組件的作者卻并不總能預見到組件被使用的場景翘狱。
所以,組件可以接收任意傳入的特性熊咽,這些特性都會被添加到組件的根元素上莫鸭。
簡單的說就是父組件可以在使用子組件的時候給子組件傳遞n多的屬性, 只有子組件使用props
接受的才會成為子組件的參數, 沒有通過props
聲明的屬性就是非props, 這些屬性會自動添加為子組件根標簽的屬性
通過示例了解:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<div>
{{msg}}
</div>
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<!-- msg因為子組件prop定義了所以會成為子組件的參數 -->
<!-- title屬性因為子組件沒有在props聲明,所以是非prop, 會自動添加為 子組件根標簽的屬性-->
<my-son :msg="msg" :title="title"></my-son>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
props:["msg"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
msg:"我想說一句話",
title:"標題"
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
顯示結果
在總結一下:
非props就是子組件并沒有明確props
來接受父組件的傳值,那么在網頁中子組件傳值的屬性將會成為標簽的私有屬性
上面一個例子,了解到如果父組件給子組件傳遞非prop屬性,會自動成為子組件模板中根標簽的標簽屬性, 適用于任何HTML屬性或特性,
可是如果父組件傳遞的非prop屬性與子組件的根標簽的屬性重名了怎么辦呢?
會發(fā)生兩種情況, 一,替換,二合并, 先來看看替換的情況
5.2 非prop屬性的替換
如果父組件傳遞的prop屬性與子組件的根標簽的屬性重名,大部分情況會覆蓋子組件根標簽上的同名屬性. 即替換效果
示例代碼如下:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<!-- 子組件根標簽的type屬性值text 被父組件傳遞過了的非prop type屬性值 radio替換了 -->
<input type="text" />
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<!-- 父組件向子組件傳遞非prop屬性 type 值為radio -->
<my-son :type="type"></my-son>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
props:["num"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
type: "radio"
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
顯示結果
通過示例.發(fā)現: 子組件根標簽的type
屬性值text
被父組件傳遞過了的非props type
屬性值 radio
替換了
這樣的效果非常不好,可能會破壞子組件. 所以一定要注意.
5.3 非prop屬性的合并
當然了大部分會發(fā)生替換, 但是也有兩個特殊的屬性,會發(fā)生合并的效果,這兩個屬性就是class
與style
屬性
示例:
<div id="app">
<!-- 使用組件 -->
<my-component></my-component>
</div>
<!-- 子組件模板 -->
<template id="son">
<div class="box" style="background:skyblue;">
我是子組件
</div>
</template>
<!-- 父組件模板 -->
<template id="MyComponent">
<div>
<my-son :class="className" :style="style"></my-son>
</div>
</template>
<script>
// 子組件選項
let sonComponent = {
// 子組件通過props接受數據并使用
props:["num"],
template:`#son`,
}
// 父組件選項對象
let MyComponent = {
template: `#MyComponent`,
data(){
return {
className: "wrap",
style: {
width: '100px',
height: '100px'
}
}
},
// 將子組件定義為父組件的局部組件
components: {
mySon: sonComponent
}
}
// 實例中注冊組件
const vm = new Vue({
el:"#app",
components: {
"MyComponent": MyComponent
}
})
</script>
示例中,父組件通過非prop傳遞過去的class 和style屬性 與子組件根標簽的class 和 style 屬性發(fā)生合并.
6. 遍歷傳值,
利用v-for
循環(huán)指令. 多次使用組件.
在配合使用 v-bind
指令將數據傳到每個組件中:
<div id="app">
<ol>
<!--
現在我們?yōu)槊總€ todo-item 傳遞了數據网棍,即其內容可以是動態(tài)的黔龟。
我們也需要為每個組件提供一個“key”,稍后再
作詳細解釋滥玷。
-->
<todo-item
v-for="item in groceryList"
v-bind:text="item.text"
v-bind:key="item.id">
</todo-item>
</ol>
</div>
<script>
// 定義子組件
let todoItem = {
props:["text"],
template:`
<li>{{text}}</li>
`
}
// Vue實例
var vm = new Vue({
el: '#app',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '隨便其它什么人吃的東西' }
]
},
components:{
todoItem
}
})
</script>
顯示結果