一、Vue組件(.vue文件) 組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現為用 is 特性進行了擴展的原生 HTML 元素。 所 ...
一、Vue組件(.vue文件)
組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現為用 is 特性進行了擴展的原生 HTML 元素。
所有的 Vue 組件同時也都是 Vue 的實例,所以可接受相同的選項對象 (除了一些根級特有的選項) 並提供相同的生命周期鉤子。
說白了,就是HTML、CSS、JS行為的一個封裝。
Vue中組件的實例化是對程式員不透明的,組件必須在components中進行“註冊”才能使用。
Vue中用K:V對的形式定義類,讓你自由的註冊名字,Vue官方推薦使用含有短橫的名字來表示自定義組件。
翻譯.vue文件需要安裝vue-loader依賴:
https://vue-loader.vuejs.org/zh-cn/
https://vue-loader-v14.vuejs.org/zh-cn/configurations/pre-processors.html
安裝其他4個開發依賴:
npm install --save-dev css-loader npm install --save-dev vue-loader npm install --save-dev vue-style-loader npm install --save-dev vue-template-compiler
修改webpack的配置(關於vue-loader的配置)官網找:
https://vue-loader-v14.vuejs.org/zh-cn/configurations/pre-processors.html
1.1組件的寫法1
.vue文件的結構:
<template></template> html結構 <script></script> js程式 <style></style> 樣式表
App.vue父組件:

