Vue筆記 - [元件]
阿新 • • 發佈:2020-08-06
元件
- 元件 (Component) 是 Vue.js 最強大的功能之一
- 元件可以擴充套件 HTML 元素,封裝可重用的代
元件註冊
全域性註冊
- Vue.component('元件名稱', { }) 第1個引數是標籤名稱,第2個引數是一個選項物件
- 全域性元件註冊後,任何vue例項都可以用
元件基礎用
<div id="example"> <!-- 2、 元件使用 元件名稱 是以HTML標籤的形式使用 --> <my-component></my-component> </div> <script> // 註冊元件 // 1、 my-component 就是元件中自定義的標籤名 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 建立根例項 new Vue({ el: '#example' }) </script>
元件注意事項
- 元件引數的data值必須是函式同時這個函式要求返回一個物件
- 元件模板必須是單個根元素
- 元件模板的內容可以是模板字串
<div id="app"> <!-- 4、 元件可以重複使用多次 因為data中返回的是一個物件所以每個元件中的資料是私有的 即每個例項可以維護一份被返回物件的獨立的拷貝 --> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> <!-- 8、必須使用短橫線的方式使用元件 --> <hello-world></hello-world> </div> <script type="text/javascript"> //5 如果使用駝峰式命名元件,那麼在使用元件的時候,只能在字串模板中用駝峰的方式使用元件, // 7、但是在普通的標籤模板中,必須使用短橫線的方式使用元件 Vue.component('HelloWorld', { data: function(){ return { msg: 'HelloWorld' } }, template: '<div>{{msg}}</div>' }); Vue.component('button-counter', { // 1、元件引數的data值必須是函式 // 同時這個函式要求返回一個物件 data: function(){ return { count: 0 } }, // 2、元件模板必須是單個根元素 // 3、元件模板的內容可以是模板字串 template: ` <div> <button @click="handle">點選了{{count}}次</button> <button>測試123</button> # 6 在字串模板中可以使用駝峰的方式使用元件 <HelloWorld></HelloWorld> </div> `, methods: { handle: function(){ this.count += 2; } } }) var vm = new Vue({ el: '#app', data: {} }); </script>
區域性註冊
- 只能在當前註冊它的vue例項中使用
<div id="app"> <my-component></my-component> </div> <script> // 定義元件的模板 var Child = { template: '<div>A custom component!</div>' } new Vue({ //區域性註冊元件 components: { // <my-component> 將只在父模板可用 一定要在例項上註冊了才能在html檔案中使用 'my-component': Child } }) </script>
組建的命名
- 短橫線方式
Vue.component('my-component',{/* ... */})
- 駝峰方式(
只能在字串模板中使用該種方式,普通標籤模板中使用短橫線方式
)
Vue.component('MyComponent',{/* ... */})
Vue除錯工具
Vue-Devtools --> Chrome外掛
Vue元件之間傳值
父元件向子元件傳值
-
父元件傳送的形式是以屬性的形
-
然後子元件用屬性
props
接收 -
在props中使用駝峰形式,模板中需要使用短橫線的形式字串形式的模板中沒有這個限制
<div id="app">
<div>{{pmsg}}</div>
<!--1、menu-item 在 APP中巢狀著 故 menu-item 為 子元件 -->
<!-- 給子元件傳入一個靜態的值 -->
<menu-item title='來自父元件的值'></menu-item>
<!-- 2、 需要動態的資料的時候 需要屬性繫結的形式設定 此時 ptitle 來自父元件data 中的資料 .
傳的值可以是數字、物件、陣列等等
-->
<menu-item :title='ptitle' content='hello' menu-title='html中用短橫線'></menu-item>
</div>
<script type="text/javascript">
Vue.component('menu-item', {
// 3、 子元件用屬性props接收父元件傳遞過來的資料
// Javascript中命名使用駝峰命名,html中用短橫線,字串模板中沒有限制
props: ['title', 'content','menuTitle'],
data: function() {
return {
msg: '子元件本身的資料'
}
},
template: '<div>{{msg + "----" + title + "-----" + content + " ---- " + menuTitle}}</div>'
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父元件中內容',
ptitle: '動態繫結屬性'
}
});
</script>
子元件向父元件傳值
- 子元件用
$emit()
觸發事件 $emit()
第一個引數為 自定義的事件名稱 第二個引數為需要傳遞的資料- 父元件用v-on 監聽子元件的事件
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<!-- 2 父元件用v-on 監聽子元件的事件
這裡 enlarge-text 是從 $emit 中的第一個引數對應 handle 為對應的事件處理函式
-->
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
子元件向父元件傳值-攜帶引數
*/
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
### 1、子元件用$emit()觸發事件
### 第一個引數為 自定義的事件名稱 第二個引數為需要傳遞的資料
<button @click='$emit("enlarge-text", 5)'>擴大父元件中字型大小</button>
<button @click='$emit("enlarge-text", 10)'>擴大父元件中字型大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父元件中內容',
parr: ['apple','orange','banana'],
fontSize: 10
},
methods: {
handle: function(val){
// 擴大字型大小
this.fontSize += val;
}
}
});
</script>
兄弟之間的傳遞
- 兄弟之間傳遞資料需要藉助於事件中心,通過事件中心傳遞資料
- 提供事件中心 var hub = new Vue()
- 傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料)
- 接收資料方,通過mounted(){} 鉤子中 觸發hub.$on()方法名
- 銷燬事件 通過hub.$off()方法名銷燬之後無法進行傳遞資料
<div id="app">
<div>父元件</div>
<div>
<button @click='handle'>銷燬事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
兄弟元件之間資料傳遞
*/
//1、 提供事件中心
var hub = new Vue();
Vue.component('test-tom', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>點選</button>
</div>
</div>
`,
methods: {
handle: function(){
//2、傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料) 觸發兄弟元件的事件
hub.$emit('jerry-event', 2);
}
},
mounted: function() {
// 3、接收資料方,通過mounted(){} 鉤子中 觸發hub.$on(方法名
hub.$on('tom-event', (val) => {
this.num += val;
});
}
});
Vue.component('test-jerry', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>點選</button>
</div>
</div>
`,
methods: {
handle: function(){
//2、傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料) 觸發兄弟元件的事件
hub.$emit('tom-event', 1);
}
},
mounted: function() {
// 3、接收資料方,通過mounted(){} 鉤子中 觸發hub.$on()方法名
hub.$on('jerry-event', (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: '#app',
data: {
},
methods: {
handle: function(){
//4、銷燬事件 通過hub.$off()方法名銷燬之後無法進行傳遞資料
hub.$off('tom-event');
hub.$off('jerry-event');
}
}
});
</script>
元件插槽
- 元件的最大特性就是複用性,而用好插槽能大大提高元件的可複用能力
匿名插槽
<div id="app">
<!-- 這裡的所有元件標籤中巢狀的內容會替換掉slot 如果不傳值 則使用 slot 中的預設值 -->
<alert-box>有bug發生</alert-box>
<alert-box>有一個警告</alert-box>
<alert-box></alert-box>
</div>
<script type="text/javascript">
/*
元件插槽:父元件向子元件傳遞內容
*/
Vue.component('alert-box', {
template: `
<div>
<strong>ERROR:</strong>
# 當元件渲染的時候,這個 <slot> 元素將會被替換為“元件標籤中巢狀的內容”。
# 插槽內可以包含任何模板程式碼,包括 HTML
<slot>預設內容</slot>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
具名插槽
- 具有名字的插槽
- 使用
中的 "name" 屬性繫結元素
<div id="app">
<base-layout>
<!-- 2、 通過slot屬性來指定, 這個slot的值必須和下面slot元件得name值對應上
如果沒有匹配到 則放到匿名的插槽中 -->
<p slot='header'>標題資訊</p>
<p>主要內容1</p>
<p>主要內容2</p>
<p slot='footer'>底部資訊資訊</p>
</base-layout>
<base-layout>
<!-- 注意點:template臨時的包裹標籤最終不會渲染到頁面上 -->
<template slot='header'>
<p>標題資訊1</p>
<p>標題資訊2</p>
</template>
<p>主要內容1</p>
<p>主要內容2</p>
<template slot='footer'>
<p>底部資訊資訊1</p>
<p>底部資訊資訊2</p>
</template>
</base-layout>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
具名插槽
*/
Vue.component('base-layout', {
template: `
<div>
<header>
### 1、 使用 <slot> 中的 "name" 屬性繫結元素 指定當前插槽的名字
<slot name='header'></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
### 注意點:
### 具名插槽的渲染順序,完全取決於模板,而不是取決於父元件中元素的順序
<slot name='footer'></slot>
</footer>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
作用域插槽
- 父元件對子元件加工處理
- 既可以複用子元件的slot,又可以使slot內容不一致
<div id="app">
<!--
1、當我們希望li 的樣式由外部使用元件的地方定義,因為可能有多種地方要使用該元件,
但樣式希望不一樣 這個時候我們需要使用作用域插槽
-->
<fruit-list :list='list'>
<!-- 2、 父元件中使用了<template>元素,而且包含scope="slotProps",
slotProps在這裡只是臨時變數
--->
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">
{{slotProps.info.name}}
</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
作用域插槽
*/
Vue.component('fruit-list', {
props: ['list'],
template: `
<div>
<li :key='item.id' v-for='item in list'>
### 3、 在子元件模板中,<slot>元素上有一個類似props傳遞資料給元件的寫法msg="xxx",
### 插槽可以提供一個預設內容,如果如果父元件沒有為這個插槽提供了內容,會顯示預設的內容。
如果父元件為這個插槽提供了內容,則預設的內容會被替換掉
<slot :info='item'>{{item.name}}</slot>
</li>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple'
},{
id: 2,
name: 'orange'
},{
id: 3,
name: 'banana'
}]
}
});
</script>
</body>
</html>
v-slot
v-slot的出現是為了代替原有的slot和slot-scope,簡化了一些複雜的語法。
一句話概括就是v-slot:
後邊是插槽名稱,=
後邊是元件內部繫結作用域值的對映。
<div id="app">
<h1>{{msg}}</h1>
<fruits-com :list="list" >
<template v-slot:fruit="item">
<strong v-if="item.info.id === currentId" class="current">{{item.info.name}}</strong>
<span v-else>{{item.info.name}}</span>
</template>
</fruits-com>
</div>
<script>
Vue.component("fruits-com",{
props:['list'],
template:`
<div>
<li :key="item.id" v-for="item in list">
<slot name="fruit" :info="item">{{item.name}}</slot>
</li>
</div>
`
})
let app = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
currentId: 2,
list:[
{
id:1,
name:'apple'
},
{
id:2,
name:'lemon'
},
{
id:3,
name:'banana'
}
]
}
})
</script>
<style scope>
.current{
color: orange;
}
</style>
小案例(購物車)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.container {
}
.container .cart {
width: 300px;
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;
left: 55px;
font-size: 16px;
}
.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration: none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>
<script>
let CartTitle = {
props: ['uname'],
template: `
<div class="title">{{uname}}的商品</div>
`
}
let CartList = {
props: ['list'],
template: `
<div>
<div class="item" :key="item.id" v-for="item in list">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="" @click.prevent="sub(item.id)">-</a>
<input type="text" class="num" :value="item.num" @blur="changeNum(item.id,$event)"/>
<a href="" @click.prevent="add(item.id)">+</a>
</div>
<div class="del" @click="del(item.id)">×</div>
</div>
</div>
`,
methods: {
del: function (id) {
this.$emit("cart-del", id);
},
changeNum: function (id, event) {
this.$emit("change-num", {
id: id,
num: event.target.value,
type: 'change'
})
},
sub: function (id) {
this.$emit("change-num", {
id: id,
type: 'sub'
})
},
add: function (id) {
this.$emit("change-num", {
id: id,
type: 'add'
})
}
}
}
let CartTotal = {
props: ['list'],
template: `
<div class="total">
<span>總價:{{total}}</span>
<button>結算</button>
</div>
`,
computed: {
total: function () {
let sum = 0;
this.list.forEach(item => {
sum += item.price * item.num;
});
return sum;
}
}
}
Vue.component('my-cart', {
data: function () {
return {
uname: '張三',
list: [{
id: 1,
name: 'TCL彩電',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '機頂盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海爾冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手機',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV電視',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
template: `
<div class="cart">
<cart-title :uname="uname"></cart-title>
<cart-list :list="list" @cart-del="delCart($event)" @change-num="changeNum($event)"></cart-list>
<cart-total :list="list"></cart-total>
</div>
`,
components: {
'cart-title': CartTitle,
'cart-list': CartList,
'cart-total': CartTotal
},
methods: {
delCart: function (id) {
let index = this.list.findIndex(item => {
return item.id === id;
});
this.list.splice(index, 1);
},
changeNum: function (val) {
if (val.type === 'change') {
this.list.some(item => {
if (item.id === val.id) {
item.num = val.num;
return true;
}
});
}else if(val.type==='sub'){
this.list.some(item => {
if (item.id === val.id) {
item.num -= 1;
return true;
}
});
}else if(val.type==='add'){
this.list.some(item => {
if (item.id === val.id) {
item.num += 1;
return true;
}
});
}
}
}
})
let app = new Vue({
el: '#app',
data: {}
})
</script>
</body>