Vue 組件系統 vue.js既然是框架,那就不能只是簡單的完成數據模板引擎的任務,它還提供了頁面佈局的功能。本文詳細介紹使用vue.js進行頁面佈局的強大工具,vue.js組件系統。 每一個新技術的誕生,都是為瞭解決特定的問題。組件的出現就是為瞭解決頁面佈局等等一系列問題。vue中的組件分為兩種, ...
vue.js既然是框架,那就不能只是簡單的完成數據模板引擎的任務,它還提供了頁面佈局的功能。本文詳細介紹使用vue.js進行頁面佈局的強大工具,vue.js組件系統。
每一個新技術的誕生,都是為瞭解決特定的問題。組件的出現就是為瞭解決頁面佈局等等一系列問題。vue中的組件分為兩種,全局組件和局部組件。
一、全局組件的註冊
通過Vue.component()創建一個全局組件之後,我們可以在一個通過 new Vue
創建的 Vue 根實例中,把這個組件作為自定義元素來使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--第二步,使用-->
<global_component></global_component>
</div>
<script>
// 第一步,註冊
Vue.component("global_component", {
template: `
<div>
<h2>Hello Vue</h2>
</div>
`
});
new Vue({
el: "#app",
});
</script>
</body>
</html>
組件的參數
因為組件是可復用的 Vue 實例,所以它們與 new Vue
接收相同的選項,例如 data
、computed
、watch
、methods
以及生命周期鉤子等。僅有的例外是像 el
這樣根實例特有的選項。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--第二步,使用-->
<global_component></global_component>
</div>
<script>
// 第一步,註冊
Vue.component("global_component", {
data: function () {
return {
count: 0
}
},
template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
});
new Vue({
el: "#app",
});
</script>
</body>
</html>
組件的復用
每個實例維護自己的一份獨立的數據。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--第二步,使用-->
<global_component></global_component>
<global_component></global_component>
<global_component></global_component>
</div>
<script>
// 第一步,註冊
Vue.component("global_component", {
data: function () {
return {
count: 0
}
},
template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
});
new Vue({
el: "#app",
});
</script>
</body>
</html>
註意當點擊按鈕時,每個組件都會各自獨立維護它的 count
。因為你每用一次組件,就會有一個它的新實例被創建。
Data必須是一個函數
data必須是一個函數,因此每個實例可以維護一份被返回對象的獨立的拷貝, 也可以寫成如下形式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--第二步,使用-->
<global_component></global_component>
<global_component></global_component>
<global_component></global_component>
</div>
<script>
// 第一步,註冊
Vue.component("global_component", {
data(){
return {
count: 0
}
},
template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
});
new Vue({
el: "#app",
});
</script>
</body>
</html>
二、局部組件的註冊
全局註冊往往是不夠理想的。比如,如果你使用一個像 webpack 這樣的構建系統,全局註冊所有的組件意味著即便你已經不再使用一個組件了,它仍然會被包含在你最終的構建結果中。這造成了用戶下載的 JavaScript 的無謂的增加。
全局組件始終是存在的,除非程式結束,如果組件越來越大,那麼程式所占用的空間和消耗的性能就會更大。
局部組件的第一種使用方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="component-demo">
<!--最後在根元素當中使用它-->
<!--第一種使用方式,會把當前div渲染進DOM-->
<my-header></my-header>
</div>
<script>
// 定義一個局部組件,其實就是一個變數,它是一個object類型
// 屬性與全局組件是一樣的
let Header = {
template: `
<button @click="count++">{{ count }}</button>
`,
data() {
return {
count: 0
}
}
};
new Vue({
el: "#component-demo",
// 第二部,需要在根實例當中使用它
components: {
'my-header': Header
}
});
</script>
</body>
</html>
局部組件的第二種使用方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="component-demo">
</div>
<script>
// 定義一個局部組件,其實就是一個變數,它是一個object類型
// 屬性與全局組件是一樣的
let Header = {
template: `
<button @click="count++">{{ count }}</button>
`,
data() {
return {
count: 0
}
}
};
new Vue({
el: "#component-demo",
// 第二種使用方式,不會將div渲染進DOM,以template為根元素
template: `<my-header></my-header>`,
// 第二步,需要在根實例當中使用它
components: {
'my-header': Header
}
});
</script>
</body>
</html>
對於 components
對象中的每個屬性來說,其屬性名就是自定義元素的名字,其屬性值就是這個組件的選項對象。
在局部組件中使用子組件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
<style>
body {
margin: 0;
}
.box {
width: 100%;
height: 50px;
background-color: #2aabd2;
}
</style>
</head>
<body>
<div id="component-demo">
</div>
<script>
// 定義一個局部組件,其實就是一個變數,它是一個object類型
// 這個對象的屬性與全局組件是一樣的(除el屬性外)
let Fcontent = {
template: `
<div>
<span>這是頭條</span>
</div>
`
};
let Header = {
template: `
<div v-bind:class='{box: isBox}'>
<button @click="count++">{{ count }}</button>
<first-content></first-content>
</div>
`,
data() {
return {
count: 0,
isBox: true
}
},
components: {
'first-content': Fcontent
}
};
new Vue({
el: "#component-demo",
// 第二種使用方式,不會將div渲染進DOM,以template為根元素
template: `<my-header></my-header>`,
// 第二步,需要在根實例當中使用它
components: {
'my-header': Header
}
});
</script>
</body>
</html>
三、父子組件的通信
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
<style>
body {
margin: 0;
}
.box {
width: 100%;
height: 50px;
background-color: #2aabd2;
}
</style>
</head>
<body>
<div id="component-demo">
</div>
<script>
// 定義一個局部組件,其實就是一個變數,它是一個object類型
// 屬性與全局組件是一樣的
let Fcontent = {
template: `
<div>
<span>這是頭條</span>
{{ fdata }}
</div>
`,
props: ['fdata']
};
let Header = {
template: `
<div v-bind:class='{box: isBox}'>
<button @click="count++">{{ count }}</button>
<first-content :fdata="fathData"></first-content>
</div>
`,
data() {
return {
count: 0,
isBox: true,
fathData: "我是你爸爸~~~"
}
},
components: {
'first-content': Fcontent
}
};
new Vue({
el: "#component-demo",
// 第二種使用方式,不會將div渲染進DOM,以template為根元素
template: `<my-header></my-header>`,
// 第二步,需要在根實例當中使用它
components: {
'my-header': Header
}
});
</script>
</body>
</html>
四、子父之間的通信
父組件在mounted的時候,監聽一個自定義事件。
給子組件綁定一個click事件之後,通過內建的方法$emit在父組件上觸發一個事件,這個事件就是父組件監聽的自定義事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
let myFooter = {
template: `
<div>
<h1>我是兒子</h1>
<button v-on:click="changeFatherSize">點擊修改爸爸的字體</button>
</div>
`,
methods: {
changeFatherSize: function () {
this.$emit('change-font', 1);
}
},
};
let myHeader = {
template: `
<div>
<my-footer v-on:change-font="changeSize"></my-footer>
<span :style="{ fontSize: fontSize + 'px'}">我是爸爸</span>
</div>
`,
data(){
return {
fontSize: 26,
}
},
methods: {
changeSize: function (value) {
console.log(value);
this.fontSize += value;
}
},
components: {
'my-footer': myFooter
}
};
let App = {
template: `
<div>
<my-header></my-header>
</div>
`,
components: {
'my-header': myHeader,
},
};
new Vue({
el: "#app",
template: `<App></App>`,
components: {
App
}
})
</script>
</body>
</html>
五、平行組件之間的通信
平行組件之間可以通過一個中間Vue實例來進行通信。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
<com-main></com-main>
</div>
<script>
let bus = new Vue();
let dogfa = {
template: `
<div>
<button @click="dogfaClick">點擊向djb道歉</button>
</div>
`,
methods: {
dogfaClick: function () {
bus.$emit("dogfa_apo", "原諒我吧,請你大保健~~~");
}
},
};
let djb = {
template: `
<div v-show="isShow">原諒你了~~~</div>
`,
mounted () {
bus.$on("dogfa_apo", (dogfasay)=> {
if ( dogfasay ) {
console.log("原諒你了~~~");
}
});
},
data () {
return {
isShow: false
};
}
};
let App = {
template: `
<div id="app">
<dogfa></dogfa>
<djb></djb>
</div>
`,
components: {
dogfa,
djb
}
};
new Vue({
el: "#app",
template: '<App></App>',
components: {
App
},
})
</script>
</body>
</html>
六、混入
混入可以提高代碼的重用性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
let mixins = {
methods: {
show: function (name) {
console.log(`${name} is here`);
},
hide: function (name) {
console.log(`${name} is here`);
}
}
};
let myAlex = {
template: `
<div>
<button @click="show('alex')">點我顯示alex</button>
<button @click="hide('alex')">點我隱藏alex</button>
</div>
`,
mixins: [mixins]
};
let myPeiQi = {
template: `
<div>
<button @click="show('peiqi')">點我顯示peiqi</button>
<button @click="hide('peiqi')">點我隱藏peiqi</button>
</div>
`,
mixins: [mixins],
};
let App = {
template: `
<div>
<my-alex></my-alex>
<my-peiqi></my-peiqi>
</div>
`,
components: {
'my-alex': myAlex,
'my-peiqi': myPeiQi,
},
};
new Vue({
el: "#app",
template: `<App></App>`,
components: {
App
}
})
</script>
</body>
</html>
七、插槽
有時候我們需要向組件傳遞一些數據,這時候可以使用插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.nav-link {
width: 100px;
height: 100px;
background-color: #2aabd2;
float: left;
margin-left: 5px;
text-align: center;
line-height: 100px;
}
</style>
<script src="../statics/vue.js"></script>
</head>
<body>
<div id="app01">
<com-content>登錄</com-content>
<com-content>註冊</com-content>
<com-content>最熱</com-content>
<com-content>段子</com-content>
<com-content>42區</com-content>
<com-content>圖片</com-content>
</div>
<script>
Vue.component('com-content', {
template: `
<div class="nav-link">
<slot></slot>
</div>
`
});
new Vue({
el: "#app01",
})
</script>
</body>
</html>
八、具名插槽
操作使用了組件的復用,如果我們在同一個組件內寫入不同的頁面呢?此時,我們需要多個插槽,並且給不同的內容命名。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.nav-link {
width: 100px;
height: 100px;
background-color: #2aabd2;
float: left;
margin-left: 5px;
text-align: center;
line-height: 100px;
}
</style>
<script src="../statics/vue.js"></script>
</head>
<body>
<div id="app01">
<base-layout>
<template slot="header">
<h1>這是標題欄</h1>
</template>
<template>
<h2>這是內容</h2>
</template>
<template slot="footer">
<h3>這是頁腳</h3>
</template>
</base-layout>
</div>
<script>
let baseLayout = {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main><slot></slot></main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
};
new Vue({
el: "#app01",
components: {
"base-layout": baseLayout
}
})
</script>
</body>
</html>
我們還是可以保留一個未命名插槽,這個插槽是預設插槽,也就是說它會作為所有未匹配到插槽的內容的統一齣口。
九、在組件上使用v-model
自定義事件也可以用於創建支持 v-model
的自定義輸入組件。記住:
`<input v-model="searchText">`
等價於:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
當用在組件上時,v-model
則會這樣:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
為了讓它正常工作,這個組件內的 <input>
必須:
將其 value 特性綁定到一個名叫 value 的 prop 上
在其 input 事件被觸發時,將新的值通過自定義的 input 事件拋出
寫成代碼之後是這樣的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
現在 v-model
就應該可以在這個組件上完美地工作起來了:
<custom-input v-model="searchText"></custom-input>
如下是在組件中使用v-model示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../statics/vue.min.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
let Model = {
template: `
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
/>
<h1>{{ value }}</h1>
`,
props: ['value']
};
let App = {
template: `
<div>
<custom-input v-model="searchText"></custom-input>
`,
components: {
'custom-input': Model,
},
data(){
return {
searchText: "",
}
}
};
new Vue({
el: "#app",
template: `<App></App>`,
components: {
App,
}
})
</script>
</body>
</html>
十、使用組件的註意事項
註意事項一:單個根元素
當構建一個內容頁面的組件時,我們的組件可能包含多個HTML標簽。
<h1>Hello World</h1>
<h2>Hello Vue</h2>
然而如果你在模板中嘗試這樣寫,Vue 會顯示一個錯誤,並解釋道 every component must have a single root element (每個組件必須只有一個根元素)。你可以將模板的內容包裹在一個父元素內,來修複這個問題,例如:
<div>
<h1>Hello World</h1>
<h2>Hello Vue</h2>
</div>
註意事項二:解析特殊HTML元素
有些 HTML 元素,諸如 <ul>
、<ol>
、<table>
和 <select>
,對於哪些元素可以出現在其內部是有嚴格限制的。而有些元素,諸如 <li>
、<tr>
和 <option>
,只能出現在其它某些特定的元素內部。
這會導致我們使用這些有約束條件的元素時遇到一些問題。例如:
<table>
<blog-post-row></blog-post-row>
</table>
這個自定義組件 <blog-post-row>
會被作為無效的內容提升到外部,並導致最終渲染結果出錯。幸好這個特殊的 is
特性給了我們一個變通的辦法:
<table>
<tr is="blog-post-row"></tr>
</table>
需要註意的是如果我們從以下來源使用模板的話,這條限制是不存在的:
字元串 (例如:template: '...')
單文件組件 (.vue)
<script type="text/x-template">