W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
熟悉自定義組件的開發(fā),了解父子組件之間的通信方式,如:props,data,$dispatch(),$broadcast()通過本節(jié),你將學(xué)會(huì):
組件自定義
開發(fā)頁面時(shí)開發(fā)者必須用到 Native 組件,如:?
text
?、?div
?,這些組件是由各平臺 Native 底層渲染出來的;如果開發(fā)一個(gè)復(fù)雜的頁面,開發(fā)者把所有的 UI 部分寫在一個(gè)文件的?<template>
?,那代碼的可維護(hù)性將會(huì)很低,并且模塊之間容易產(chǎn)生不必要的耦合關(guān)系為了更好的組織邏輯與代碼,可以把頁面按照功能拆成多個(gè)模塊,每個(gè)模塊負(fù)責(zé)其中的一個(gè)功能部分,最后頁面將這些模塊引入管理起來,傳遞業(yè)務(wù)與配置數(shù)據(jù)完成代碼分離,那么這就是?
自定義組件
?的意義自定義組件是一個(gè)開發(fā)者編寫的組件,使用起來和 Native 一樣,最終按照組件的?
<template>
?來渲染;同時(shí)開發(fā)起來又和頁面一樣,擁有 ViewModel 實(shí)現(xiàn)對數(shù)據(jù)、事件、方法的管理這么來看,頁面也是一種特殊的自定義組件,無需引入即可使用,同時(shí)服務(wù)于整個(gè)頁面
示例如下:
<template> <div class="tutorial-page"> <text class="tutorial-title">自定義組件:</text> <text>{{ say }}</text> <text>{{ obj.name }}</text> </div> </template> <style lang="less"> .tutorial-page { flex-direction: column; padding-top: 20px; .tutorial-title { font-weight: bold; } } </style> <script> // 子組件 export default { data: { say: 'hello', obj: { name: 'quickApp' } }, /* data(){ return { say:'hello', obj:{ name:'quickApp' } } }, */ onInit() { console.log('我是子組件') } } </script>
自定義組件中數(shù)據(jù)模型只能使用data 屬性,data 類型可以是 Object 或 Function。如果是函數(shù),返回結(jié)果必須是對象。
組件引入
快應(yīng)用中是通過?
<import>
?標(biāo)簽引入組件,如下面代碼所示<import name="XXX" src="XXX"></import>
?
<import>
?標(biāo)簽中的的?src
?屬性指定自定義組件的地址,?name
?屬性指定在父組件中引用該組件時(shí)使用的標(biāo)簽名稱示例如下:
<import name="comp-part1" src="./part1"></import> <template> <div class="tutorial-page"> <text class="tutorial-title">引入組件:</text> <comp-part1></comp-part1> </div> </template> <style lang="less"> .tutorial-page { flex-direction: column; padding: 20px 10px; .tutorial-title { font-weight: bold; } } </style> <script> // 父組件 export default { private: {}, onInit() { this.$page.setTitleBar({ text: '引入組件' }) } } </script>
父子組件通信
父組件通過 Prop 向子組件傳遞數(shù)據(jù)
父組件向子組件傳遞數(shù)據(jù),通過在子組件的
props
屬性中聲明對外暴露的屬性名稱,然后在組件引用標(biāo)簽
上聲明傳遞的父組件數(shù)據(jù),詳見Props示例如下:
<!-- 子組件 --> <template> <div class="child-demo"> <text class="title">子組件:</text> <text>{{ say }}</text> <text>{{ propObject.name }}</text> </div> </template> <script> export default { props: ['say', 'propObject'], onInit() { console.info(`外部傳遞的數(shù)據(jù):`, this.say, this.propObject) } } </script>
<!-- 父組件 --> <import name="comp" src="./comp"></import> <template> <div class="parent-demo"> <comp say="{{say}}" prop-object="{{obj}}"></comp> </div> </template> <script> export default { private: { say:'hello', obj:{ name:'child-demo' } } } </script>
子組件對父組件通信
當(dāng)子組件對數(shù)據(jù)進(jìn)行改造后,把最終數(shù)據(jù)交給父組件甚至往上,往往有三種辦法
- 父組件傳遞的數(shù)據(jù)本身就是對象,子組件直接修改對象中的屬性,父組件的值也會(huì)發(fā)生改變,不推薦這種;
- 子組件通過
$dispatch()
觸發(fā)自定義事件,父組件通過$on()
監(jiān)控自定義事件的觸發(fā),如:del;- 子組件通過
$emit()
觸發(fā)在節(jié)點(diǎn)上綁定的自定義事件來執(zhí)行父組件的方法,如:add;示例如下:
<!-- 父組件 --> <import name="comp1" src="./comp1.ux"></import> <import name="comp2" src="./comp2.ux"></import> <import name="comp3" src="./comp3.ux"></import> <template> <div class="parent-demo"> <text>我是父組件count:{{count}}</text> <comp1 count="{{count}}" onemit-evt="emitEvt"></comp1> <text>我是父組件num:{{num}}</text> <comp2 num="{{num}}"></comp2> <text>我是父組件age:{{age}}</text> <input type="button" onclick="evtTypeEmit" value="觸發(fā)$broadcast()"></input> <comp3></comp3> </div> </template> <script> export default { private:{ count:20, num:20, age:18 }, onInit(){ this.$on('dispathEvt',this.dispathEvt) }, emitEvt(evt){ this.count = evt.detail.count }, dispathEvt(evt){ this.num = evt.detail.num }, evtTypeEmit(){ this.$broadcast('broadevt',{ age:19 }) }, } </script>
<!-- comp1 --> <template> <div class="child-demo"> <text>我是子組件一count:{{compCount}}</text> <input type="button" onclick='addHandler' value='add'></input> </div> </template> <script> export default { props: ['count'], data(){ return{ compCount:this.count } }, addHandler(){ this.compCount ++ this.$emit('emitEvt',{ count:this.compCount }) }, } </script>
<!-- comp2 --> <template> <div class="child-demo"> <text>我是子組件二num:{{compNum}}</text> <input type="button" onclick='delHandler' value='del'></input> </div> </template> <script> export default { props: ['num'], data(){ return{ compNum:this.num } }, delHandler(){ this.compNum -- this.$dispatch('dispathEvt',{ num:this.compNum }) }, } </script>
<!-- comp3 --> <template> <div class="child-demo"> <text>我是子組件三age:{{compAge}}</text> </div> </template> <script> export default { props:[], data(){ return{ compAge:null } }, onInit(){ this.$on('broadevt',this.broadevt) }, broadevt(evt){ this.compAge = evt.detail.age } } </script>
所以,框架向開發(fā)者提供了雙向的事件傳遞
- 向下傳遞:父組件觸發(fā),子組件響應(yīng);調(diào)用parentVm.$broadcast()完成向下傳遞,如:broadevt
- 向上傳遞:子組件觸發(fā),父組件響應(yīng);調(diào)用childVm.$dispath()完成向上傳遞,如:evtType2
提示:
- 觸發(fā)時(shí)傳遞參數(shù),再接收時(shí)使用evt.detail來獲取參數(shù)
- 當(dāng)傳遞結(jié)束后,可以調(diào)用evt.stop()來結(jié)束傳遞,否則會(huì)一直傳遞下去
兄弟/跨級組件通信
傳統(tǒng)的兄弟組件通信、跨父子組件通信,可以通過
Publish/Subscribe
模型來完成,這里提供兩種實(shí)現(xiàn)思路:1) 開發(fā)者實(shí)現(xiàn)一個(gè) Pub/Sub 模型完成通信:
開發(fā)者單獨(dú)寫一個(gè) JS 文件,提供發(fā)布訂閱的能力;然后各個(gè)
ViewModel
引入這個(gè)JS文件;或者將其掛載在頁面級別的ViewModel
,子組件通過$root
引用到頁面級別的ViewModel
;/** * @file pubsub.js 提供發(fā)布訂閱的能力 */ /** * 提供Publish-Subscribe模型 */ export default class Pubsub { constructor(name) { this.name = name this.eventMap = {} } /** * 訂閱事件 * @param type {string} 事件名稱 * @param fn {function} 響應(yīng)函數(shù) * @param options {object} 暫時(shí)保留 * @return {*} */ subscribe(type, fn, options) { if (options && options.once) { const fnOnce = args => { fn(args) this.remove(type, fnOnce) } return this.subscribe(type, fnOnce) } this.eventMap[type] = this.eventMap[type] || [] if (typeof fn === 'function') { const list = this.eventMap[type] if (list.indexOf(fn) === -1) { list.push(fn) } } } /** * 發(fā)布事件 * @param type {string} 事件名稱 * @param args {array} 事件觸發(fā)時(shí)的參數(shù) * @return {*} */ publish(type, args) { let lastRet = null const list = this.eventMap[type] || [] for (let i = 0, len = list.length; i < len; i++) { lastRet = list[i](args, lastRet) } return lastRet } /** * 刪除事件訂閱 * @param type {string} 事件名稱 * @param fn {function} 響應(yīng)函數(shù) */ remove(type, fn) { if (!this.eventMap[type]) return const list = this.eventMap[type] const index = list.indexOf(fn) if (index > -1) { list.splice(index, 1) } } } // 實(shí)例緩存 const modelCache = {} /** * 用于創(chuàng)建或獲取一個(gè)指定名稱的Pubsub模型的實(shí)例 * @param name {string} 通過名稱創(chuàng)建不同的實(shí)例 * @return {*} */ export function createOrRetrieveInst (name) { if (!modelCache[name]) { modelCache[name] = new Pubsub(name) } return modelCache[name] }
接著,UX文件中引入進(jìn)來,綁定在
ViewModel
上面,例如:<script> import { createOrRetrieveInst } from './pubsub.js' export default { onReady () { // 1. 實(shí)例化:并綁定在VM上 this.pubsubModel = createOrRetrieveInst() // 2. 訂閱:其它VM也可以調(diào)用 this.pubsubModel.subscribe('count-add', function (vArg0, vArg1){ ... }) // 3. 發(fā)布:其它VM也可以調(diào)用 this.pubsubModel.publish('count-add', ['arg0', 'arg1']) } } </script>
子組件通過
$root
引用,例如:<script> export default { onReady () { // 1. 訂閱 this.$root().pubsubModel.subscribe('count-add', function (vArg0, vArg1){ ... }) // 2. 發(fā)布 this.$root().pubsubModel.publish('count-add', ['arg0', 'arg1']) } } </script>
開發(fā)者通過上面的方式完成解耦,示例代碼僅供參考;
2) 利用框架本身提供的事件綁定接口:
在業(yè)務(wù)邏輯相對簡單的情況下,也可以使用
ViewModel
本身的事件綁定來處理:$on()
,$emit()
。示例如下:
子組件定義了 Sub 端的邏輯處理,有
processMessage()
、customEventInVm2()
,后者同使用$on
效果一致<template> <div class="tutorial-page"> <text class="tutorial-title">自定義組件2:</text> <text>處理消息:{{msg}}</text> <text>事件內(nèi)容:{{eventDetail}}</text> </div> </template> <style lang="less"></style> <script> // 子組件: part2 export default { props: [], data() { return { msg: null, eventDetail: null } }, processMessage(msg) { const now = new Date().toISOString() this.msg = `${now}: ${msg}` }, /** * 通過events對象:綁定事件 */ events: { customEventInVm2(evt) { const now = new Date().toISOString() this.eventDetail = `${now}: ${evt.detail}` } } } </script>
另外一個(gè)兄弟組件可以通過父組件中建立相互引用達(dá)到相互持有
ViewModel
的目的,通過在生命周期onReady()
中執(zhí)行establishRef()
實(shí)現(xiàn),如下代碼所示:<!-- 父組件 --> <import name="comp-part2" src="./part2"></import> <import name="comp-part3" src="./part3"></import> <template> <div class="tutorial-page"> <!-- 兄弟VM通信 --> <comp-part2 id="sibling1"></comp-part2> <comp-part3 id="sibling2"></comp-part3> </div> </template> <style lang="less"></style> <script> export default { onReady() { this.establishRef() }, /** * 建立相互VM的引用 */ establishRef() { const siblingVm1 = this.$vm('sibling1') const siblingVm2 = this.$vm('sibling2') siblingVm1.parentVm = this siblingVm1.nextVm = siblingVm2 siblingVm2.parentVm = this siblingVm2.previousVm = siblingVm1 } } </script>
那么另外一個(gè)子組件的 Pub 端定義就很簡單了,執(zhí)行
sendMesssage()
即可完成觸發(fā),如下代碼所示:<template> <div class="tutorial-page"> <text class="tutorial-title">自定義組件3:</text> <text onclick="sendMesssage">點(diǎn)擊發(fā)送消息</text> </div> </template> <style lang="less"></style> <script> // 子組件: part3 export default { sendMesssage() { if (this.previousVm) { // Way1. 調(diào)用方法 this.previousVm.processMessage('兄弟之間通信的消息內(nèi)容') // Way2. 觸發(fā)事件 this.previousVm.$emit('customEventInVm2', '兄弟之間通信的消息內(nèi)容') } } } </script>
總結(jié)
自定義組件有助于更好的組織代碼邏輯,結(jié)構(gòu)更清晰;了解父子組件之間的數(shù)據(jù)通信,是開發(fā)自定義組件的必備技能
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: