一、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" 而不是一個數值。如果想傳遞一個真正的 JavaScript 數值,則需要使用 v-bind,從而讓它的值被當作 JavaScript 表達式計算。
3.5組件化思維-調色板案例
用組件重新做昨天的調色板和購物車案例。
l v-model可以綁定一個引用類型值。
如果props是一個引用類型值,而不是基本類型值,此時v-model可以直接綁定修改,由vue內部實現對父組件的更改。
App.vue父組件

<template> <div> <div class="box" :style="{background:`rgb(${color.r},${color.g},${color.b})`}"></div> <!-- <Bar :v="r" name="r"></Bar> <Bar :v="g" name="g"></Bar> <Bar :v="b" name="b"></Bar> --> <Bar :color="color" name="r"></Bar> <Bar :color="color" name="g"></Bar> <Bar :color="color" name="b"></Bar> </div> </template> <script> import Bar from "./components/Bar.vue"; export default{ data(){ return { // 這里為什么要封裝一個對象 // 因為vue有一個機制,如果子組件的v-model與父組件傳入的引用類型值綁定 // 會自動幫你改父組件的值,而不會報錯。 color : { r : 100, g : 100, b : 100 } } } components : { Bar } } </script> <style> .box{ width: 200px; height: 200px; } </style>
Bar.vue子組件

<template> <div> <input type="range" max="255" v-model="color[name]"> <input type="number" max="255" v-model="color[name]"> </div> </template> <script> export default{ props:["color","name"], data(){ return { } } } </script>
3.6組件化思維-購物車案例
is屬性解釋:https://segmentfault.com/q/1010000007205176
說白了,就是讓它顯示哪個組件。
Bar.vue子組件:

<template> <tr> <td>{{item.id}}</td> <td>{{item.title}}</td> <td>{{item.price}}</td> <td> <button @click="minus">-</button> <input type="number" min="0" v-model.number="item.number"> <button @click="add">+</button> </td> <td> {{item.number * item.price}} </td> </tr> </template> <script> export default { props : ["item"], data(){ return { } }, methods : { add(){ // 可以直接改,因為父親傳入引用類型值 this.item.number++ }, minus(){ if(this.item.number <= 0 ) return; this.item.number-- } } } </script>
App.vue父組件

<template> <div> <table> <tr> <th>編號</th> <th>商品</th> <th>價格</th> <th>數量</th> <th>小計</th> </tr> <!-- <Bar v-for="item in carts" :item="item"></Bar> --> <tr is="Bar" v-for="item in carts" :item="item"></tr> </table> <h1>總價格:{{this.carts.reduce((a,b)=>a + b.price * b.number , 0)}}</h1> </div> </template> <script> import Bar from "./components/Bar.vue"; export default { data(){ return { carts : [ {"id":1,"title":"空調", "price":5000, "number":1}, {"id":2,"title":"手機", "price":4999, "number":1}, {"id":3,"title":"電腦", "price":6000, "number":1}, {"id":4,"title":"冰箱", "price":8000, "number":1} ] } }, components : { Bar } } </script>
3.7組件化思維-所有子組件不需要對其它兄弟組件負責
組件的思維:所有子組件不需要對其它兄弟組件負責,只對父組件負責即可。
教室:黑板、桌椅、側面宣傳欄、后黑板
只對父組件負責,不對其他組件負責!!!
App.vue父組件:
<template> <div> <Heiban class="heiban" :banzhang="banzhang"></Heiban> <Zhuoyi :students="students" :changeBanzhang="changeBanzhang"></Zhuoyi> <Xuanchuanlan class="xuanchuanlan" :banzhang="banzhang"></Xuanchuanlan> <Houheiban class="houheiban" :banzhang="banzhang"></Houheiban> </div> </template> <script> import Zhuoyi from "./components/Zhuoyi.vue"; import Houheiban from "./components/Houheiban.vue"; import Xuanchuanlan from "./components/Xuanchuanlan.vue"; import Heiban from "./components/Heiban.vue"; export default { data(){ return { students : ["小明","小紅","小強","小黑","小剛"], banzhang : "小明" } }, components : { //注冊組件 Heiban,Zhuoyi,Xuanchuanlan,Houheiban }, methods : { changeBanzhang(banzhang){ this.banzhang = banzhang; //改變班長 } } } </script> <style> .heiban, .houheiban{ width:650px; height:100px; border:1px solid #000; } .zhuoyi{ float:left; width:400px;height:300px; border:1px solid #000; } .xuanchuanlan{float:left; width:250px;height:300px; border:1px solid #000; } </style>
Heiban.vue,Houheiban.vue,Xuanchuanlan.vue子組件,都一樣:
<template> <div> 我是黑板。我班班長:{{banzhang}} </div> </template> <script> export default { props : ["banzhang"] } </script>
Zhuoyi.vue子組件:
<template> <div> <ul> <li v-for="item in students"> {{item}} <button @click="changeBanzhang(item)">成為班長</button> </li> </ul> </div> </template> <script> export default { props : ["students" , "changeBanzhang"] } </script> <style> li{float:left;width:100px;height:100px;margin:10px;background:orange;} </style>
四、計算屬性(computed )★
4.1 computed屬性的使用
我們現在export default(){ }中只寫了:data(){}、methods、components、props、watch
現在學習computed,表示計算后的值,做實驗看看什么意思。
注意:{{}}中如果放computed的值,是沒有圓括號的,雖然它是函數。
計算屬性的結果會被緩存,除非依賴的屬性(data、props中的值)變化才會重新計算。
當data/props中對應數據發生改變時,計算屬性的值也會發生改變。
如果想觸發computed的函數,必須在頁面中使用,哪怕是display:none了
computed就是返回一些和data、props相關的值,data或props的值變化時能夠自動觸發。
computed就是計算最多的一個地方,這里算法很重。
4.2關於methods和computed的區別
https://www.cnblogs.com/coderL/p/7506957.html
l computed就是要監聽哪些數據變化時要用到的。當監聽的數據發生變化時,立刻會執行計算,並返回結果。
l methods只是定義函數的。如要執行,還得自己手動執行,來動態當作方法來用的。
l 相同:兩者達到的效果是同樣的。
l 不同:
1.最明顯的不同,就是調用的時候,methods要加上()
2.可以使用methods替代computed,效果上兩個都是一樣的,但是 computed 是基於它的依賴緩存,只有相關依賴發生改變時才會重新取值。只要相關依賴未改變,只會返回之前的結果,不再執行函數。
而使用 methods,在重新渲染時,函數總會重新調用執行。
可以說使用 computed 性能會更好,但是如果你不希望緩存,你可以使用 methods 屬性。
computed 屬性默認只有 getter ,不過在需要時你也可以提供一個 setter :所以其實computed也是可以傳參的。
<template> <div> <h1>{{a}}</h1> <h2>{{pingfang}}</h2> <button @click="add">+</button> </div> </template> <script> export default { data(){ return { a:100 } }, computed:{ pingfang(){ return this.a * this.a } }, methods:{ add(){ this.a++ } } } </script> <style></style>