使用組件的細節點
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
<script>
Vue.component('row', {
template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
我們發現 tr 不在 tbody 裏面,原因是 HTML5 規定 table 下面必須是 tbody,tbody 下面必須是 tr。
所以我們需要在 tbody 下面寫 tr,但是我們可以通過 is
屬性來指明模版。
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
<script>
Vue.component('row', {
template: '<tr><td>This is a row</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component('row', {
data() {
return {
content: 'This is a row'
}
},
template: '<tr><td>{{content}}</td></tr>'
})
var vm = new Vue({
el: '#app'
})
</script>
在子組件中定義 data 時,data 必須是一個函數,不能是一個對象。
ref
引用
我希望 div 被點擊的時候,把 div 的內容打印出來。
<div id="app">
<div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
console.log(this.$refs.hello.innerHTML);
}
}
})
</script>
$refs
指所有引用,.hello
就是 ref=“hello” 這個引用。
$ref 在組件中的使用:
「 計數器 」
<div id="app">
<counter></counter>
</div>
<script>
Vue.component('counter', {
data() {
return {
number: 0
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
}
}
})
var vm = new Vue({
el: '#app'
})
</script>
「 counter 求和 」
<body>
<div id="app">
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<div>total: {{total}}</div>
</div>
<script>
Vue.component('counter', {
data() {
return {
number: 0
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
this.$emit('change');
}
}
})
var vm = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleChange() {
this.total = this.$refs.one.number + this.$refs.two.number;
}
}
})
</script>
</body>
父子組件的數據傳遞
父組件通過屬性的形式向子組件傳遞數據。
子組件通過 props:[]
接收數據。
<div id="app">
<counter :count="0"></counter> <!-- 通過:count屬性傳值 -->
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ['count'],
template: '<div>{{count}}</div>'
}
var vm = new Vue({
el: '#app',
components: {
counter: counter
}
})
</script>
Vue 單向數據流
父組件可以向子組件通過屬性的形式傳遞參數,傳遞的數據可以隨便進行修改。
但是子組件只能使用父組件傳遞過來的數據,不能修改父組件傳遞過來的數據。
一個父組件可以傳值給多個子組件。一旦子組件接收的是引用形式的數據時,在子組件中改變了數據內容,會影響其它的子組件。
子組件使用父組件傳遞過來的數據時,不能修改。這個時候可以複製一份:(在 data 裏定義一個 number,把父組件的值賦值給 number)
<div id="app">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ['count'],
data() {
return {
number: this.count
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
}
}
}
var vm = new Vue({
el: '#app',
components: {
counter: counter
}
})
</script>
子組件通過事件的形式向父組件傳值。子組件被點擊的時候,可以通過 this.$emit('change')
觸發一個 change 事件。
<div id="app">
<counter :count="0" @change="handleIncrease"></counter>
<!-- @監聽事件,名字可以隨便寫(比如可以寫成 @inc),和下面 this.$emit 裏的對應即可。 -->
<counter :count="1" @change="handleIncrease"></counter>
<div>total: {{total}}</div>
</div>
<script>
var counter = {
props: ['count'],
data() {
return {
number: this.count
}
},
template: '<div @click="handleClick">{{number}}</div>',
methods: {
handleClick() {
this.number++;
this.$emit('change', 1);
}
}
}
var vm = new Vue({
el: '#app',
data: {
total: 1
},
components: {
counter: counter
},
methods: {
handleIncrease(step) {
this.total += step;
}
}
})
</script>
this.$emit('change', 1);
的第二個參數實際上和上面那句this.number++;
相對應,代表增長的步長。比如,上面寫成this.number=this.number+2;
則可以寫成this.$emit('change', 2);
用以告訴父組件增加了多少。
組件參數校驗與非 props 特性
組件參數校驗
我們都知道父組件通過屬性(content="hello world"
)向子組件傳遞參數,那組件的參數校驗是什麼?
父組件向子組件傳遞了一些內容,子組件有權對這些內容做一個約束。這些約束就可以叫做參數的校驗。
比如:需求是傳遞過來的 content 必須是一個字符串。
<div id="app">
<child :content="{}"></child> <!-- :content 有:時,後面跟的是js變量,沒有:時,後面的值是字符串-->
</div>
<script>
Vue.component('child', {
props: {
content: String
},
template: '<div>{{content}}</div>'
})
var vm = new Vue({
el: '#app',
})
</script>
如果 content 不是一個字符串(現在是{},一個對象),控制檯就會報錯。
參數校驗可以是一個數組(有多個條件):
content: [Number, String]
也可以是一個對象:
<child content="hello"></child>
content: {
type: String, // 類型
required: true, // 是否必須存在
default: 'default value' // 默認值
validator(value) { // 自定義校驗器
return (value.length > 5)
}
}
非 props 特性
props 特性指:當父組件使用子組件時,通過屬性向子組件傳值的時候,恰好子組件中聲明瞭對父組件傳遞過來的屬性的接收(props: )。父子組件有一個對應關係。
父組件傳,子組件接。
非 props 特性,子組件中沒有聲明。沒有接收父組件傳的值。沒辦法使用。
父組件傳,子組件不接。
聲明瞭一個非 props 特性的時候,其屬性會展示在子組件最外層的 dom 標籤的 html 屬性裏面。
給組件綁定原生事件
<div id="app">
<child @click="handleClick"></child> <!-- 這裏綁定的事件是監聽的自定義事件(目前無效)-->
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>', // 這裏綁定的事件是監聽的原生事件
methods: {
handleChildClick() {
console.log("handleChildClick");
}
}
})
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
this.$emit('click')
console.log("handleClick"); // (目前無效)
}
}
})
</script>
監聽的自定義事件如何被觸發?子組件想觸發父組件的事件的監聽,需要調用 this.$emit()
方法。
<div id="app">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: '<div @click="handleChildClick">Child</div>',
methods: {
handleChildClick() {
this.$emit('click');
// console.log("handleChildClick");
}
}
})
var vm = new Vue({
el: '#app',
methods: {
handleClick() {
console.log("handleClick");
}
}
})
</script>
還有什麼更簡單的方法嗎?
<child @click.native="handleClick"></child>
可以在事件綁定的後面加上 .native
修飾符,監聽原生事件。
非父子組件間的傳值
利用總線機制解決非父子組件間的傳值問題。
( Bus / 總線 / 發佈訂閱模式 / 觀察者模式 )
比如:我想要點擊上面的 div 時,把其內容改成 daze;點擊下面的 div 時,把其內容改成 bule。
<div id="app">
<child content="bule"></child>
<child content="daze"></child>
</div>
<script>
Vue.prototype.bus = new Vue() //bus總線
Vue.component('child', {
data() {
return {
selfContent: this.content
}
},
props: {
content: String
},
template: '<div @click="handleClick">{{selfContent}}</div>',
methods: {
handleClick() {
this.bus.$emit('change', this.selfContent);
}
},
mounted() {
var this_ = this;
this.bus.$on('change', function (msg) {
this_.selfContent = msg;
})
}
})
var vm = new Vue({
el: '#app'
})
</script>
在 Vue 中使用插槽
<div id="app">
<child content='<p>bule_daze</p>'></child>
</div>
<script>
Vue.component('child', {
props: ['content'],
template: `<div>
<p>hello</p>
<div v-html="this.content">{{content}}</div>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
如果我不想要 p 標籤外面顯示 div 標籤,應該如何去做?
template 佔位符在這裏是不好用的。
而通過 content 傳值,想直接使用 content 裏面的 p 標籤是有問題的,必須在外面包裹一個 div (<div v-html="this.content">
);且當傳遞的內容很多時,代碼不利於閱讀。
當子組件有一部分內容是根據父組件傳遞過來的 DOM 進行顯示時,我們可以利用插槽 slot。
<div id="app">
<child>
<p>bule_daze</p>
</child>
</div>
<script>
Vue.component('child', {
props: ['content'],
template: `<div>
<p>hello</p>
<slot></slot>
</div>`,
})
var vm = new Vue({
el: '#app'
})
</script>
默認值
slot 插槽還可以定義默認值:<slot>默認內容</slot>
(當父組件使用子組件時不傳遞插槽的內容時,纔會顯示出默認內容。<child></child>
)
具名插槽
如果只寫成 <slot></slot>
,代表插槽中的所有內容。如果我希望 slot 的內容分別對應,可以利用具名插槽,即給插槽取個名字。
- 父組件中
slot="header"
- 子組件中
<slot name="header"></slot>
<div id="app">
<my-content>
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</my-content>
</div>
<script>
Vue.component('my-content', {
template: `<div>
<slot name="header"></slot>
<div class="content">content</div>
<slot name="footer"></slot>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
具名插槽也可設置默認值。
作用域插槽
<div id="app">
<child></child>
</div>
<script>
Vue.component('child', {
data() {
return {
list: [1, 2, 3, 4]
}
},
template: `<div>
<ul>
<li v-for="item of list">
{{item}}
</li>
</ul>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
現在有一個需求,child 組件有可能在很多的地方被調用。我們希望在不同的地方調用 child 組件的時候,列表怎樣循環列表的樣式不是由 child 組件控制的,而是外部告訴我們的。(即 template 裏面需要把 li 去掉)
需要用到作用域插槽:
<div id="app">
<child>
<template slot-scope="props">
<li>{{props.item}}-hello</li>
</template>
</child>
</div>
<script>
Vue.component('child', {
data() {
return {
list: [1, 2, 3, 4]
}
},
template: `<div>
<ul>
<slot v-for="item of list" :item=item ></slot>
</ul>
</div>`
})
var vm = new Vue({
el: '#app'
})
</script>
父組件調用子組件的時候,給子組件傳了一個插槽,這個插槽叫做作用域插槽。作用域插槽必須是一個以 <template></template>
開頭和結尾的內容;且這個插槽要聲明其要接收的數據都放在哪(slot-scope="props"
都放在 props 裏面);還需要告訴子組件一個模版的信息,接到 props 應該如何展示(<li>{{props.item}}-hello</li>
)。
使用情境
當子組件做循環時,或者某一部分的 DOM 結構應該由外部傳遞進來的時候,使用作用域插槽。
使用作用域插槽時,子組件可以向父組件的插槽裏面傳數據。父組件如果想要接收這個數據,必須在外層使用 template,同時通過 slot-scope 對應的屬性的名字來接收傳遞過來的所有的數據。
動態組件與 v-once 指令
<div id="app">
<child-one></child-one>
<child-two></child-two>
<button>change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app'
})
</script>
現在想實現當點擊 change 按鈕的時候,一會 child-one 進行顯示,一會 child-two 進行顯示:
<div id="app">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
type: 'child-one'
},
methods: {
handleBtnClick() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
動態組件
可以利用動態組件的方式來實現上述需求。
<div id="app">
<component :is="type"></component>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div>child-one</div>'
})
Vue.component('child-two', {
template: '<div>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
type: 'child-one'
},
methods: {
handleBtnClick() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
<component></component>
是 Vue 裏面自帶的一個標籤,指的就是動態組件。動態組件的意思就是根據屬性is
裏面數據的變化自動加載不同的組件。
v-once
上面的動態組件在每次點擊按鈕切換的時候,Vue 底層會幫我們進行判斷,第一個組件不用了,就會銷燬第一個組件,創建第二個組件;當第二個組件不用了的時候,就會銷燬第二個組件,創建第一個組件。
這種操作會耗費一定的性能。如果組件的內容每一次都一樣,我們可以使用 v-once
指令:
Vue.component('child-one', {
template: '<div v-once>child-one</div>'
})
Vue.component('child-two', {
template: '<div v-once>child-two</div>'
})
第一次被渲染的時候,會保存至內存中;當再次使用的時候不需要重新創建,而是直接從內存中拿。
v-once 可以提高靜態內容的展示效率。