vue.js 精學組件記錄
組件需要註冊後才可以使用。
Vue.component(‘my-component‘,{
template:‘<div>這是組件內容</div>‘
});
局部註冊組件
var Child = { template:‘<div>局部註冊組件內容</div>‘ } var app = new Vue({ el:"#app", components: { ‘my-component‘:Child } })
Vue組件模板在某些情況下會受到限制,比如table,ul,ol.select等,這時可以類似這樣:
<table> <tbody is="my-component"></tbody> </table>
還可使用數據綁定,但data必須是函數:
Vue.component(‘my-component‘,{ template:‘<div>{{message}}</div>‘, data:function(){ return { message:‘局部註冊內容,data方法return‘ } } })
使用props傳遞數據
<my-component message="來自父組件的信息"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message}}</div>‘ })
父組件動態數據:
<input type="text" v-model="message"/> <my-component :message="message"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message}}</div>‘ }) var app = new Vue({ el:"#app", data:{ message:‘‘ } })
當輸入框輸入時,子組件接收到的props "message" 也會實時響應,並更新組件模板。
註意:
如果直接傳遞數字,布爾值,數組,對象,而不是用v-bind,傳遞的是字符串。
<my-component message="[1,2,3]"></my-component> <my-component :message="[1,2,3]"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message.length}}</div>‘ })
此時上一個會輸出字符串的長度7,下面才是輸出數組的長度3。
單向數據流
父組件傳遞初始值進來,子組件作為初始值保存,在自己的作用域下可隨意修改。
<div id="app"> <my-component :init-count="1"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘initCount‘], template:‘<div>{{count}}</div>‘, data:function(){ return { count:this.initCount+1 } } }) var app = new Vue({ el:"#app", data:{ message:‘‘ } }) </script>
<div id="app"> <my-component :width="100"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘width‘], template:‘<div :style="style">組件內容</div>‘, computed:{ style:function(){ return { width:this.width+"px", background:‘red‘ } } } }) var app = new Vue({ el:"#app", data:{ message:‘‘ } }) </script>
子組件傳遞數值到父組件:
<div id="app"> <p>總數:{{total}}</p> <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div><button @click="handleIncrease">+1</button><button @click="handleReduce">-1</button></div>‘, data:function(){ return { counter: 0 } }, methods:{ handleIncrease:function(){ this.counter++; this.$emit(‘increase‘,this.counter); }, handleReduce:function(){ this.counter--; this.$emit(‘reduce‘,this.counter); } } }); var app = new Vue({ el:"#app", data:{ total:0 }, methods:{ handleGetTotal:function(total){ this.total=total console.log(total) } } });
在子組件定義兩個事件,改變自身數據的同時使用$emit來觸發父組件的自定義事件名稱,從而觸發對應的方法,再把改變的數據通過方法傳到父組件的方法,從而改變父組件的數據。
使用v-model
<div id="app"> <p>總數:{{total}}</p> <my-component v-model="total"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div><button @click="handleIncrease">+1</button></div>‘, data:function(){ return { counter: 0 } }, methods:{ handleIncrease:function(){ this.counter++; this.$emit(‘input‘,this.counter); } } }); var app = new Vue({ el:"#app", data:{ total:0 } }); </script>
這次區別是$emit通知的是特殊的input,但是並沒有在組件上使用@input,這是因為v-model,這也可以稱作是一個語法糖。
因為上面的示例可以間接實現:
<my-component @input="total"></my-component>
v-model還可以用來創建自定義的表單輸入組件,進行數據雙向綁定:
<div id="app"> <p>總數:{{total}}</p> <my-component v-model="total"></my-component> <button @click="handleReduce">-1</button> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘value‘], template:‘<input :value="value" @input="updateValue" />‘, methods:{ updateValue:function(event){ this.$emit(‘input‘,event.target.value); } } }); var app = new Vue({ el:"#app", data:{ total:0 }, methods:{ handleReduce:function(){ this.total--; } } }); </script>
子組件input改變value值後出發updateValue,通過$emit觸發特殊的input事件,傳值輸入的value值,改掉父組件的total,父組件的handleReduce方法改變total後,由於是雙向綁定,所以子組件的value值也隨著改變。
這需要滿足兩個條件:
1.接收一個props的value值,
2.更新value觸發特殊的input事件
非父子組件通信
在vue1.x中是采用$dispatch()和$broadcast()這兩個方法。$dispatch()由於向上級派發事件,只要是它的父級,都可在vue實例的events選項內接收。
在vue2.x中廢棄了上述兩種方法。在vue2.x中推薦使用一個空的vue實例作為中央事件總線(bus),也就是一個中介。
如下:
<div id="app"> <p>信息:{{message}}</p> <my-component></my-component> </div> <script type="text/javascript"> var bus = new Vue(); Vue.component(‘my-component‘,{ template:‘<button @click="handleEvent"></button>‘, methods:{ handleEvent:function(event){ bus.$emit(‘on-message‘,‘來自組件component的內容‘); } } }); var app = new Vue({ el:"#app", data:{ message:‘‘ }, mounted:function(){ var _this = this; bus.$on(‘on-message‘,function(msg){ _this.message=msg; }) } }); </script>
定義一個空的vue實例當做中間人,在鉤子函數mounted中監聽了來自bus的事件on-message,在組件中會點擊按鈕通過bus把事件on-message發出去,此時app會接收到來自bus的事件。
除了中間介這種方式外,還有兩種方法可實現組件間的通信:父鏈和子組件索引。
父鏈
子組件中使用this.$parent可直接訪問父實例或組件,父組件也可以通過this.$children訪問它所有子組件。
<div id="app"> <p>信息:{{message}}</p> <my-component></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<button @click="handleEvent">通過$parent改變信息內容</button>‘, methods:{ handleEvent:function(event){ this.$parent.message=‘子組件通過$parent改變了信息內容‘; } } }); var app = new Vue({ el:"#app", data:{ message:‘‘ } }); </script>
子組件內通過this.$parent.message直接修改信息內容,正常使用不推薦使用。因為這樣使得父子組件緊耦合,只看父組件,很難理解父組件狀態,因為它可能被任意組件修改。最好還是通過props和$emit來通信。
子組件索引
<div id="app"> <button @click="handleRef">通過red獲取子組件實例</button> <my-component ref="Mycom"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div>子組件</div>‘, data:function() { return { message:‘子組件內容‘ } } }); var app = new Vue({ el:"#app", methods:{ handleRef:function(){ var msg = this.$refs.Mycom.message; console.log(msg) } } }); </script>
父組件內的內容通過this.$refs.Mycom找到子組件,並輸出了子組件的內容。
使用slot分發內容
當需要讓組件組合使用,混合父組件發的內容與子組件的模板時,就會用到slot,這個過程叫做內容分發。
slot用法
單個slot: 在子組件標簽內的素有內容替代子組件的<slot>標簽及它的內容。如下:
<div id="app"> <child-component> <p>分發的內容</p> <p>更多分發的內容</p> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:‘<div><slot><p>如果沒有父組件插入內容,我將默認出現</p></slot></div>‘, }); var app = new Vue({ el:"#app", }); </script>
當父組件沒有插入分發的內容,即上述代碼沒有
<p>分發的內容</p>
<p>跟多分發的內容</p>
的時候,子組件將默認顯示子組件裏slot的內容,如果父組件裏插入了分發的內容,則將替換掉子組件裏slot標簽裏的全部內容。
具名slot
給slot元素指定一個name後可以分發多個內容,具名slot可以與單個slot共存。
如下:
<div id="app"> <child-component> <h2 slot="header">標題</h2> <p>正文內容</p> <p>更多正文內容</p> <div slot="footer">底部信息</div> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:`<div class="container"> <div class="header"> <slot name="header"></slot> </div> <div class="main"> <slot></slot> </div> <div class="footer"> <slot name="footer"></slot> </div> </div>`, }); var app = new Vue({ el:"#app", }); </script>
在父組件上面可以具名插入slot的name,然後子組件會更具name插入具體的內容。
作用域插槽
是一種特殊的slot使用一個可以復用的模板替換已渲染的元素。
<div id="app"> <child-component> <template scope="props"> <p>來自父組件的內容</p> <p>{{props.msg}}</p> </template> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:`<div class="container"> <slot msg="來自子組件的內容"></slot> </div>`, }); var app = new Vue({ el:"#app", }); </script>
子組件模板,在slot元素上有一個類似pros傳遞數據給組件的寫法 msg="XXX",將數據傳到了插槽。父組件使用了template且擁有一個scope="props"的特性,這裏的props知識一個臨時變量,通過臨時變量props來訪問來自子組件的數據msg。渲染的最終結果為:
<div id="app"> <div class="container"> <p>來自父組件的內容</p> <p>來自子組件的內容</p> </div> </div>
作用域插槽具代表性的用例是列表組件,允許組件自定義應該如何渲染列表的每一頁。
<div id="app"> <my-list :book="books"> <!--作用域插槽也可以是具名的slot--> <template slot="book" scope="props"> <li>{{props.bookName}}</li> </template> </my-list> </div> <script type="text/javascript"> Vue.component(‘my-list‘,{ props:{ books:{ type:Array, default:function(){ return []; } } }, template:‘<ul><slot name="book" v-for="book in books" :bookName="book.name"></slot></ul>‘, }); var app = new Vue({ el:"#app", data:{ books:[ {name:‘js‘}, {name:‘css‘}, {name:‘html‘} ] } }); </script>
子組件my-list接收一個來自父級的prop數組books,並且將它在name為book的slot上使用v-for指令循環,同時暴露一個變量bookName。
訪問slot
vue2.x提供了用來訪問被slot分發的內容的方法$slots。
<div id="app"> <my-component> <h2 slot="header">標題</h2> <p>正文內容</p> <div slot="footer">底部信息</div> </my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:`<div class="container"> <div class="header"> <slot name="header"></slot> </div> <div class="main"> <slot></slot> </div> <div class="footer"> <slot name="footer"></slot> </div> </div>`, mounted:function(){ var header = this.$slots.header; var main = this.$slots.default; var footer = this.$slots.footer; console.log(footer[0].elm.innerHTML) } }); var app = new Vue({ el:"#app", }); </script>
在子組件鉤子函數中能用$slots得到對應的slot。
組件的高級用法
遞歸組件
組件可以在它的模板內調用自己,只要給組件設置name屬性就可以。
<div id="app"> <my-component :count="1"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ name:‘my-component‘, props:{ count:{ type:Number, default:function(){ return 1; } } }, template:‘<div class="child"><my-component :count="count+1" v-if="count<3"></my-component></div>‘, }); var app = new Vue({ el:"#app", }); </script>
設置name後就可以遞歸使用了,不過需要一個限制條件,不然會報錯。
內聯模板
vue提供了一個內斂模板的功能,在使用組件時,給組件標簽使用inline-template特性,組件就會把它的內容當做模板,而不是把它當內容分發。
<div id="app"> <my-component inline-template> <div> <h2>父組件定義子組件模板</h2> <p>{{message}}</p> <p>{{msg}}</p> </div> </my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ data:function(){ return { msg:‘子組件內容‘ } } }); var app = new Vue({ el:"#app", data:{ message:‘父組件內容‘ } }); </script>
渲染成:
<div> <h2>父組件定義子組件模板</h2> <p>父組件內容</p> <p>子組件內容</p> </div>
使用內聯模板會導致作用域混淆,應避免使用。
動態組件
vue.js提供了一個特殊的元素<component>用來動態掛載不同的組件,使用is特性來選擇掛載的組件。
<div id="app"> <component :is="currentView"></component> <button @click="handleChangeView(‘A‘)">切換到A</button> <button @click="handleChangeView(‘B‘)">切換到B</button> <button @click="handleChangeView(‘C‘)">切換到C</button> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", components:{ comA:{ template:‘<div>A模板</div>‘ }, comB:{ template:‘<div>B模板</div>‘ }, comC:{ template:‘<div>C模板</div>‘ }, }, data:{ currentView:‘comA‘, }, methods:{ handleChangeView:function (component){ this.currentView=‘com‘+component } } }); </script>
動態地改變currentView就可以切換視圖。
異步組件
當工程足夠大,使用組件足夠多時,是時候考慮下性能了,因為一開始把所有組件都加載時時一筆開銷。vue.js允許將組件定義為一個工廠函數,動態地解析組件。
<div id="app"> <my-component></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,function(resolve,reject){ setTimeout(()=>{ resolve({ template:‘<div>我是異步渲染的</div>‘ }) },2000) }); var app = new Vue({ el:"#app", }); </script>
$nextTick
當v-if,動態切換顯示時,直接去取div的內容時獲取不到的,因為此時div還沒有被創建出來。這裏涉及到一個vue的重要概念:異步更新隊列。
vue在觀察到數據變化時並不是直接更新DOM,而是開啟一個隊列,並緩沖在同一事件循環中發生的數據變化。
$nextTick就是用來知道DOM什麽時候更新好的。
X-template
沒有使用webpack,gulp等工具,組件內容復雜,如果都在javascript中拼接字符串,效率很低,vue提供了另一種定義模板的方式,在script標簽使用text/template類型,指定一個ID,將這個id賦給template。
<div id="app"> <my-component></my-component> <script type="text/x-template" id="my-component"> <div>這是組件內容</div> </script> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:"#my-component" }); var app = new Vue({ el:"#app", }); </script>
vue的初衷不是濫用它,因為它將模板和組件的其他定義隔離了。
vue.js 精學組件記錄