組件 (Component) 是 Vue.js 最強大的功能之一。組件能夠擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器爲它添加特殊功能。在有些狀況下,組件也能夠表現爲用 is 特性進行了擴展的原生 HTML 元素。javascript
全部的 Vue 組件同時也都是 Vue 的實例,因此可接受相同的選項對象 (除了一些根級特有的選項) 並提供相同的生命週期鉤子。css
說白了,就是HTML、CSS、JS行爲的一個封裝。html
Vue中組件的實例化是對程序員不透明的,組件必須在components中進行「註冊」才能使用。vue
Vue中用K:V對的形式定義類,讓你自由的註冊名字,Vue官方推薦使用含有短橫的名字來表示自定義組件。java
翻譯.vue文件須要安裝vue-loader依賴:webpack
https://vue-loader.vuejs.org/zh-cn/程序員
https://vue-loader-v14.vuejs.org/zh-cn/configurations/pre-processors.htmlweb
安裝其餘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的配置)官網找:npm
https://vue-loader-v14.vuejs.org/zh-cn/configurations/pre-processors.html
.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父組件的內容了
使用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>
在前面看到,在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對象,只能返回函數。
<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>
<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>
<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>
<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>
l 使用 Prop 傳遞數據
組件實例的做用域是孤立的。這意味着不能 (也不該該) 在子組件的模板內直接引用父組件的數據。父組件的數據須要經過 prop 才能下發到子組件中。
l 動態 Prop:
與綁定到任何普通的 HTML屬性相相似,能夠用v-bind來動態地將 prop 綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件
1) 組件的使用:子組件不能直接改變父組件傳入的值,必須調用父組件的函數來改變(引用類型值能直接改)
2) 組件的思惟:全部子組件不須要對其它兄弟組件負責,只對父組件負責便可。
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>
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>
若是傳入引用類型值,子組件是能夠直接改父組件的值,而不報錯的。
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 會在控制檯給出警告。
使用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 表達式計算。
用組件從新作昨天的調色板和購物車案例。
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>
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>
組件的思惟:全部子組件不須要對其它兄弟組件負責,只對父組件負責便可。
教室:黑板、桌椅、側面宣傳欄、後黑板
只對父組件負責,不對其餘組件負責!!!
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>
咱們如今export default(){ }中只寫了:data(){}、methods、components、props、watch
如今學習computed,表示計算後的值,作實驗看看什麼意思。
注意:{{}}中若是放computed的值,是沒有圓括號的,雖然它是函數。
計算屬性的結果會被緩存,除非依賴的屬性(data、props中的值)變化纔會從新計算。
當data/props中對應數據發生改變時,計算屬性的值也會發生改變。
若是想觸發computed的函數,必須在頁面中使用,哪怕是display:none了
computed就是返回一些和data、props相關的值,data或props的值變化時可以自動觸發。
computed就是計算最多的一個地方,這裏算法很重。
http://www.javashuo.com/article/p-zynvanie-gr.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>