目錄
四、組件化
什么是組件
借鑒了將一個大的問題拆分成一個個的小問題這種思想 , 就是"基礎庫"或者“基礎組件",意思是把代碼重復的部分提煉出一個個組件供給功能使用。
- 將一個頁面拆分成一個個的小組件,可以遞歸的拆分
- 每個組件完成自己相關的功能,多個組件共同組成一個頁面或者程序
- 復用性:下次需要同樣的功能就可以復用
- 類似於項目中的一個模塊,只不過更加細化了。
組件的使用
- 創建組件的構造器
- 注冊組件
- 使用組件
組件必須放在vue管理的作用域內,如果是多個標簽必須被一個元素包裹,就是有一個唯一的祖先元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../../js/vue.js"></script>
<div id="app">
<cpt></cpt>
<cpt></cpt>
<cpt></cpt>
<cpt></cpt>
</div>
<script>
// 1. 創建組件構造器
const component = Vue.extend({
template: `
<div>
hello
</div>`,
});
// 2. 注冊組件 全局組件
Vue.component('cpt', component);
const app = new Vue({
el: "#app",
data: {
message: "hello world"
}
});
</script>
</body>
</html>

局部組件
<div id="app">11
<cpt></cpt>
<cpt></cpt>
</div>
<div id="app2">22
<cpt></cpt>
</div>
<script>
// 1. 創建組件構造器
const component = Vue.extend({
template: `
<div>
hello
</div>`,
});
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
cpt: component
}
});
//未注冊組件
const app2 = new Vue({
el: "#app2",
data: {
message: "hello"
}
});
</script>

