將一個複雜的問題,拆分成很多歌可以處理的小問題,再將其放在整體當中。
元件化也是類似的思想:
元件化是Vue.js中的重要思想
註冊元件的基本步驟
註冊元件步驟解析
1.Vue.extend():
2.Vue.component():
3.元件必須掛載在某個Vue範例下,否則它不會生效。(見下頁)
<my-cpn></my-cpn>
全域性元件和區域性元件
當我們通過呼叫Vue.component()註冊元件時,元件的註冊是全域性的。
如果我們註冊的元件是掛載在某個vue的範例中, 那麼就是一個區域性元件。
子元件放在父元件下進行註冊,Child元件是在Parent元件中註冊的,它只能在Parent元件中使用,確切地說:子元件只能在父元件的template中使用。
父子元件錯誤用法:以子標籤的形式在Vue範例中使用。
<div id="app">
<parent-component>
<child-component></child-component>
</parent-component>
</div>
有以下四點原因
<child-cpn></child-cpn>
是只能在父元件中被識別的(在爺爺的元件是識別不了)。<child-cpn></child-cpn>
是會被瀏覽器忽略的。在上面註冊元件的方式,可能會有些繁瑣。
之前方式:
<body>
<div id="app">
<!-- 3.使用元件-->
<cpn1></cpn1>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.建立元件構造器
const cpn1 = Vue.extend({
template: `<div><h2>我是標題2</h2></div>`
});
// 2.註冊元件
Vue.component('cpn1', cpn1)
</script>
<script>
const app = new Vue({
el: '#app', // 用於掛載要管理的元素
})
</script>
</body>
語法糖註冊全域性元件和區域性元件:
全域性元件
Vue.component()的第1個引數是標籤名稱,第2個引數是一個選項物件,使用選項物件的template屬性定義元件模板。
使用這種方式,Vue在背後會自動地呼叫Vue.extend()。
<div id="app">
<!-- 3.使用元件-->
<cpn1></cpn1>
</div>
<script src="../js/vue.js"></script>
<script>
// 一起呼叫 1.建立元件構造器 2.註冊元件
Vue.component('cpn1', {
template: `<div><h2>我是標題2</h2></div>`
})
</script>
<script>
const app = new Vue({
el: '#app', // 用於掛載要管理的元素
})
</script>
區域性元件
在選項物件的components屬性中實現區域性註冊:
<div id="app">
<!-- 3.使用元件-->
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app', // 用於掛載要管理的元素
components: {
'cpn2': {
template: `<h2>我是區域性元件</h2>`
}
}
})
</script>
如果我們能將其中的HTML分離出來寫,然後掛載到對應的元件上,必然結構會變得非常清晰。
Vue提供了兩種方案來定義HTML模組內容:
以下做法是錯誤的:元件內部是不能存取Vue範例的。
元件中的data必須是函數
傳入Vue構造器的多數選項也可以用在 Vue.extend() 或Vue.component()中,不過有兩個特例: data 和el。
Vue.js規定:在定義元件的選項時,data和el選項必須使用函數。
在開發中,往往一些資料確實需要從上層傳遞到下層:
如何進行父子元件間的通訊呢?Vue官方提到
props基本用法
在元件中,使用選項props來宣告需要從父級接收到的資料。
props的值有兩種方式:
父傳子
在下面的程式碼中,我直接將Vue範例當做父元件,並且其中包含子元件來簡化程式碼。
真實的開發中,Vue範例和子元件的通訊和父元件和子元件的通訊過程是一樣的。
props資料驗證
在前面,我們的props選項是使用一個陣列。
除了陣列之外,我們也可以使用物件,當需要對props進行型別等驗證時,就需要物件寫法了。
驗證都支援哪些資料型別呢?
String、Number、Boolean、Array、Object、Date、Function、Symbol
props基礎範例
下面的程式碼定義了一個子元件my-component,在Vue範例中定義了data選項。
var vm = new Vue({
el: '#app',
data: {
name: 'keepfool',
age: 28
},
components: {
'my-component': {
template: '#myComponent',
props: ['myName', 'myAge']
}
}
})
為了便於理解,你可以將這個Vue範例看作my-component的父元件。
如果我們想使用父元件的資料,則必須先在子元件中定義props屬性,也就是props: ['myName', 'myAge']
這行程式碼。
定義子元件的HTML模板:
<template id="myComponent">
<table>
<tr>
<th colspan="2">
子元件資料
</th>
</tr>
<tr>
<td>my name</td>
<td>{{ myName }}</td>
</tr>
<tr>
<td>my age</td>
<td>{{ myAge }}</td>
</tr>
</table>
</template>
將父元件資料通過已定義好的props屬性傳遞給子元件:
<div id="app">
<my-component v-bind:my-name="name" v-bind:my-age="age"></my-component>
</div>
注意:在子元件中定義prop時,使用了camelCase命名法。由於HTML特性不區分大小寫,camelCase的prop用於特性時,需要轉為 kebab-case(短橫線隔開)。例如,在prop中定義的myName,在用作特性時需要轉換為my-name。
<my-component v-bind:my-name="name"
父元件是如何將資料傳給子元件的呢?相信看了下面這圖,也許你就能很好地理解了。
在父元件中使用子元件時,通過以下語法將資料傳遞給子元件:
<child-component v-bind:子元件prop="父元件資料屬性"></child-component>
當我們有自定義建構函式時,驗證也支援自定義的型別
prop的繫結型別
單向繫結
既然父元件將資料傳遞給了子元件,那麼如果子元件修改了資料,對父元件是否會有所影響呢?
我們將子元件模板和頁面HTML稍作更改:
var vm = new Vue({
el: '#app',
data: {
name: 'keepfool',
age: 28
},
components: {
'my-component': {
template: '#myComponent',
props: ['myName', 'myAge']
}
}
})
<div id="app">
<table>
<tr>
<th colspan="3">父元件資料</td>
</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-component v-bind:my-name="name" v-bind:my-age="age"></my-component>
</div>
<template id="myComponent">
<table>
<tr>
<th colspan="3">子元件資料</td>
</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>
修改了父元件的資料,同時影響了子元件。
prop預設是單向繫結:當父元件的屬性變化時,將傳導給子元件,但是反過來不會。這是為了防止子元件無意修改了父元件的狀態
雙向繫結
可以使用.sync顯式地指定雙向繫結,這使得子元件的資料修改會回傳給父元件。
<my-component v-bind:my-name.sync="name" v-bind:my-age.sync="age"></my-component>
單次繫結
可以使用.once
顯式地指定單次繫結,單次繫結在建立之後不會同步之後的變化,這意味著即使父元件修改了資料,也不會傳導給子元件。
<my-component v-bind:my-name.once="name" v-bind:my-age.once="age"></my-component>
範例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="styles/demo.css" />
</head>
<body>
<div id="app">
<div id="searchBar">
Search <input type="text" v-model="searchQuery" />
</div>
<simple-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery">
</simple-grid>
</div>
<template id="grid-template">
<table>
<thead>
<tr>
<th v-for="col in columns">
{{ col | capitalize}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in data | filterBy filterKey">
<td v-for="col in columns">
{{entry[col]}}
</td>
</tr>
</tbody>
</table>
</template>
</body>
<script src="js/vue.js"></script>
<script>
Vue.component('simple-grid', {
template: '#grid-template',
props: {
data: Array,
columns: Array,
filterKey: String
}
})
var demo = new Vue({
el: '#app',
data: {
searchQuery: '',
gridColumns: ['name', 'age', 'sex'],
gridData: [{
name: 'Jack',
age: 30,
sex: 'Male'
}, {
name: 'Bill',
age: 26,
sex: 'Male'
}, {
name: 'Tracy',
age: 22,
sex: 'Female'
}, {
name: 'Chris',
age: 36,
sex: 'Male'
}]
}
})
</script>
</html>
1.prop驗證
props: {
data: Array,
columns: Array,
filterKey: String
}
這段程式碼錶示:父元件傳遞過來的data和columns必須是Array型別,filterKey必須是字串型別。
2.filterBy過濾器
可以根據指定的字串過濾資料
在子元件中定義props,可以讓父元件的資料傳遞下來,這就好比子元件告訴父元件:「嘿,老哥,我開通了一個驛站,你把東西放到驛站我就可以拿到了。」
今天我們將著重介紹slot和父子元件之間的存取和通訊,slot是一個非常有用的東西,它相當於一個內容插槽,它是我們重用元件的基礎。Vue的事件系統獨立於原生的DOM事件,它用於元件之間的通訊。
本文的主要內容如下:
<slot>
標籤作為內容插槽$children, $refs, $parent
實現父子元件之間的範例存取$dispatch
向父元件派發事件;在父元件中,使用$broadcast
向子元件傳播事件儘管使用元件就像使用一般的HTML元素一樣,但它畢竟不是標準的HTML元素,為了讓瀏覽器能夠識別它,元件會被解析為標準的HTML片段,然後將元件的標籤替換為該HTML片段。
<div id="app">
<my-component>
</my-component>
</div>
<template id="myComponent">
<div>
<h2>{{ msg }}</h2>
<button v-on:click="showMsg">Show Message</button>
</div>
</template>
<script src="js/vue.js"></script>
<script>
new Vue({
el: '#app',
components: {
'my-component': {
template: '#myComponent',
data: function() {
return {
msg: 'This is a component!'
}
},
methods: {
showMsg: function() {
alert(this.msg)
}
}
}
}
})
這段程式碼定義了一個my-component元件,<my-component><my-component>
不是標準的HTML元素,瀏覽器是不理解這個元素的。
那麼Vue是如何讓瀏覽器理解<my-component><my-component>
標籤的呢?
在建立一個Vue範例時,除了將它掛載到某個HTML元素下,還要編譯元件,將元件轉換為HTML片段。
除此之外,Vue範例還會識別其所掛載的元素下的<my-component>
標籤,然後將<my-component>
標籤替換為HTML片段。
元件在使用前,經過編譯已經被轉換為HTML片段了,元件是有一個作用域的,那麼元件的作用域是什麼呢?
你可以將它理解為元件模板包含的HTML片段,元件模板內容之外就不是元件的作用域了。
例如,my-component元件的作用域只是下面這個小片段。
父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯
通俗地講,在子元件中定義的資料,只能用在子元件的模板。在父元件中定義的資料,只能用在父元件的模板。如果父元件的資料要在子元件中使用,則需要子元件定義props。
為了讓元件可以組合,我們需要一種方式來混合父元件的內容與子元件自己的模板。這個處理稱為內容分發,Vue.js 實現了一個內容分發 API,使用特殊的 <slot>
元素作為原始內容的插槽。
<slot>
元素是一個內容插槽。
單個Slot
下面的程式碼在定義my-component元件的模板時,指定了一個<slot></slot>
元素。
<div id="app">
<my-component>
<h1>Hello Vue.js!</h1>
</my-component>
<my-component>
</my-component>
</div>
<template id="myComponent">
<div class="content">
<h2>This is a component!</h2>
<slot>如果沒有分發內容,則顯示slot中的內容</slot>
<p>Say something...</p>
</div>
</template>
<script src="js/vue.js"></script>
<script>
Vue.component('my-component', {
template: '#myComponent'
})
new Vue({
el: '#app'
})
</script>
第一個<my-component>
標籤有一段分發內容<h1>Hello Vue.js!</h1>
,渲染元件時顯示了這段內容。
第二個<my-component>
標籤則沒有,渲染元件時則顯示了slot標籤中的內容
上面這個範例是一個匿名slot,它只能表示一個插槽。如果需要多個內容插槽,則可以為slot元素指定name屬性。
多個slot一起使用時,會非常有用。例如,對話方塊是HTML常用的一種互動方式。
在不同的運用場景下,對話方塊的頭部、主體內容、底部可能是不一樣的。
這時,使用不同名稱的slot就能輕易解決這個問題了。
<template id="dialog-template">
<div class="dialogs">
<div class="dialog" v-bind:class="{ 'dialog-active': show }">
<div class="dialog-content">
<div class="close rotate">
<span class="iconfont icon-close" @click="close"></span>
</div>
<slot name="header"></slot>
<slot name="body"></slot>
<slot name="footer"></slot>
</div>
</div>
<div class="dialog-overlay"></div>
</div>
</template>
<script src="js/vue.js"></script>
<script>
Vue.component('modal-dialog', {
template: '#dialog-template',
props: ['show'],
methods: {
close: function() {
this.show = false
}
}
})
new Vue({
el: '#app',
data: {
show: false
},
methods: {
openDialog: function() {
this.show = true
},
closeDialog: function() {
this.show = false
}
}
})
</script>
在定義modal-dialog元件的template時,我們使用了3個slot,它們的name特性分別是header、body和footer。
在<modal-dialog>
標籤下,分別為三個元素指定slot特性:
<div id="app">
<modal-dialog v-bind:show.sync="show">
<header class="dialog-header" slot="header">
<h1 class="dialog-title">提示資訊</h1>
</header>
<div class="dialog-body" slot="body">
<p>你想在對話方塊中放什麼內容都可以!</p>
<p>你可以放一段文字,也可以放一些表單,或者是一些圖片。</p>
</div>
<footer class="dialog-footer" slot="footer">
<button class="btn" @click="closeDialog">關閉</button>
</footer>
</modal-dialog>
<button class="btn btn-open" @click="openDialog">開啟對話方塊</button>
</div>
對話方塊的標題內容、主體內容、底部內容,完全由我們自定義,而且這些內容就是一些簡單的HTML元素!
有時候我們需要父元件存取子元件,子元件存取父元件,或者是子元件存取根元件。
針對這幾種情況,Vue.js都提供了相應的API:
父元件存取子元件:使用$children或$refs
子元件存取父元件:使用$parent
子元件存取根元件:使用$root
下面這段程式碼定義了3個元件:父元件parent-component,兩個子元件child-component1和child-component2。
在父元件中,通過this.$children
可以存取子元件。
this.$children
是一個陣列,它包含所有子元件的範例。
<div id="app">
<parent-component></parent-component>
</div>
<template id="parent-component">
<child-component1></child-component1>
<child-component2></child-component2>
<button v-on:click="showChildComponentData">顯示子元件的資料</button>
</template>
<template id="child-component1">
<h2>This is child component 1</h2>
</template>
<template id="child-component2">
<h2>This is child component 2</h2>
</template>
<script src="js/vue.js"></script>
<script>
Vue.component('parent-component', {
template: '#parent-component',
components: {
'child-component1': {
template: '#child-component1',
data: function() {
return {
msg: 'child component 111111'
}
}
},
'child-component2': {
template: '#child-component2',
data: function() {
return {
msg: 'child component 222222'
}
}
}
},
methods: {
showChildComponentData: function() {
for (var i = 0; i < this.$children.length; i++) {
alert(this.$children[i].msg)
}
}
}
})
new Vue({
el: '#app'
})
</script>
元件個數較多時,我們難以記住各個元件的順序和位置,通過序號存取子元件不是很方便。
在子元件上使用v-ref指令,可以給子元件指定一個索引ID:
<template id="parent-component">
<child-component1 v-ref:cc1></child-component1>
<child-component2 v-ref:cc2></child-component2>
<button v-on:click="showChildComponentData">顯示子元件的資料</button>
</template>
在父元件中,則通過$refs.索引ID
存取子元件的範例:
showChildComponentData: function() {
alert(this.$refs.cc1.msg);
alert(this.$refs.cc2.msg);
}
下面這段程式碼定義了兩個元件:child-component和它的父元件parent-component。
在子元件中,通過this.$parent可以存取到父元件的範例。
<div id="app">
<parent-component></parent-component>
</div>
<template id="parent-component">
<child-component></child-component>
</template>
<template id="child-component">
<h2>This is a child component</h2>
<button v-on:click="showParentComponentData">顯示父元件的資料</button>
</template>
<script src="js/vue.js"></script>
<script>
Vue.component('parent-component', {
template: '#parent-component',
components: {
'child-component': {
template: '#child-component',
methods: {
showParentComponentData: function() {
alert(this.$parent.msg)
}
}
}
},
data: function() {
return {
msg: 'parent component message'
}
}
})
new Vue({
el: '#app'
})
</script>
注意:儘管可以存取父鏈上任意的範例,不過子元件應當避免直接依賴父元件的資料,儘量顯式地使用 props 傳遞資料。另外,在子元件中修改父元件的狀態是非常糟糕的做法,因為:
1.這讓父元件與子元件緊密地耦合;
2.只看父元件,很難理解父元件的狀態。因為它可能被任意子元件修改!理想情況下,只有元件自己能修改它的狀態。
說到底,元件的API主要來源於以下三部分: