32 組件
- 什么是組件: 用我自己的話理解就是 “可以復(fù)用的、易于管理的div”掏愁。 => 一個(gè)頁面需要輪播圖歇由,輪播圖寫成一個(gè)組件,如果需要復(fù)用在另一個(gè)頁面載入該組件即可果港。用 Vue 開發(fā)的網(wǎng)頁就是由不同的組件組成的沦泌。(之前都是寫無數(shù)個(gè)div劃分區(qū)域,現(xiàn)在寫無數(shù)個(gè)組件然后組裝成網(wǎng)頁)
- 基礎(chǔ)代碼
<div id="app">
<!-- 定義好全局組件之后辛掠, -->
<test1></test1>
<test2></test2>
<test3></test3>
<test4></test4>
</div>
<script>
// 我們在這里定義 “全局組件”
Vue.component('test1', { // Vue.component('標(biāo)簽', { //... 定義 })
template: '<h1> 這是全局組件的模板test1 </h1>', //模板
});
// 為了方便我們還可以定義一個(gè)變量存儲(chǔ)局部組件然后在根組件中載入 (這個(gè)定義也必須聲明在前面)
var test4= {
template: '<h2> 這是局部組件的模板test4 </h2>',
}
// 我們之前一直定義的其實(shí)是 “根組件”
var app = new Vue({
el: '#app',
data: {
},
// 我們還可以定義 “局部組件”
components: {
test3: {
template: '<h2> 這是局部組件的模板test3 </h2>',
},
// test4: test4,
// 可以使用 ES6 語法谢谦,由于 key: value 是一樣的,直接:
test4
}
});
// “全局組件” 必須在根組件之前定義
Vue.component('test2', {
template: '<h1> 這是全局組件的模板test2 </h1>',
});
</script>
- 定義全局組件
Vue.component('這里對應(yīng)載入時(shí)在html代碼中寫的標(biāo)簽名', { //這里面寫具體定義 })
- 全局組件 必須 在根組件前面聲明萝衩。
- 定義局部組件回挽,寫在根組件的 components 里面。
標(biāo)簽名: { //...定義 }
猩谊。
- 也可以在根組件前面聲明并用變量存儲(chǔ)起來千劈,然后直接在 components 中載入該變量即可。
- ES6的語法牌捷,如果 json 的 鍵值對墙牌, key的名字和value的名字一樣,可以直接寫 key 暗甥,不寫 value 喜滨。
33 組件中定義 data 數(shù)據(jù)
<div id="app">
<test></test>
</div>
<!-- 在 Vue 中可以使用這樣的方式定義模板 -->
<script type="text/x-template" id="myComponent">
<ul>
<li v-for="v in news">{{ v.id }} - {{ v.title }}</li>
</ul>
</script>
<script>
var myComponent = {
template: "#myComponent",
// data: {} //子組件中不能把data定義成屬性。會(huì)報(bào)錯(cuò):
// The "data" option should be a function that returns a per-instance value in component definitions.
data() { //必須這樣定義 data() { return {json對象} }
return {
news: [
{id:1, title:'測試1'},
{id:2, title:'測試2'},
],
};
}
};
var app = new Vue({
el: '#app',
data: {
},
components: {
// 有個(gè)坑: ES6 方式注冊子組件不能使用 駝峰 風(fēng)格命令淋袖,Vue會(huì)自動(dòng)轉(zhuǎn)換成全小寫鸿市。
test: myComponent,
}
});
</script>
- 我們是在外面定義的子組件锯梁,然后在根組件的 components 中注冊即碗。
- 在注冊的時(shí)候發(fā)現(xiàn)了一個(gè)問題: 用ES6語法注冊時(shí)焰情,我將組件名定義為 "myComponent" ,Vue 解析為 "mycomponent"剥懒。沒辦法注冊内舟。
- 在子組件中定義data必須使用
data() { //json對象 }
這樣的語法,而不能直接寫 data: {}
初橘。
- 可以使用
<script type="text/x-template" id="test"></script>
來定義模板验游。然后再子組件定義中使用模板 template: "#test"
34 父組件給子組件傳遞參數(shù)
<div id="app">
<!-- 在標(biāo)簽里面?zhèn)鬟f :子組件.props中聲明的名字 = "根組件.data中的變量名" -->
<!-- 如果不寫 :屬性="value" 的話,會(huì)解析為字符串 -->
<students :students="students" flag1="false" :flag2="true"></students>
</div>
<!-- 模板 -->
<script type="text/x-template" id="students">
<ul>
<li v-for="student in students"> {{ student.id }} - {{ student.name }} </li>
<span v-if="flag1"> 這其實(shí)是字符串 {{ flag1 }} </span> <!-- 這里之所以flag = "false" 會(huì)顯示是因?yàn)?字符串"false" = 布爾true -->
<span v-if="flag2"> :這才是布爾值 {{ flag2 }} </span> <!-- 因此這里如果傳遞 flag2 = false 則不會(huì)顯示 -->
</ul>
</script>
<script>
// 子組件
var students= {
template: "#students",
// 在接收時(shí)需要在 props 屬性中聲明
props: ['students', 'flag1', 'flag2'],
};
// 根組件
var app = new Vue({
el: '#app',
// 定義父組件的數(shù)據(jù)
data: {
students: [
{id: 1, name: 'liuihaoyu'},
{id: 2, name: 'lidaye'},
{id: 3, name: 'linainai'},
],
},
// 注冊子組件
components: {
students,
}
});
</script>
- 整個(gè)過程:(根組件)父組件.data中定義數(shù)據(jù)保檐,然后在html代碼中 子組件的標(biāo)簽上 使用
:xxx="定義的變量"
傳遞數(shù)據(jù)耕蝉。
- 子組件需要聲明
props = ['接受的數(shù)據(jù)xxx', '接受的數(shù)據(jù)yyy', '接受的數(shù)據(jù)zzz']
- 如果在html中, 子組件的標(biāo)簽上這么傳遞數(shù)據(jù)
xxx="value"
夜只,這樣 子組件.props 接受的其實(shí)是字符串垒在,而不是父組件data中聲明的值。
35 props 數(shù)據(jù)驗(yàn)證
<div id="app">
<!-- 在標(biāo)簽里面?zhèn)鬟f :子組件.props中聲明的名字 = "根組件.data中的變量名" -->
<!-- 如果不寫 :屬性="value" 的話扔亥,會(huì)解析為字符串 -->
<students></students>
</div>
<!-- 模板 -->
<script type="text/x-template" id="students">
<ul>
<li v-for="student in students"> {{ student.id }} - {{ student.name }} </li>
</ul>
</script>
<script>
// 子組件
var students= {
template: "#students",
// 在 props 中進(jìn)行數(shù)據(jù)驗(yàn)證
props: { // 1场躯、要求props定義成一個(gè)對象
students: { // 2、用 【屬性名: { //...相關(guān)配置 }】 進(jìn)行數(shù)據(jù)接收
type: [Array, Object], // 指定數(shù)據(jù)類型
// required: true, // 設(shè)置數(shù)據(jù)是否必填
default() { // 設(shè)置默認(rèn)值
return [
{id: 1, name: 'liuhaoyu'},
];
},
validator(value) { // 設(shè)置數(shù)據(jù)驗(yàn)證規(guī)則
return value[0].id > 0;
}
},
}
};
// 根組件
var app = new Vue({
el: '#app',
// 定義父組件的數(shù)據(jù)
data: {
},
// 注冊子組件
components: {
students,
}
});
</script>
- 如果想對 子組件.props 屬性聲明并接收的數(shù)據(jù)進(jìn)行數(shù)據(jù)校驗(yàn)的話旅挤,需要將 props 寫成對象踢关。
- 之后接受數(shù)據(jù)時(shí)用
變量名: { //...相關(guān)定義 }
進(jìn)行接收。
- 在相關(guān)定義里粘茄,我們可以:
-
type: 指定數(shù)據(jù)類型签舞,可以使用數(shù)組的形式指定多種數(shù)據(jù)類型
,
-
required: true | false
指定是否必須傳入該參數(shù)
-
default() { //return一個(gè)默認(rèn)值 }
設(shè)置默認(rèn)值
-
validator(value) { //執(zhí)行相關(guān)驗(yàn)證為true通過 }
進(jìn)行數(shù)據(jù)驗(yàn)證,為真則通過柒瓣。 這里發(fā)現(xiàn)不通過也會(huì)顯示數(shù)據(jù)瘪菌,但是會(huì)在瀏覽器控制臺(tái)提醒錯(cuò)誤。記得參數(shù)里要寫一個(gè) value 對應(yīng)傳遞進(jìn)來的數(shù)據(jù)嘹朗。
36 通過子組件呼叫父組件實(shí)現(xiàn)簡單的購物車
<div id="app">
<cart :goods="goods" @refresh="totalPrice"></cart>
<span>
總計(jì):¥ {{ total }} 元
</span>
</div>
<!-- 模板 -->
<script type="text/x-template" id="cart">
<table border="1">
<thead>
<tr>
<th>商品名稱</th>
<th>價(jià)格</th>
<th>數(shù)量</th>
</tr>
</thead>
<tbody>
<tr v-for="good in goods">
<td>{{ good.name }}</td>
<td>{{ good.price }}</td>
<td>
<input type="text" v-model="good.number" @blur="refresh">
</td>
</tr>
</tbody>
</table>
</script>
<script>
//子組件
var cart = {
template: "#cart",
props: {
goods: {
type: Array,
}
},
methods: {
refresh() {
this.$emit('refresh')
}
},
}
// 根組件
var app = new Vue({
el: '#app',
data: {
// 定義商品信息
goods: [
{name: "macbookPro 2018", price: "20000", number:1},
{name: "iphone 8", price: "6888", number:1},
{name: "iphone8 Plus", price: "8888", number:1},
],
// 總價(jià)初始化
total: 0,
},
// 注冊子組件
components: {
cart,
},
methods: {
// 計(jì)算總價(jià)
totalPrice() {
this.total = 0;
this.goods.forEach((good) => {
this.total += good.price * good.number;
});
}
},
// 掛載鉤子程序
mounted() { // 類似于 初始化程序
this.totalPrice(); // 直接調(diào)用計(jì)算總價(jià)的方法
},
});
</script>
- 在父組件掛載鉤子程序
mounted() { //調(diào)用里面的方法师妙,記得 **this.**function() }
。 “相當(dāng)于初始化方法” => 當(dāng)組件載入完的時(shí)候就執(zhí)行屹培。
- 子組件在更改商品數(shù)量時(shí)默穴,更新父組件中的總價(jià):
- 第1步: html代碼中,在子組件的標(biāo)簽上綁定事件 refresh
@refresh="totalPrice"
即子組件調(diào)用 refresh() 方法時(shí)褪秀,就調(diào)用的是父組件里的 計(jì)算總價(jià) 方法蓄诽。
- 第2步: 給綁定了商品數(shù)量的 input 添加一個(gè)失焦事件
@blur
當(dāng)它改變時(shí),調(diào)用子組件的 refresh() 方法
- 第3步: 子組件的 refresh() 方法被調(diào)用時(shí)媒吗,使用
$this.$emit('refresh')
調(diào)用 第1步 上綁定的自定義事件 refresh仑氛。
- 即: 子組件標(biāo)簽上的
@refresh
=> 事件, @blur="refresh"
=> 子組件的 methods 中定義的方法。 是 refresh() 方法锯岖,通過 this.$emit('事件名')
呼叫了事件介袜。
37 更優(yōu)寫法實(shí)現(xiàn)36購物車功能
<div id="app">
<!-- 這里使用 :綁定屬性.sync同步數(shù)據(jù)="父組件的goods" => 達(dá)到了當(dāng)子組件的goods發(fā)生變化時(shí),父組件的goods也會(huì)變化 -->
<cart :goods.sync="goods"></cart>
<span>
總計(jì):¥ {{ totalPrice }} 元
</span>
</div>
<!-- 模板 -->
<script type="text/x-template" id="cart">
<table border="1">
<thead>
<tr>
<th>商品名稱</th>
<th>價(jià)格</th>
<th>數(shù)量</th>
</tr>
</thead>
<tbody>
<tr v-for="good in goods">
<td>{{ good.name }}</td>
<td>{{ good.price }}</td>
<td>
<input type="text" v-model="good.number">
</td>
</tr>
</tbody>
</table>
</script>
<script>
//子組件
var cart = {
template: "#cart",
props: {
goods: {
type: Array,
}
},
}
// 根組件
var app = new Vue({
el: '#app',
data: {
// 定義商品信息
goods: [
{name: "macbookPro 2018", price: "20000", number:1},
{name: "iphone 8", price: "6888", number:1},
{name: "iphone8 Plus", price: "8888", number:1},
],
// 總價(jià)初始化
total: 0,
},
// 注冊子組件
components: {
cart,
},
// 計(jì)算總價(jià)
computed: {
totalPrice() {
var sum = 0;
this.goods.forEach((good) => {
sum += good.price * good.number;
});
return sum;
}
}
});
</script>
- 不需要在子組件標(biāo)簽上綁定自定義事件了出吹,只需要
:goods.sync="goods"
來實(shí)現(xiàn):當(dāng)子組件的變量 goods 發(fā)生變化時(shí)遇伞, 父組件的goods也同步(sync)發(fā)生變化。
- 同時(shí)不需要再在子捶牢、父組件中定義 methods 了鸠珠。 子組件的 input 中也不需要
@blur
綁定失焦事件了。
- 在父組件中定義 computed 計(jì)算屬性
computed: { 屬性() { //...進(jìn)行計(jì)算 return 結(jié)果 } }
- 在 html 代碼秋麸,總價(jià)中載入 計(jì)算屬性 中定義的 totalPrice 即可渐排。
38 子組件 slot 內(nèi)容分發(fā)
<div id="app">
<test>
<h1 slot="title">這是標(biāo)題</h1>
<p slot="content">這是內(nèi)容</p>
<test1 slot="myinput" type="email" title="郵箱" placeholder="username@example.com"></test1>
<test1 slot="myinput" type="text" title="用戶名" placeholder="yourNickName"></test1>
<test1 slot="myinput" type="password" title="密碼" placeholder="yourBirthday"></test1>
</test>
</div>
<!-- 子組件模板 test -->
<script type="text/x-template" id="test">
<div>
<slot name="title"></slot>
<slot name="content"></slot>
<slot name="myinput"></slot>
</div>
</script>
<!-- 子組件模板 test1 -->
<script type="text/x-template" id="test1">
<div>
<span>{{ title }}</span>
<input :type="type" :placeholder="placeholder">
</div>
</script>
<script>
// 子組件 test
var test = {
template: "#test",
};
// 子組件 test1
var test1 = {
template: "#test1",
props: ['type', 'title', 'placeholder']
}
// 根組件
var app = new Vue({
el: '#app',
data: {
},
components: {
test,
test1,
}
});
</script>
- 在子組件中定義 slot
<slot name="取個(gè)名字">
- 在根組件中填充 slot
<任意標(biāo)簽 slot="要填充的slot名字">填充的內(nèi)容</slot>
- 利用其他組件在根組件中填充某個(gè)組件
<其他組件 slot="要填充的slot名字" 話可以傳遞屬性...></其他組件>
- 可以反復(fù)填充一個(gè)slot。
39 scope 的使用
<div id="app">
<users :users="users" scope="v">
<!-- 這里 **必須** 使用 template 標(biāo)簽 -->
<!-- 并且使用 scope="任意變量名" 接收 slot拋出的數(shù)據(jù) -->
<template scope="data">
<li>
{{ data.user.id }} - {{ data.user.name }}
</li>
</template>
</users>
</div>
<script type="text/x-template" id="users">
<ul>
<!-- <li v-for="user in users"> {{ user.id }} - {{ user.name }}</li> -->
<!-- 這里用slot定義灸蟆,但是在后面用 :user="user" 拋出數(shù)據(jù) -->
<slot v-for="user in users" :user="user"></slot>
</ul>
</script>
<script>
var users = {
template: "#users",
props: {
users: {
type: Array,
},
}
};
var app = new Vue({
el: '#app',
data: {
users: [
{id: 1, name: 'liuhaoyu'},
{id: 2, name: 'lidaye'},
{id: 3, name: 'linainai'},
]
},
components: {
users,
}
});
</script>
- 具體過程: 父組件中定義 data.users 數(shù)據(jù)飞盆,然后在子組件標(biāo)簽上傳遞給子組件。
- 然后在子組件循環(huán)時(shí)次乓,使用
<slot>
來遍歷數(shù)據(jù)
- 并且在遍歷時(shí)吓歇,拋出數(shù)據(jù)
<slot v-for="data in datas" :data="data"></slot>
- 在父組件中,載入子組件的標(biāo)簽內(nèi)
<子組件>
<template scope="任意變量名這里暫時(shí)用data">
<合適的標(biāo)簽>
{{ data.data.屬性 }} =>第一個(gè)data對應(yīng)scope里的票腰,第二個(gè)data對應(yīng)子組件中使用 :data 拋出的城看。
</合適的標(biāo)簽>
</template>
</子組件>
- 總體來說,這個(gè)功能不常用杏慰。整個(gè)邏輯更像是在 html 代碼中遍歷子組件中的 slot测柠。
40 動(dòng)態(tài)組件
<div id="app">
<input type="radio" v-model="formType" value="myInput"> 使用 input
<input type="radio" v-model="formType" value="myTextarea"> 使用 textarea
<div :is="formType"></div>
</div>
<script>
var myInput = {
template: "<div> <input> </div>",
};
var myTextarea = {
template: "<div> <textarea></textarea> </div>",
}
var app = new Vue({
el: '#app',
data: {
formType: "myInput",
},
components: {
myInput,
myTextarea
}
});
</script>
- 我們實(shí)現(xiàn)了一個(gè)功能:通過勾選不同的單選框,顯示不同的表單項(xiàng)(input | textarea).
- 具體實(shí)現(xiàn)是通過一個(gè) div 作為載體, 綁定 id 屬性
<div :id="formType">
.
- 這里的 formType 對應(yīng)的是我們定義并在根組件的 components 屬性中聲明的根組件.
- 補(bǔ)充一點(diǎn)疑問: 子組件在 components 聲明叫做 myInput, 那么在 html 中載入組件應(yīng)該使用
<my-input></my-input>
載入,而不是 <myInput></myInput>
=> 因?yàn)檫@樣瀏覽器會(huì)解析為全小寫的變量名.(之前遇到過這個(gè)問題)