父子組件
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<script>
/*第1個組件構造器*/
const child = Vue.extend({
template: `
<div>
child
</div>`
});
// 第二創建組件構造器
const parent = Vue.extend({
template: `
<div>
parent
<cd></cd>
</div>`,
components: {
cd: child
}
});
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: parent
}
});
</script>
組件的傳遞
組件不會向上級作用域傳遞,只會向下傳遞,孫子沒有在爺爺的作用域注冊的話孫子只能在父親的作用域使用
組件的語法糖
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<script>
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
// 語法糖直接可以放在注冊的地方
template: `
<div>
hello
</div>`
}
}
});
</script>
模板的分離
<script src="../../js/vue.js"></script>
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<!--<script type="text/x-template" id="pt">
<div>
<div>我是標題</div>
</div>
</script>-->
<template id="pt">
<div>
<div>我是tempalte</div>
</div>
</template>
<script>
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
// 語法糖直接可以放在注冊的地方
template: "#pt"
}
}
});
</script>
組件訪問數據
- 組件不能訪問實例中的數據
- 只能訪問自己的數據
- 在子組件中data屬性是一個function不是對象,可以返回一個數據對象供它訪問
- 組件也有method屬性,它的原型實際上是指向vue的實例的
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<template id="pt">
<div>
<div>我是{{title}}</div>
</div>
</template>
<script>
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
template: "#pt",
//是一個函數,且只能訪問自己的數據
data(){
return {title:"title"};
}
}
}
});
</script>
組件的data必須是函數
- 如果寫屬性的話,很容易造成多個組件的數據引用指向同一塊內存,會相互影響
- 用function的話你只要每次返回一個匿名對象,他是沒有公共引用指向的所以不會影響,如果需要的話你自己可以return 一個公用的引用就會相互影響的
- 所以為了避免這種bug,data不是function就會報錯
- 必須return 一個對象{}
父子組件通信
父傳子
- props屬性 : 可以寫成數組或者對象,對象可以限制類型,對象更好點,也可以類型寫成對象添加更多的限制、給默認值
- 給默認值的時候如果是對象或者是數組,不能直接用{}、[] 需要用工廠(default(){})來創建
- 自定義validator
- 可以自定義一個類作為類型
<div id="app">
<pt :msg="msg" :title="title"></pt>
</div>
<template id="pt">
<div>
<div>{{title}}</div>
<div>{{msg}}</div>
</div>
</template>
<script>
// 1.注冊組件
const pt = {
template:"#pt",
data() {
return {};
},
methods: {},
// props:["title","msg"] 可以寫成數組或者對象,對象可以限制類型,對象更好點
props:{
// title:Array,
title:{
type: Array,
default(){
return [];
}
},
//也可以寫成對象的添加更多的限制、給默認值
msg:{
type:String,
default:"",
required:true,
//自定義validator 這個待查閱
validator: function (val) {
return val == "hello worl";
}
}
}
}
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
msg: "hello world",
title:["aaa","bbb","ccc"]
},
//字面量簡寫 pt可替換pt:pt
components:{pt}
});
</script>
子傳父|自定義事件
v-on事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以v-on:myEvent將會變成v-on:myevent——導致myEvent不可能被監聽到。- $emit --》this.$emit('myevent')會傳遞給父組件的監聽事件要同名
- 推薦你始終使用 kebab-case 的事件名 my-event
- 子組件盡量和自己的data屬性去綁定
<div id="app">
<!-- 不寫參數會默認將$emit事件后傳的參數【可多個】傳出來,寫了參數報錯-->
<pt @child-click="parentClick"></pt>
</div>
<template id="pt">
<div>
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script>
// 1.注冊組件
const pt = {
template: "#pt",
data() {
return {
categories: [
{id: "aaa", name: "aaa"},
{id: "bbb", name: "bbb"},
{id: "ccc", name: "ccc"},
{id: "ddd", name: "ddd"}
]
};
},
methods: {
btnClick(ite) {
// js中這樣寫不能駝峰,vue可以
this.$emit('child-click', ite,1);
}
}
};
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
msg: "hello world",
title: ["aaa", "bbb", "ccc"]
},
components: {pt},
methods: {
parentClick(obj,a) {
console.log(obj,a);
}
}
});
</script>
練習
- num1、num2從父組件傳遞過來
- 修改num1,dnum1也變,同時傳dnum1給父組件,父組件改變num1,也改變了prop1
- dnum2一直是dnum1的1%
<!--1. num1、num2從父組件傳遞過來
2. 修改num1,dnum1也變,同時傳dnum1給父組件,父組件改變num1,也改變了prop1
3. dnum2一直是dnum1的1%-->
<div id="app">
<pt :cnum1="num1" :cnum2="num2"
@change1="cc1"
@change2="cc2"
></pt>
</div>
<template id="pt">
<div>
<p>props:{{cnum1}}</p>
<p>data:{{dnum1}}</p>
cnum1<input type="text" :value="dnum1" @input="changeProp1"><br>
<p>props:{{cnum2}}</p>
<p>data:{{dnum2}}</p>
cnum2<input type="text" :value="dnum2" @input="changeProp2">
</div>
</template>
<script>
//局部組件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
num1: 1,
num2: 2
},
methods: {
cc1(eve1) {
this.num1 = eve1;
},
cc2(eve2) {
this.num2 = eve2;
}
},
components: {
pt: {
template: "#pt",
props: {
cnum1: {
type: Number,
default: 3
},
cnum2: {
type: Number,
default: 4
}
},
data() {
return {
dnum1: this.cnum1,
dnum2: this.cnum2,
};
},
methods: {
changeProp1(event1) {
this.dnum1 = event1.target.value;
console.log(this.dnum1)
if (this.dnum1) {
this.dnum1 = parseInt(this.dnum1)
this.dnum2 = this.dnum1 / 100;
this.$emit('change1', this.dnum1);
} else {
this.dnum2 = "";
}
},
changeProp2(event2) {
this.dnum2 = event2.target.value;
this.$emit('change2', parseInt(this.dnum2));
}
}
}
}
});
</script>
watch
-
watch監聽對象不能直接監聽,可以用computed代替對象
-
語法:
watch:{ 監聽的屬性名(newValue, oldValue){ } }
<script src="../../js/vue.js"></script>
<div id="app">
{{message}}
<input type="text" v-model="message">
{{demo.name}}
<input type="text" v-model="demo.name">
</div>
<template id="cd">
<div>
aaaaa
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
demo: {
name: "nameObj"
}
},
computed:{
demoName(){
return this.demo.name;
}
},
watch: {
message(newVal, oldVal) {
console.log(newVal, oldVal);
},
//不能直接監聽對象
// demo(val) {
// console.log(val);
// }
demoName(val) {
console.log(val);
}
},
components: {
cd: {
template: "#cd"
}
}
});
</script>
-
如果是鍵的路徑需要用引號包裹
-
也可以外部調用
-
immediate和handler
watch有一個特點,就是當值第一次綁定的時候,不會執行監聽函數,只有值發生改變才會執行。如果我們需要在最初綁定值的時候也執行函數,則就需要用到immediate屬性。
比如當父組件向子組件動態傳值時,子組件props首次獲取到父組件傳來的默認值時,也需要執行函數,此時就需要將immediate設為true。
-
**deep: **當需要監聽一個對象的改變時,普通的watch方法無法監聽到對象內部屬性的改變,只有data中的數據才能夠監聽到變化,此時就需要deep屬性對對象進行深度監聽
<div id="app">
{{demo1.name}}
<input type="text" v-model="demo1.name">
{{demo.name}}
<input type="text" v-model="demo.name">
<input type="text" v-model="demo2">
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
demo: {
name: "nameObj"
},
demo1: {
name: "nameObj"
},
demo2:"qweqw"
},
computed: {
demoName() {
return this.demo.name;
}
},
watch: {
//如果是鍵的路徑需要用引號包裹
"demo.name": function (val) {
console.log(val);
},
// childrens: { //監聽的屬性的名字
// handler:function(val){
// console.log(val.name);
// },
// deep: true, //可以監聽到一個對象的內部屬性變化
// immediate: true
// },
// "childrens.name":function (val) {
// console.log(val);
// }
}
});
//外部調用
app.$watch("demo2",function (val) {
console.log(val)
})
</script>
訪問子組件實例 $children和$refs
- 一般不會用$children來取子組件
- $refs.refName | $refs['refName']
- 如果多個相同的引用會取最后一個
- 如果綁定的是一個普通標簽拿到的就是一個dom對象
<div id="app">
<tmp ref="a"></tmp>
<tmp ref="a"></tmp>
<tmp ref="b"></tmp>
<button @click="btnClick">打印子組件</button>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
methods:{
btnClick(){
//1. 一般不會用$children來取子組件
// console.log("第一個子組件:",this.$children[0]);
// console.log("所有子組件:",this.$children);
// 2.$refs.refName|['refName']
console.log("所有組件有ref屬性的組件:",this.$refs);
//如果多個相同的引用會取最后一個
console.log("取得固定的ref的元素:",this.$refs["a"]);
console.log("取得固定的ref的元素:",this.$refs.b);
}
},
components: {
tmp: {
template: "#tmp"
}
},
});
</script>
訪問父組件實例
- 不建議使用this.$parent,會讓組件的耦合增強不夠獨立
- 祖先組件this.$root
<div id="app">
<tmp></tmp>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
<button @click="btnClick">打印父組件</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp",
methods: {
btnClick() {
//1. 不建議使用,會讓組件的耦合增強不夠獨立
console.log("打印直系父組件:", this.$parent);
//祖先組件
console.log("打印root組件:", this.$root);
}
}
},
},
});
插槽slot
- 拓展組件像回調函數一樣,usb接口一樣
- 插槽的基本使用
- 插槽的默認值
默認值
<!--1. 插槽的基本使用 <slot></slot>-->
<!--2. 插槽的默認值 <slot>默認值</slot>-->
<div id="app">
<tmp></tmp><br>
<tmp></tmp><br>
<tmp></tmp><br>
<tmp><div>我是插槽</div></tmp>
<tmp><i>我是插槽i</i></tmp>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
<slot><p>我是默認值*******</p></slot>
<p>娃娃</p>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp"
},
}
});
</script>
具名插槽
<div id="app">
<tmp ><a slot="right" href="#">我替換右邊</a></tmp><br>
<tmp ><a slot="left" href="#">我替換左邊</a></tmp><br>
<tmp><a href="#">我替換沒名字的</a></tmp><br>
</div>
<template id="tmp">
<div>
<slot name="left"><p>我是默認值left</p></slot>
<slot name="center"><p>我是默認值center</p></slot>
<slot name="right"><p>我是默認值right</p></slot>
<slot><p>我是默認值沒有名字</p></slot>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp"
},
}
});
編譯作用域
- 始終使用自己組件中的變量
<div id="app">
<!-- 在誰的作用域用誰的變量-->
<cp v-show="isShow"></cp>
</div>
<template id="cp">
<div v-show="isShow"><!-- div父元素初始化的時候不受影響 -->
<a href="">aaa</a>
<button v-show="isShow">按鈕</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
isShow: true
},
components: {
cp: {
template: "#cp",
data() {
return {
isShow: false
};
}
}
}
});
</script>
作用域插槽
- 父組件想要替換子組件的插槽的數據,數據的具體值還是由子組件來決定
- slot-scope="slotData",類似於該組件的對象,2.5之前要用template標簽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../../js/vue.js"></script>
<div id="app">
<cp>
<!-- slotData:類似於該組件的對象,2.5之前要用template-->
<template slot-scope="slotData">
<!-- 取得綁定在組件中的數據-->
<span v-for="item in slotData.datas">{{item}}-</span>
</template>
</cp>
<cp>
<template slot-scope="slotData">
<!-- join方法將數組拼接成字符串-->
<span>{{slotData.datas.join(' * ')}}</span>
</template>
</cp>
</div>
<template id="cp">
<div>
<!-- 作為傳遞的數據-->
<slot :datas="languages">
<ul>
<li v-for="item in languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
},
components: {
cp: {
template: "#cp",
data() {
return {
languages: ['java', 'javascript', 'css', 'html', 'vb', 'python']
};
}
}
}
});
</script>
</body>
</html>
五、es6模塊化
把功能進行划分,將同一類型的代碼整合在一起,所以模塊的功能相對復雜,但都同屬於一個業務
為什么有模塊化
- js是按順序加載的,所以一般相互依的js是具有強制性的
- 多個js文件定義的引用會污染全局變量,多人協作開發可能會有沖突
- 可以用閉包
閉包解決多人協作開發
- 只需要寫好自己的模塊化的命名,就可以很好的避免沖突了,相當於把所有的容錯點都聚焦在一個點上,犯錯的機會就少了,
- 但是代碼的復用性還是很差
// ;是為了防止其他的導入js相互影響
;var xm01 = (function xiaoming01() {
return {
aa:"asdas",
flag: true
};
}())
//js文件2
;(function () {
if (xm01.flag) {
alert("xm01.flag:" + xm01.flag);
}
}());
組件化類似模塊化的更細粒度,組件充當了基本類庫一樣的東西目的是復用拓展性,模塊主要是以功能區分類別划分盡量隔離其他業務
