Vue 组件化
什么是組件化?
任何一個(gè)人處理信息的邏輯能力都是有限的
所以,當(dāng)面對(duì)一個(gè)非常復(fù)雜的問題時(shí),我們不太可能一次性搞定一大堆的內(nèi)容。
但是,我們?nèi)擞幸环N天生的能力,就是將問題進(jìn)行拆解。
如果將一個(gè)復(fù)雜的問題,拆分成很多個(gè)可以處理的小問題,再將其放在整體當(dāng)中,你會(huì)發(fā)現(xiàn)大的問題也會(huì)迎刃而解。
組件化也是類似的思想:
如果我們將一個(gè)頁(yè)面中所有的處理邏輯全部放在一起,處理起來就會(huì)變得非常復(fù)雜,而且不利于后續(xù)的管理以及擴(kuò)展。
Vue組件化思想
組件化是Vue.js中的重要思想
它提供了一種抽象,讓我們可以開發(fā)出一個(gè)個(gè)獨(dú)立可復(fù)用的小組件來構(gòu)造我們的應(yīng)用。
任何的應(yīng)用都會(huì)被抽象成一顆組件樹。
組件化思想的應(yīng)用:
有了組件化的思想,我們?cè)谥蟮拈_發(fā)中就要充分的利用它。
盡可能的將頁(yè)面拆分成一個(gè)個(gè)小的、可復(fù)用的組件。
這樣讓我們的代碼更加方便組織和管理,并且擴(kuò)展性也更強(qiáng)。
注冊(cè)組件的基本步驟
創(chuàng)建組件構(gòu)造器
const cpnC = Vue.extend({template: `<div><h2>我是標(biāo)題</h2><p>我是內(nèi)容, 哈哈哈哈</p><p>我是內(nèi)容, 呵呵呵呵</p></div>`})注冊(cè)組件
Vue.computend('my-cpn', cpnC)使用組件。
<my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> <body><div class="app"><my-cpn></my-cpn><my-cpn></my-cpn><my-cpn></my-cpn><my-cpn></my-cpn></div><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const cpnC = Vue.extend({template: `<div><h2>我是標(biāo)題</h2><p>我是內(nèi)容, 哈哈哈哈</p><p>我是內(nèi)容, 呵呵呵呵</p></div>`})Vue.computend('my-cpn', cpnC)const app = new Vue({el: '.app',data: {message: '你好啊'}})</script> </body>Vue.extend():
調(diào)用Vue.extend()創(chuàng)建的是一個(gè)組件構(gòu)造器。 通常在創(chuàng)建組件構(gòu)造器時(shí),傳入template代表我們自定義組件的模板。 該模板就是在使用到組件的地方,要顯示的HTML代碼。Vue.component():
調(diào)用Vue.component()是將剛才的組件構(gòu)造器注冊(cè)為一個(gè)組件,并且給它起一個(gè)組件的標(biāo)簽名稱。
所以需要傳遞兩個(gè)參數(shù): 1、注冊(cè)組件的標(biāo)簽名 2、組件構(gòu)造器組件必須掛載在某個(gè)Vue實(shí)例下,否則它不會(huì)生效。
下面使用了三次<my-cpn></my-cpn>而第三次其實(shí)并沒有生效:
全局組件和局部組件
當(dāng)我們通過調(diào)用Vue.component()注冊(cè)組件時(shí),組件的注冊(cè)是全局的
這意味著該組件可以在任意Vue示例下使用。
如果我們注冊(cè)的組件是掛載在某個(gè)實(shí)例中, 那么就是一個(gè)局部組件
<body><div id="app"><cpn></cpn><cpn></cpn></div><div id="app2"><cpn></cpn></div><script src="../js/vue.js"></script><script>// 1.創(chuàng)建組件構(gòu)造器const cpnC = Vue.extend({template: `<div><h2>我是標(biāo)題</h2><p>我是內(nèi)容,哈哈哈哈啊</p></div>`})// 2.注冊(cè)組件(全局組件, 意味著可以在多個(gè)Vue的實(shí)例下面使用)// Vue.component('cpn', cpnC)const app = new Vue({el: '#app',data: {message: '你好啊'},components: {// cpn使用組件時(shí)的標(biāo)簽名cpn: cpnC}})const app2 = new Vue({el: '#app2'})</script></body>此時(shí)app2是無法使用組件的 因?yàn)榻M件在app1中注冊(cè) 是局部組件
父組件和子組件
組件和組件之間存在層級(jí)關(guān)系
而其中一種非常重要的關(guān)系就是父子組件的關(guān)系
父子組件錯(cuò)誤用法:以子標(biāo)簽的形式在Vue實(shí)例中使用
<div id="app"><cpn2></cpn2><cpn1></cpn1> //這是錯(cuò)誤的</div>因?yàn)楫?dāng)子組件注冊(cè)到父組件的components時(shí),Vue會(huì)編譯好父組件的模塊
該模板的內(nèi)容已經(jīng)決定了父組件將要渲染的HTML
(相當(dāng)于父組件中已經(jīng)有了子組件中的內(nèi)容了)
注冊(cè)組件語法糖
在上面注冊(cè)組件的方式,可能會(huì)有些繁瑣。
Vue為了簡(jiǎn)化這個(gè)過程,提供了注冊(cè)的語法糖。
主要是省去了調(diào)用Vue.extend()的步驟,而是可以直接使用一個(gè)對(duì)象來代替。
模板的分離寫法
剛才,我們通過語法糖簡(jiǎn)化了Vue組件的注冊(cè)過程,另外還有一個(gè)地方的寫法比較麻煩,就是template模塊寫法。
如果我們能將其中的HTML分離出來寫,然后掛載到對(duì)應(yīng)的組件上,必然結(jié)構(gòu)會(huì)變得非常清晰。
Vue提供了兩種方案來定義HTML模塊內(nèi)容:
使用<script>標(biāo)簽 使用<template>標(biāo)簽 <body><div id="app"><cpn></cpn></div><!-- 1.script標(biāo)簽, 注意:類型必須是text/x-template--><script type="text/x-template" id="cpn"><div><h2>我是標(biāo)題</h2><p>我是內(nèi)容,哈哈哈</p></div></script><!--2.template標(biāo)簽--><template id="cpn"><div><h2>我是標(biāo)題</h2><p>我是內(nèi)容,呵呵呵</p></div> </template><script src="../js/vue.js"></script><script>// 1.注冊(cè)一個(gè)全局組件Vue.component('cpn', {template: '#cpn'})const app = new Vue({el: '#app',data: {message: '你好啊'}})</script></body>組件中的數(shù)據(jù)存放問題
組件是一個(gè)單獨(dú)功能模塊的封裝:
這個(gè)模塊有屬于自己的HTML模板,也應(yīng)該有屬性自己的數(shù)據(jù)data。
組件中的數(shù)據(jù)是保存在哪里呢?頂層的Vue實(shí)例中嗎?
測(cè)試一下,組件中能不能直接訪問Vue實(shí)例中的data
我們發(fā)現(xiàn)不能訪問,而且即使可以訪問,如果將所有的數(shù)據(jù)都放在Vue實(shí)例中,Vue實(shí)例就會(huì)變的非常臃腫。
結(jié)論:Vue組件應(yīng)該有自己保存數(shù)據(jù)的地方。
組件數(shù)據(jù)的存放
組件對(duì)象也有一個(gè)data屬性(也可以有methods等屬性)
只是這個(gè)data屬性必須是一個(gè)函數(shù)
而且這個(gè)函數(shù)返回一個(gè)對(duì)象,對(duì)象內(nèi)部保存著數(shù)據(jù)
data屬性為什么是一個(gè)函數(shù)呢?
首先,如果不是一個(gè)函數(shù),Vue直接就會(huì)報(bào)錯(cuò)。
其次,原因是在于Vue讓每個(gè)組件對(duì)象都返回一個(gè)新的對(duì)象
如果是同一個(gè)對(duì)象的,組件在多次使用后會(huì)相互影響。
<body><!--組件實(shí)例對(duì)象--><div id="app"><cpn></cpn><cpn></cpn></div><template id="cpn"><div><h2>當(dāng)前計(jì)數(shù): {{counter}}</h2><button @click="increment">+</button><button @click="decrement">-</button></div> </template><script src="../js/vue.js"></script><script>// 1.注冊(cè)組件const obj = {counter: 0}Vue.component('cpn', {template: '#cpn',// data() {// return {// counter: 0// }// },data() {return obj},methods: {increment() {this.counter++},decrement() {this.counter--}}})const app = new Vue({el: '#app',data: {message: '你好啊'}})</script><script>function abc() {return {name: 'xiao',}}//let obj1 = abc()let obj2 = abc()let obj3 = abc()obj1.name = 'kobe'console.log(obj2); //還是xiaoconsole.log(obj3); //還是xiao 不會(huì)互相影響 因?yàn)槭呛瘮?shù)const obj = {name: 'why',age: 18}function abc() {return obj}let obj4 = abc()let obj5 = abc()let obj6 = abc()obj4.name = 'kobe'console.log(obj5); //kobeconsole.log(obj6); //kobe 對(duì)象指向的同一地址 一個(gè)改變都會(huì)改變 因?yàn)榈刂犯淖?/span></script></body>父子通信-父?jìng)髯?#xff08;props)
子組件是不能引用父組件或者Vue實(shí)例的數(shù)據(jù)的。
在開發(fā)中,往往一些數(shù)據(jù)確實(shí)需要從上層傳遞到下層:
比如在一個(gè)頁(yè)面中,從服務(wù)器請(qǐng)求到了很多的數(shù)據(jù)。
其中一部分?jǐn)?shù)據(jù),并非是我們整個(gè)頁(yè)面的大組件來展示的,而是需要下面的子組件進(jìn)行展示。
這個(gè)時(shí)候,并不會(huì)讓子組件再次發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求,而是直接讓大組件(父組件)將數(shù)據(jù)傳遞給小組件(子組件)。
Vue官方提到
通過props向子組件傳遞數(shù)據(jù)
通過事件向父組件發(fā)送消息
可以直接將Vue實(shí)例當(dāng)做父組件,并且其中包含子組件來簡(jiǎn)化代碼。
真實(shí)的開發(fā)中,Vue實(shí)例和子組件的通信和父組件和子組件的通信過程是一樣的。
props基本用法
在組件中,使用選項(xiàng)props來聲明需要從父級(jí)接收到的數(shù)據(jù)。
props的值有兩種方式:
方式一:字符串?dāng)?shù)組,數(shù)組中的字符串就是傳遞時(shí)的名稱。
方式二:對(duì)象,對(duì)象可以設(shè)置傳遞時(shí)的類型,也可以設(shè)置默認(rèn)值等。
一個(gè)最簡(jiǎn)單的props傳遞:
props數(shù)據(jù)驗(yàn)證
前面,我們的props選項(xiàng)是使用一個(gè)數(shù)組。
除了數(shù)組之外,我們也可以使用對(duì)象,當(dāng)需要對(duì)props進(jìn)行類型等驗(yàn)證時(shí),就需要對(duì)象寫法了。
驗(yàn)證都支持哪些數(shù)據(jù)類型呢?
String
Number
Boolean
Array
Object
Date
Function
Symbol
當(dāng)我們有自定義構(gòu)造函數(shù)時(shí),驗(yàn)證也支持自定義的類型
父?jìng)髯又械膒rops駝峰標(biāo)識(shí)
如果要使用駝峰標(biāo)識(shí)法,比如cInfo 再綁定父組件數(shù)據(jù)時(shí)要改寫成c-info
<body><div class="app"><!-- 注意要使用駝峰寫法 這里cInfo 要寫成c-info --><cpn :c-info="info"></cpn></div><template id="cpn"><div><!-- 這里的cInfo不用改 --><h2>{{cInfo}}</h2></div></template><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const cpn = {template: '#cpn',props: {cInfo: {type: Object,default () {return {}}}}}const app = new Vue({el: '.app',data: {info: {name: 'xxx',age: 18}},components: {cpn}})</script> </body>父子通信-子傳父(自定義事件 $emit)
props用于父組件向子組件傳遞數(shù)據(jù),還有一種比較常見的是子組件傳遞數(shù)據(jù)或事件到父組件中。
如何處理呢?這個(gè)時(shí)候,我們需要使用自定義事件來完成。
當(dāng)子組件需要向父組件傳遞數(shù)據(jù)時(shí),就要用到自定義事件了。
我們之前學(xué)習(xí)的v-on不僅僅可以用于監(jiān)聽DOM事件,也可以用于組件間的自定義事件。
自定義事件的流程
在子組件中,通過$emit()來觸發(fā)事件。
在父組件中,通過v-on來監(jiān)聽子組件事件。
組件訪問-父訪問子-($children $refs)
有時(shí)候我們需要父組件直接訪問子組件,子組件直接訪問父組件,或者是子組件訪問跟組件。
父組件訪問子組件:使用$children或$refs 子組件訪問父組件:使用$parentthis.$children是一個(gè)數(shù)組類型,它包含所有子組件對(duì)象。
<body><div class="app"><cpn></cpn><cpn ref='aaa'></cpn><!-- 點(diǎn)擊按鈕訪問子組件的屬性 --><button @click="btnClick"></button></div><template id="cpn"><div><h2>我是子組件</h2></div></template><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const app = new Vue({el: '.app',data: {},methods: {btnClick() {for (let i of this.$children) {console.log(i.name);i.showMessage();}}},components: {cpn: {template: '#cpn',data() {return {name: 'xiaoY'}},methods: {showMessage() {console.log('showMessage');}}}}})</script> </body>$children的缺陷:
通過$children訪問子組件時(shí),是一個(gè)數(shù)組類型,訪問其中的子組件必須通過索引值。
但是當(dāng)子組件過多,我們需要拿到其中一個(gè)時(shí),往往不能確定它的索引值,甚至還可能會(huì)發(fā)生變化。
有時(shí)候,我們想明確獲取其中一個(gè)特定的組件,這個(gè)時(shí)候就可以使用$refs
$refs的使用:
$refs和ref指令通常是一起使用的。首先,我們通過ref給某一個(gè)子組件綁定一個(gè)特定的ID。
其次,通過this.$refs.ID就可以訪問到該組件了。
組件訪問-子訪問父-($parent $root)
如果我們想在子組件中直接訪問父組件,可以通過$parent
如果訪問根組件 可以使用$root
<body><div class="app"><cpn></cpn></div><!-- 子組件 --><template id="cpn"><div><h2>我是子組件</h2><ccpn></ccpn></div></template><!-- 子子組件 --><template id="ccpn"><div><h2>我是子子組件</h2><button @click="btnClick"></button></div></template><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const app = new Vue({el: '.app',data: {name: 'xiaoY',message: 'ni hao',},components: {cpn: {template: '#cpn',data() {return {name: '我是子組件的name'}},components: {ccpn: {template: '#ccpn',methods: {btnClick() {console.log(this.$parent);console.log(this.$root);console.log(this.$parent.name);console.log(this.$root.message);console.log(this.$root.name);}}}}}},})</script> </body>
注意事項(xiàng):
盡管在Vue開發(fā)中,我們?cè)试S通過$parent來訪問父組件,但是在真實(shí)開發(fā)中盡量不要這樣做。
子組件應(yīng)該盡量避免直接訪問父組件的數(shù)據(jù),因?yàn)檫@樣耦合度太高了。
如果我們將子組件放在另外一個(gè)組件之內(nèi),很可能該父組件沒有對(duì)應(yīng)的屬性,往往會(huì)引起問題。另外,更不好做的是通過$parent直接修改父組件的狀態(tài),那么父組件中的狀態(tài)將變得飄忽不定,很不利于我的調(diào)試和維護(hù)。
插槽slot
slot翻譯為插槽:
在生活中很多地方都有插槽,電腦的USB插槽,插板當(dāng)中的電源插槽。
插槽的目的是讓我們?cè)瓉淼脑O(shè)備具備更多的擴(kuò)展性。
比如電腦的USB我們可以插入U(xiǎn)盤、硬盤、手機(jī)、音響、鍵盤、鼠標(biāo)等等。
組件的插槽:
組件的插槽也是為了讓我們封裝的組件更加具有擴(kuò)展性。
讓使用者可以決定組件內(nèi)部的一些內(nèi)容到底展示什么。
具名插槽slot
當(dāng)子組件的功能復(fù)雜時(shí),子組件的插槽可能并非是一個(gè)。
比如我們封裝一個(gè)導(dǎo)航欄的子組件,可能就需要三個(gè)插槽,分別代表左邊、中間、右邊。那么,外面在給插槽插入內(nèi)容時(shí),如何區(qū)分插入的是哪一個(gè)呢?
這個(gè)時(shí)候,我們就需要給插槽起一個(gè)名字
如何使用具名插槽呢?
非常簡(jiǎn)單,只要給slot元素一個(gè)name屬性即可
編譯作用域
父組件模板的所有東西都會(huì)在父級(jí)作用域內(nèi)編譯;子組件模板的所有東西都會(huì)在子級(jí)作用域內(nèi)編譯。 <body><div id="app"><cpn v-show="isShow"></cpn></div><template id="cpn"><div><h2>我是子組件</h2><p>我是內(nèi)容, 哈哈哈</p><button v-show="isShow">按鈕</button></div> </template><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const app = new Vue({el: '#app',data: {message: '你好啊',isShow: true},components: {cpn: {template: '#cpn',data() {return {isShow: false}}},}})</script></body>在使用<cpn v-show="isShow"></cpn>的時(shí)候,整個(gè)組件的使用過程是相當(dāng)于在父組件中出現(xiàn)的。
那么他的作用域就是父組件,使用的屬性也是屬于父組件的屬性。
因此,isShow使用的是Vue實(shí)例中的屬性,而不是子組件的屬性。
上圖代碼中子組件的 isShow為false 所以子組件中的button按鈕就不會(huì)顯示作用域插槽
作用域插槽是slot一個(gè)比較難理解的點(diǎn),而且官方文檔說的又有點(diǎn)不清晰。
一句話對(duì)其做一個(gè)總結(jié):父組件替換插槽的標(biāo)簽,但是內(nèi)容由子組件來提供。
示例
子組件中包括一組數(shù)據(jù),比如:
內(nèi)容在子組件,希望父組件告訴我們?nèi)绾握故?#xff0c;怎么辦呢?
利用slot作用域插槽就可以了
<body><div id="app"><cpn></cpn><cpn><!--目的是獲取子組件中的pLanguages--><template slot-scope="slotProps"><span v-for="item in slotProps.data"> {{item}} - </span><!-- <span>{{slotProps.data.join(' - ')}}</span> --></template></cpn><cpn><!--目的是獲取子組件中的pLanguages--><template slot-scope="slotProps"><!--<span v-for="item in slotProps.data">{{item}} * </span>--><span>{{slotProps.data.join(' * ')}}</span></template></cpn><!--<cpn></cpn>--></div><template id="cpn"><div><slot :data = "pLanguages"><ul><li v-for="item in pLanguages">{{item}}</li></ul></slot></div> </template><script src="../Vue基礎(chǔ)/js/vue.js"></script><script>const app = new Vue({el: '#app',data: {message: '你好啊'},components: {cpn: {template: '#cpn',data() {return {pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']}}}}})</script> </body>父組件使用我們的子組件時(shí),從子組件中拿到數(shù)據(jù):
我們通過獲取到slotProps屬性
在通過slotProps.data就可以獲取到剛才我們傳入的data了
總結(jié)
- 上一篇: php json encode 参数,P
- 下一篇: vue中图片解析失败