<template> <h1>我是App父組件{{a}}</h1> </template> <script> export default { data(){ return { a:100 } } } </script> <style></style>示例代碼
有了App.vue組件就可以在main.js中通過import引入,並註冊:
import Vue from 'vue'; import App from './App.vue'; new Vue({ el : "#app", data : { }, components : { App } })
在index.html頁面中使用即可:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> </body> <script type="text/javascript" src="dist/all.js"></script> </html>
此時看見App父組件的內容了
1.2組件的寫法2
使用vue2新增的render()函數
index.html頁面中就不用放在<App></App>自定義標簽了,也不需要註冊了。
main.js
import Vue from 'vue'; import App from './App.vue'; new Vue({ el : "#app", render : (h)=> h(App) })
Vue中不允許出現片段標簽,必須用一個標簽包裹所有
<template> <div> <h1>我是app組件{{a}}</h1> <h1>我是app組件{{a}}</h1> </div> </template>
錯誤寫法: <template> <h1>我是app組件{{a}}</h1> <h1>我是app組件{{a}}</h1> </template>
如果有兩個組件就需要有一個components的文件夾 裡面可以放.vue組件
mian.vue
<style> </style> <template> <div> <h1>我是mian組件{{a}}</h1> </div> </template> <script> export default{ data(){ return{ a : 100 } } } </script>
組件在App.vue裡面使用
<style> </style> <template> <div> <vue-main></vue-main> <VueMain></VueMain> </div> </template> <script> import VueMain from "./components/main.vue" export default { data(){ return { a: 100 } }, components:{ VueMain } } </script>
使用的時候以下兩種方法:
<VueMain></VueMain>
等價於:
<vue-main></vue-main>
1.3關於data
在前面看到,在new Vue()的時候,創建或註冊模板時,傳入一個data屬性作為用來綁定的數據。是可以給data直接賦值為一個對象的。但是在組件中,data必須是一個函數,而不能直接把一個對象賦值給它。
第1種,在main.js主入口中的寫法:
new Vue({ el:'#app', data:{ } })
第2種,在組件中data選項必須是一個函數:
new Vue({ el:'#app', data(){ return { //返回一個唯一的對象,不要和其他組件共用一個對象進行返回 } } })
【區別】:
1)在簡單的Vue實例中,沒什麼區別,因為你new出的對象不會被覆用。
new Vue({...})
2)但在組件中,因為可能在多處調用同一組件,所以為了不讓多處的組件共用同一data對象,只能返回函數。
二、案例
2.1調色板

<template> <div> <div class="box" :style="{background:`rgb(${r},${g},${b})`}"></div> <p> <input type="range" min="0" max="255" v-model="r"> <input type="number" min="0" max="255" v-model="r"> </p> <p> <input type="range" min="0" max="255" v-model="g"> <input type="number" min="0" max="255" v-model="g"> </p> <p> <input type="range" min="0" max="255" v-model="b"> <input type="number" min="0" max="255" v-model="b"> </p> </div> </template> <script> export default { data(){ return { r : 100, g : 100, b : 100 } } } </script> <style> .box{ width: 200px; height: 200px; } </style>示例代碼
2.2購物車

<template> <div> <table> <tr> <th>號碼</th> <th>東西</th> <th>價格</th> <th>數量</th> <th>小計</th> </tr> <tr v-for="item in carts"> <td>{{item.id}}</td> <td>{{item.title}}</td> <td>{{item.price}}</td> <td> <button @click="minus(item.id)">-</button> <input type="number" min="0" v-model="item.number"> <button @click="add(item.id)">+</button> </td> <td>{{item.number * item.price}}</td> </tr> </table> <h1>總價格:{{this.carts.reduce((a,b)=>a + b.price * b.number , 0)}}</h1> </div> </template> <script> export default { data(){ return { carts : [ {"id" : 1 , "title" : "空調" , "price" : 5000, "number" : 1}, {"id" : 2 , "title" : "手機" , "price" : 3000, "number" : 1}, {"id" : 3 , "title" : "滑鼠" , "price" : 200 , "number" : 1} ] } }, methods : { add(id){ //this.carts.filter(item=>item.id == id)[0].number++ //vue不能識別數組某個項單獨修改!!要改必須直接改變數組!! this.carts = this.carts.map(item=>item.id == id ? {...item , "number" : item.number +}: item); }, minus(id){ //this.carts.filter(item=>item.id == id)[0].number-- this.carts = this.carts.map(item=>item.id == id ? {...item , "number" : item.number -}: item); } } } </script> <style> table,tr,td,th{border:1px solid red;} td{width:200px;height:60px;} </style>示例代碼
2.3選項卡

<style> .box { width: 600px;height: 400px; margin: 10px auto; border: 1px solid #333; header { ul { overflow: hidden; li { float: left; width: 33.333%; height: 40px; line-height: 40px; text-align: center; } li.cur { background: red; color: #fff; } } } } </style> <template> <div class="box"> <header> <ul> <li v-for="(item,index) in tabNav" :class="{cur:item.click}" @click="changeTab(dex)">{{item.title}}</li> </ul> </header> <div class="content"> <div v-show="tabIndex == 0"> 新聞新聞新聞新聞新聞 </div> <div v-show="tabIndex == 1"> 軍事軍事軍事軍事軍事 </div> <div v-show="tabIndex == 2"> 圖片圖片圖片圖片圖片 </div> </div> </div> </template> <script> export default { data() { return { tabNav: [ {title: "新聞", click: true}, {title: "軍事", click: false}, {title: "圖片", click: false} ], tabIndex: 0 } }, methods:{ changeTab(index){ // 遍歷tabNav數組 進行迴圈 去掉所有類 this.tabNav.forEach(function(item){ item.click = false }); // 點擊的那個tab 加類 this.tabNav[index].click = true // 改變索引 this.tabIndex = index } } } </script>示例代碼
2.4三級聯動

<template> <div> <select v-model="sheng"> <option v-for="item in info" :value="item.name"> {{item.name}} </option> </select> <select v-model="shi"> <option v-for="item in info.filter(i=>i.name == sheng)[0].city" :value="item.name" > {{item.name}} </option> </select> <select v-model="xian"> <option v-for="item in info.filter(i=>i.name==sheng)[0].city.filter(i=>i.name==shi)[0].area" :value="item" > {{item}} </option> </select> <h1>你的地址{{sheng}}{{shi}}{{xian}}</h1> </div> </template> <script> import info from "./info.js"; //引入全國省市數據 export default { data(){ return { info , sheng:"廣東省", shi : "廣州市", xian: "花都區" } }, watch : { sheng(){ //當data中的sheng變化的時候,觸發 this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name; this.xian=info.filter(i=>i.name==this.sheng)[0].city.filter(i=>i.name==this.shi)[0].area[0] } } } </script>示例代碼
簡化:

<template> <div> <!-- <select v-model="sheng" @change="changeSheng($event)"> --> <select v-model="sheng"> <option v-for="item in info" :value="item.name"> {{item.name}} </option> </select> <select v-model="shi"> <option v-for="item in allShi()" :value="item.name"> {{item.name}} </option> </select> <select v-model="xian"> <option v-for="item in allXian()" :value="item"> {{item}} </option> </select> </div> </template> <script> import info from "../lib/info.js"; export default { data(){ return { info, sheng:"廣東省", shi:"廣州市", xian:"天河區" } }, methods:{ // changeSheng(e){ // this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name; // this.xian = info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area[0]; // } allShi(){ return info.filter(i=>i.name == this.sheng)[0].city; }, allXian(){ return info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area } }, watch:{ //當data中的sheng變化的時候,觸發這個函數 sheng(){ this.shi = info.filter(i=>i.name == this.sheng)[0].city[0].name; this.xian = info.filter(i=>i.name == this.sheng)[0].city.filter(i=>i.name == this.shi)[0].area[0]; } } } </script>示例代碼
三、props(★)
l 使用 Prop 傳遞數據
組件實例的作用域是孤立的。這意味著不能 (也不應該) 在子組件的模板內直接引用父組件的數據。父組件的數據需要通過 prop 才能下發到子組件中。
l 動態 Prop:
與綁定到任何普通的 HTML屬性相類似,可以用v-bind來動態地將 prop 綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件
1) 組件的使用:子組件不能直接改變父組件傳入的值,必須調用父組件的函數來改變(引用類型值能直接改)
2) 組件的思維:所有子組件不需要對其它兄弟組件負責,只對父組件負責即可。
3.1傳基本類型值
l 如果想讓父組件data中的數據傳遞給子組件,需要使用標簽屬性傳遞(如果是動態值用v-bind)
l 子組件需要使用props接收父組件的值,如果父組件中修改a值,同時會影響子組件。
App.vue父組件
<template> <div> <Haha a="8"></Haha> <Haha :b="a"></Haha> </div> </template> <script> import Haha from "./components/Haha.vue"; export default { data(){ return { a : 100 } }, components : { Haha } } </script>
Haha.vue子組件
<template> <div> <h1>我是子組件{{a}} {{b}}</h1> </div> </template> <script> export default { props : ["a","b"], //接收父組件傳過來的屬性的值 data(){ return { a : 100 //從父組件接收有a,這裡就不能有同名的a了 } } } </script>
3.2子組件要改props必須調用父親傳的函數
l 子組件不能直接改變父組件傳入的值,必須調用父組件的函數來改變
l 傳值就要傳它的改變函數給子組件,本質上還是調用父親的函數去改變。
App.vue父組件傳值:
<template> <div> <h1>父組件{{a}}</h1> <button @click="add">父組件按鈕+</button> <button @click="minus">父組件按鈕-</button> <Haha :a="a" :add="add" :minus="minus"></Haha> </div> </template> <script> import Haha from "./components/Haha.vue"; export default { data(){ return { a : 100 } }, components : { Haha }, methods : { add(){ this.a++; }, minus(){ this.a--; } } } </script>
Haha.vue子組件接收
<template> <div> <h1>我是子組件{{a}}</h1> <button @click="add">子組件按鈕+</button> <button @click="minus">子組件按鈕-</button> </div> </template> <script> export default { //子組件不能直接改父組件的數據,要改就必須傳父組件的函數來改變 props : ["a","add","minus"], //接收父組件傳過來的屬性的值 data(){ return { } } } </script>
3.3傳引用類型值可以直接改
如果傳入引用類型值,子組件是可以直接改父組件的值,而不報錯的。
App.vue父組件
<template> <div> <h1>父組件{{obj.a}}</h1> <Haha :obj="obj"></Haha> <button @click="add">父組件按鈕+</button> </div> </template> <script> import Haha from "./components/Haha.vue"; export default { data(){ return { obj : { a : 100 } } }, components : { Haha }, methods : { add(){ this.obj.a++; } } } </script>
Haha.vue子組件
<template> <div> <h1>我是子組件{{obj.a}}</h1> <button @click="add">子組件按鈕+</button> </div> </template> <script> export default { // 子組件不能直接改父組件的數據,要改就必須傳父組件的函數來改變 props : ["obj"], //接收父組件傳過來的屬性的值 methods:{ add(){ this.obj.a++ } } } </script>
l 單向數據流
Prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是反過來不會。這是為了防止子組件無意間修改了父組件的狀態,來避免應用的數據流變得難以理解。
另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應該在子組件內部改變 prop。如果你這麼做了,Vue 會在控制台給出警告。
3.4字面量語法 vs 動態語法
使用v-bind是動態語法,不使用就是字元串
app.vue父組件:
<div> <Haha a="a"></Haha> </div> <script> import Haha from "./components/Haha.vue" export default { data() { return { a: 100 } } components:{ Haha } } </script>
Haha.vue子組件:
<template> <div> <h1>{{typeof a}}</h1> </div> </template> <script> export default{ props:["a"] } </script>
這樣傳遞得到的是字元串的 "a"
初學者常犯的一個錯誤是使用字面量語法傳遞數值:
因為它是一個字面量 prop,它的值是字元串 "a" 而不是一個數值。如果想