<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在微信小程式開發過程中,對於一些可能在多個頁面都使用的頁面模組,可以把它封裝成一個元件,以提高開發效率。雖然說我們可以引入整個元件庫比如 weui、vant 等,但有時候考慮微信小程式的包體積限制問題,通常封裝為自定義的元件更為可控。
並且對於一些業務模組,我們就可以封裝為元件複用。本文主要講述以下兩個方面:
微信小程式的元件系統底層是通過 Exparser 元件框架實現,它內建在小程式的基礎庫中,小程式內的所有元件,包括內建元件和自定義元件都由 Exparser 組織管理。
自定義元件和寫頁面一樣包含以下幾種檔案:
以編寫一個 tab 元件為例: 編寫自定義元件時需要在 json 檔案中講 component 欄位設為 true:
{ "component": true }
在 js 檔案中,基礎庫提供有 Page 和 Component 兩個構造器,Page 對應的頁面為頁面根元件,Component 則對應:
Component({ options: { // 元件設定 addGlobalClass: true, // 指定所有 _ 開頭的資料欄位為純資料欄位 // 純資料欄位是一些不用於介面渲染的 data 欄位,可以用於提升頁面更新效能 pureDataPattern: /^_/, multipleSlots: true // 在元件定義時的選項中啟用多slot支援 }, properties: { vtabs: {type: Array, value: []}, }, data: { currentView: 0, }, observers: { // 監測 activeTab: function(activeTab) { this.scrollTabBar(activeTab); } }, relations: { // 關聯的子/父元件 '../vtabs-content/index': { type: 'child', // 關聯的目標節點應為子節點 linked: function(target) { this.calcVtabsCotentHeight(target); }, unlinked: function(target) { delete this.data._contentHeight[target.data.tabIndex]; } } }, lifetimes: { // 元件宣告週期 created: function() { // 元件範例剛剛被建立好時 }, attached: function() { // 在元件範例進入頁面節點樹時執行 }, detached: function() { // 在元件範例被從頁面節點樹移除時執行 }, }, methods: { // 元件方法 calcVtabsCotentHeight(target) {} } });
如果有了解過 Vue2 的小夥伴,會發現這個宣告很熟悉。
在小程式啟動時,構造器會將開發者設定的properties、data、methods等定義段,
寫入Exparser的元件登入檔中。這個元件在被其它元件參照時,就可以根據這些註冊資訊來建立自定義元件的範例。
模版檔案 wxml:
<view class='vtabs'> <slot /> </view>
樣式檔案:
.vtabs {}
外部頁面元件使用,只需要在頁面的 json 檔案中引入
{ "navigationBarTitleText": "商品分類", "usingComponents": { "vtabs": "../../../components/vtabs", } }
在初始化頁面時,Exparser 會建立出頁面根元件的一個範例,用到的其他元件也會響應建立元件範例(這是一個遞迴的過程):
元件建立的過程大致有以下幾個要點:
由於業務的負責度,我們常常需要把一個大型頁面拆分為多個元件,多個元件之間需要進行資料通訊。
對於跨代元件通訊可以考慮全域性狀態管理,這裡只討論常見的父子元件通訊:
用於父元件向子元件的指定屬性設定資料。
子宣告 properties 屬性
Component({ properties: { vtabs: {type: Array, value: []}, // 資料項格式為 `{title}` } })
父元件呼叫:
<vtabs vtabs="{{ vtabs }}"</vtabs>
用於子元件向父元件傳遞資料,可以傳遞任意資料。
子元件派發事件,先在 wxml 結構繫結子元件的點選事件:
<view bindtap="handleTabClick">
再在 js 檔案中進行派發事件,事件名可以自定義填寫, 第二個引數可以傳遞資料物件,第三個引數為事件選項。
handleClick(e) { this.triggerEvent( 'tabclick', { index }, { bubbles: false, // 事件是否冒泡 // 事件是否可以穿越元件邊界,為 false 時,事件只在參照元件的節點樹上觸發, // 不進入其他任何元件的內部 composed: false, capturePhase: false // 事件是否擁有捕獲階段 } ); }, handleChange(e) { this.triggerEvent('tabchange', { index }); },
最後,在父元件中監聽使用:
<vtabs vtabs="{{ vtabs }}" bindtabclick="handleTabClick" bindtabchange="handleTabChange" >
通過 selectComponent 方法可以獲取子元件的範例,從而呼叫子元件的方法。
父元件的 wxml
<view> <vtabs-content="goods-content{{ index }}"></vtabs-content> </view>
父元件的 js
Page({ reCalcContentHeight(index) { const goodsContent = this.selectComponent(`#goods-content${index}`); }, })
selector類似於 CSS 的選擇器,但僅支援下列語法。
在電商/物流等微信小程式中,會存在這樣的使用者故事,有一個「下單頁面A」和「貨物資訊頁面B」
微信小程式由一個 App()
範例和多個 Page()
組成。小程式框架以棧的方式維護頁面(最多10個) 提供了以下 API 進行頁面跳轉,頁面路由如下
可以簡單封裝一個 jumpTo 跳轉函數,並傳遞引數:
export function jumpTo(url, options) { const baseUrl = url.split('?')[0]; // 如果 url 帶了引數,需要把引數也掛載到 options 上 if (url.indexof('?') !== -1) { const { queries } = resolveUrl(url); Object.assign(options, queries, options); // options 的優先順序最高 } cosnt queryString = objectEntries(options) .filter(item => item[1] || item[0] === 0) // 除了數位 0 外,其他非值都過濾 .map( ([key, value]) => { if (typeof value === 'object') { // 物件轉字串 value = JSON.stringify(value); } if (typeof value === 'string') { // 字串 encode value = encodeURIComponent(value); } return `${key}=${value}`; } ).join('&'); if (queryString) { // 需要組裝引數 url = `${baseUrl}?${queryString}`; } const pageCount = wx.getCurrentPages().length; if (jumpType === 'navigateTo' && pageCount < 5) { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } else { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } }
jumpTo 輔助函數:
export const resolveSearch = search => { const queries = {}; cosnt paramList = search.split('&'); paramList.forEach(param => { const [key, value = ''] = param.split('='); queries[key] = value; }); return queries; }; export const resolveUrl = (url) => { if (url.indexOf('?') === -1) { // 不帶引數的 url return { queries: {}, page: url } } const [page, search] = url.split('?'); const queries = resolveSearch(search); return { page, queries }; };
在「下單頁面A」傳遞資料:
jumpTo({ url: 'pages/consignment/index', { sender: { name: 'naluduo233' } } });
在「貨物資訊頁面B」獲得 URL 引數:
const sender = JSON.parse(getParam('sender') || '{}');
url 引數獲取輔助函數
// 返回當前頁面 export function getCurrentPage() { const pageStack = wx.getCurrentPages(); const lastIndex = pageStack.length - 1; const currentPage = pageStack[lastIndex]; return currentPage; } // 獲取頁面 url 引數 export function getParams() { const currentPage = getCurrentPage() || {}; const allParams = {}; const { route, options } = currentPage; if (options) { const entries = objectEntries(options); entries.forEach( ([key, value]) => { allParams[key] = decodeURIComponent(value); } ); } return allParams; } // 按欄位返回值 export function getParam(name) { const params = getParams() || {}; return params[name]; }
雖然微信小程式官方檔案沒有說明可以頁面攜帶的引數有多長,但還是可能會有引數過長被截斷的風險。
我們可以使用全域性資料記錄引數值,同時解決 url 引數過長和路由 api 不支援攜帶引數的問題。
// global-data.js // 由於 switchTab 不支援攜帶引數,所以需要考慮使用全域性資料儲存 // 這裡不管是不是 switchTab,先把資料掛載上去 const queryMap = { page: '', queries: {} };
更新跳轉函數
export function jumpTo(url, options) { // ... Object.assign(queryMap, { page: baseUrl, queries: options }); // ... if (jumpType === 'switchTab') { wx.switchTab({ url: baseUrl }); } else if (jumpType === 'navigateTo' && pageCount < 5) { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } else { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } }
url 引數獲取輔助函數
// 獲取頁面 url 引數 export function getParams() { const currentPage = getCurrentPage() || {}; const allParams = {}; const { route, options } = currentPage; if (options) { const entries = objectEntries(options); entries.forEach( ([key, value]) => { allParams[key] = decodeURIComponent(value); } ); + if (isTabBar(route)) { + // 是 tab-bar 頁面,使用掛載到全域性的引數 + const { page, queries } = queryMap; + if (page === `${route}`) { + Object.assign(allParams, queries); + } + } } return allParams; }
輔助函數
// 判斷當前路徑是否是 tabBar const { tabBar} = appConfig; export isTabBar = (route) => tabBar.list.some(({ pagePath })) => pagePath === route);
按照這樣的邏輯的話,是不是都不用區分是否是 isTabBar 頁面了,全部頁面都從 queryMap 中獲取?這個問題目前後續探究再下結論,因為我目前還沒試過從 頁面範例的 options 中拿到的值是缺少的。所以可以先保留讀取 getCurrentPages 的值。
前面我談到從「當前頁面A」傳遞資料到被開啟的「頁面B」可以通過 url 引數。那麼想獲取被開啟頁面傳送到當前頁面的資料要如何做呢?是否也可以通過 url 引數呢?
答案是可以的,前提是不需要儲存「頁面A」的狀態。如果要保留「頁面 A」的狀態,就需要使用 navigateBack 返回上一頁,而這個 api 是不支援攜帶 url 引數的。
這樣時候可以使用 頁面間事件通訊通道 EventChannel。
pageA 頁面
// wx.navigateTo({ url: 'pageB?id=1', events: { // 為指定事件新增一個監聽器,獲取被開啟頁面傳送到當前頁面的資料 acceptDataFromOpenedPage: function(data) { console.log(data) }, }, success: function(res) { // 通過eventChannel向被開啟頁面傳送資料 res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' }) } });
pageB 頁面
Page({ onLoad: function(option){ const eventChannel = this.getOpenerEventChannel() eventChannel.emit('acceptDataFromOpenedPage', {data: 'test'}); // 監聽acceptDataFromOpenerPage事件,獲取上一頁面通過eventChannel傳送到當前頁面的資料 eventChannel.on('acceptDataFromOpenerPage', function(data) { console.log(data) }) } })
小程式的棧不超過 10 層,如果當前「頁面A」不是第 10 層,那麼可以使用 navigateTo 跳轉保留當前頁面,跳轉到「頁面B」,這個時候「頁面B」填寫完畢後傳遞資料給「頁面A」時,「頁面A」是可以監聽到資料的。
如果當前「頁面A」已經是第10個頁面,只能使用 redirectTo 跳轉「PageB」頁面。結果是當前「頁面A」出棧,新「頁面B」入棧。這個時候將「頁面B」傳遞資料給「頁面A」,呼叫 navigateBack 是無法回到目標「頁面A」的,因此資料是無法正常被監聽到。
不過我分析做過的小程式中,棧中很少有10層的情況,5 層的也很少。因為呼叫 wx.navigateBack 、wx.redirectTo 會關閉當前頁面,呼叫 wx.switchTab 會關閉其他所有非 tabBar 頁面。
所以很少會出現這樣無法回到上一頁面以監聽到資料的情況,如果真出現這種情況,首先要考慮的不是資料的監聽問題了,而是要保證如何能夠返回上一頁面。
比如在「PageA」頁面中先呼叫 getCurrentPages 獲取頁面的數量,再把其他的頁面刪除,之後在跳轉「PageB」頁面,這樣就避免「PageA」呼叫 wx.redirectTo
導致關閉「PageA」。但是官方是不推薦開發者手動更改頁面棧的,需要慎重。
如果有讀者遇到這種情況,並知道如何解決這種的話,麻煩告知下,感謝。
除了使用官方提供的 EventChannel 外,我們也可以自定義一個全域性的 EventBus 事件中心。 因為這樣更加靈活,不需要在呼叫 wx.navigateTo
等APi裡傳入引數,多平臺的遷移性更強。
export default class EventBus { private defineEvent = {}; // 註冊事件 public register(event: string, cb): void { if(!this.defineEvent[event]) { (this.defineEvent[event] = [cb]); } else { this.defineEvent[event].push(cb); } } // 派遣事件 public dispatch(event: string, arg?: any): void { if(this.defineEvent[event]) {{ for(let i=0, len = this.defineEvent[event].length; i<len; ++i) { this.defineEvent[event][i] && this.defineEvent[event][i](arg); } }} } // on 監聽 public on(event: string, cb): void { return this.register(event, cb); } // off 方法 public off(event: string, cb?): void { if(this.defineEvent[event]) { if(typeof(cb) == "undefined") { delete this.defineEvent[event]; // 表示全部刪除 } else { // 遍歷查詢 for(let i=0, len=this.defineEvent[event].length; i<len; ++i) { if(cb == this.defineEvent[event][i]) { this.defineEvent[event][i] = null; // 標記為空 - 防止dispath 長度變化 // 延時刪除對應事件 setTimeout(() => this.defineEvent[event].splice(i, 1), 0); break; } } } } } // once 方法,監聽一次 public once(event: string, cb): void { let onceCb = arg => { cb && cb(arg); this.off(event, onceCb); } this.register(event, onceCb); } // 清空所有事件 public clean(): void { this.defineEvent = {}; } } export connst eventBus = new EventBus();
在 PageA 頁面監聽:
eventBus.on('update', (data) => console.log(data));
在 PageB 頁面派發
eventBus.dispatch('someEvent', { name: 'naluduo233'});
本文主要討論了微信小程式如何自定義元件,涉及兩個方面:
如果你使用的是 taro 的話,直接按照 react 的語法自定義元件就好。而其中的元件通訊的話,因為 taro 最終也是會編譯為微信小程式,所以 url 和 eventbus 的頁面元件通訊方式是適用的。後續會分析 vant-ui weapp 的一些元件原始碼,看看有贊是如何實踐的。
1、點選一個資料夾右鍵——新建Page 、新建Component
2、元件的js檔案
Component({ properties: { //父元件傳過來的data num: Number, flag:Boolean }, /** * 頁面的初始資料 */ data: { }, methods:{ //元件的方法要寫在methods中 jia(e) { let a if (this.properties.flag) { a = -1; } else { a = 1; } this.setData({ flag: !this.properties.flag, num: this.properties.num + a }) } },
3、頁面的js檔案
Page({ data: { flag:false, number:1, motto: 'Hello World', }, //事件處理常式,直接作為引數 bindViewTap: function() { wx.navigateTo({ url: '../logs/logs' }) }, })
到此這篇關於微信小程式自定義元件的文章就介紹到這了,更多相關微信小程式自定義元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45