摘要:我們參考小程序的設計思路進行了優化升級,為每一個需要特有化配置的頁面添加一個格式的配置文件,配置文件包括導航欄的配置頁面級別的配置跳轉的配置等,將配置工程化標準化。設置導航欄按鈕包含按鈕樣式的數組通過完成按鈕事件的回調。
一、背景1.為什么是Weex在公司快速發展的大環境下,App的更新迭代高速、高頻,技術團隊平均兩周便可誕生一款中型App,但App團隊只有6個人(iOS 、Android各3人),在確保效率、質量的前提下,單純依靠Native的能力顯得步履蹣跚——我們亟需提升團隊效率,希望單人可完成原本2~3人的工作量。
其一,接入Web頁面,一個頁面適配兩端;
其二,選擇Weex、React Native、Flutter、Chameleon等跨平臺開發框架,主流框架對比如下:
對比內容 | React Native | Flutter | Weex |
上手難度 | 一般 | 一般 | 容易 |
接入特點 | 適合開發整體App | 適合開發整體App | 適合單頁面 |
維護難度 | 一般 | 一般 | 容易 |
開發語言 | React | Dart | Vue、Rax |
框架體量 | 較重 | 重 | 較輕 |
Bundle大小 | 較大 | 不需要 | 較小 |
社區 | 豐富 | 新起之秀 | 不夠完善 |
支持終端 | Android、iOS | Android、iOS、Web等 | Android、iOS、Web |
引擎 | JSCore、V8 | Flutter Engine | JSCore、V8 |
通過對比,最終選擇了Weex,有以下幾個主要原因:
Weex的上手成本較低,且單頁面的支持更符合項目規劃;
Vue框架,契合團隊的大前端環境;
Weex承接了淘寶、飛豬等App的大量頁面,給予外界充足的信心。
雖然Web頁面的上手、維護成本更低,但與Weex相比,同等頁面的Web包體積要比Weex大,Web頁面無法做到純Native的體驗,且頁面加載速度很難極致化,在某些設備上容易出現白屏。Weex所具備的下列優勢,足以讓一個追求極致的團隊所青睞。
二、Weex基本原理
Weex支持 Vue 和 Rax兩個前端框架,由于前端團隊使用Vue進行日常開發,為了降低上手成本,我們選擇Vue框架進行Weex開發。Weex的基本工作流程如下:
Weex we 文件 --------------前端(we源碼)
↓ (轉換) ------------------前端(構建過程)
JS Bundle -----------------前端(JS Bundle代碼)
↓ (部署) ------------------服務器或本地
在服務器或本地上的JS bundle ----服務器或本地
↓ (編譯) ------------------ 客戶端(JS引擎)
虛擬 DOM 樹 --------------- 客戶端(Weex JS Framework)
↓ (渲染) ------------------ 客戶端(渲染引擎)
Native視圖 --------------- 客戶端(渲染引擎)
來自官網
除去前端頁面的編寫,Weex可劃分為
DOM
、Render
、JSBridge
三大塊(線程)。DOM
負責JSBundle
的解析、綁定、映射等處理,并通知Render
線程進行UI的更新。而Render
線程,即UI線程,負責Component
的渲染,例如前端編寫的TextComponent。JSBridge
負責JS
端與Native
端的雙向通信,通信的具體邏輯處理則由原生實現的一個個Module來完成
。下圖是一個頁面被渲染的完整流程:三、客戶端系統架構
依托于Native,借助于前端,演變成大前端的架構,整體結構如下圖。我們希望App作為終端,提供容器的能力,做好底層服務,完美融合Weex、Web等跨平臺技術。
四、實踐與解決方案以下記錄了實踐中遇到的一些較為重要、常見的問題,及其解決方案,它們貫穿了Weex頁面的生命周期。
頁面間通信
路由
Weex自身提供的navigator只支持簡單的在線資源,而不支持本地文件的加載,同時無法滿足動態化的呈現方式和復雜參數傳遞,需要自行實現一套完整的頁面跳轉規則。
在大量使用了
Web、Weex
技術的前提下,為了保證頁面跳轉、頁面間參數傳遞、回調等的統一和便捷性,以及模塊間的解耦,跳轉目標可配置化,采用了路由的形式來作為頁面間跳轉的技術方案。基本的路由形式以及完整的過程如下:// 打開Web頁面 scheme:/web/open");
Weex中發起一次頁面跳轉的示例:
navigator.openUrl({
url: "scheme://weex/open",
params: {
bundleUrl: "/dist/about.js",
name: "這是下一個頁面所需的參數",
},
})
// 備注: 而下一個頁面只需要在data中聲明一個與參數同名的屬性來接收具體的參數即可。
反向傳值
我們在考慮反向傳值的問題時,主要涉及兩種場景,一是Weex頁面反向傳值給Weex頁面,二是Native反向傳值給Weex頁面。我們可以使用Weex提供的基于W3C 規范的
BroadcastChannel
來輕松滿足第一種場景。但是目前并沒有現成的API能滿足第二種場景,我們需要不斷的嘗試:在頁面跳轉時將
WXModuleKeepAliveCallback
傳入下一個頁面,然后在合適的時候執行該回調。這對于iOS客戶端很容易實現,但由于Android在頁面間傳值時需要將參數序列化到內存中,然后在對應的頁面從內存中反序列化出來,這會導致生成一個新的對象,從而無法完成回調。我們有嘗試借鑒
BroadcastChannel
的實現,通過Weex項目全局的JSContext對象來觸發廣播完成反向傳值,但最后無疾而終。我們最后選擇使用
fireGlobalEvent
,步驟如下:1.Weex添加事件監聽
const globalEvent = weex.requireModule("globalEvent");
globalEvent.addEventListener("eventName", (e) => {
// ...
});
2.原生頁面發送事件
weexInstance.fireGlobalEventCallback("eventName", params); // Android
[weexInstance fireGlobalEvent:seventName params:params]; // iOS
代碼中的weexInstance,可以理解為一個頁面的實例對象,該流程需要在發送監聽的頁面需要獲取上一個
Weex
頁面對應的weexInstance
,可以以一種相對優雅的方式告知下一級----
在跳轉的路由中添加一個needListen
來告知下一個頁面:navigator.openUrl({
url: "scheme://weex/open",
params: {
bundleUrl: "/dist/about.js",
needListen: true,
},
})
2. 頁面配置文件
起初Weex頁面導航欄、跳轉方式等配置,均在自建Module中進行處理,例如一個頁面要設置導航欄,我們需要在mounted方法中加入如下代碼:
created () {
navigator.setTitle("導航欄標題")
navigator.setItems([{
title: "按鈕",
}])
}
這種不夠工程化、標準化的方式給后期的維護、跨項目移植、架構升級造成了極大的干擾。
我們參考小程序的設計思路進行了優化升級,為每一個需要特有化配置的Weex頁面添加一個json格式的配置文件,配置文件包括導航欄的配置、頁面級別的配置、跳轉的配置等,將配置工程化、標準化。
例如我為about頁面添加了配置文件,json文件的類容為:
{
"navigationBarTitle": "導航欄標題",
"navigationItems": [{
"title": "按鈕"
}]
}
打包后的資源文件目錄如下,about.js、about.json文件在同級目錄:
那么一個完整的頁面打開步驟如下:
經過擴展,配置文件變得更加豐富,先前麻煩的跳轉處理、彈框等都可以通過配置文件實現,下面是一些常用的屬性介紹:
1.部分屬性屬性 | 類型 | 默認值 | 描述 |
backgroundColor | HexColor | 同項目配置 | 窗口背景色 |
navigationBarBackgroundColor | HexColor | 同項目配置 | 導航欄背景色 |
navigationBarHidden | Bool | false | 隱藏導航欄 |
navigationBarTitle | String | ? | 導航欄標題 |
navigationBarTitleColor | HexColor | 同項目配置 | 導航欄標題顏色 |
針對于iOS客戶端存在頁面需要Present等情況,可以設置以下屬性:
present | Bool | false | Present頁面 |
presentWithNavigationBar | Bool | false | Present頁面,并攜帶導航欄 |
transition | Map | false | 以轉場的形式呈現,并規定轉場的動畫表達式(默認背景alpha從0到1) |
優先級:transition > presentWithNavigationBar > present |
transition中需定義動畫的表達式,Native則需要解析該表達式,并按照表達式執行動畫。
3.設置導航欄按鈕navigationItems | Array | [] | 包含按鈕樣式的數組 |
通過fireEvent完成按鈕事件的回調。 |
按鈕樣式說明:
{
"type": "TEXT", // "TEXT", "IMG", "TEXT_IMG",必傳
"title": "標題", // 文字標題或者
"image": "刷新", // 是圖片地址,圖片地址支持本地圖片和網絡圖片
"textColor": "FFFFFF", // 16進制, 默認為白色,可不傳
"backgroundColor": "", // 16進制, 默認透明色,可不傳
"borderColor": "", // 16進制, 默認無邊框,可不傳
"borderWidth": 1, // 默認無邊框,可不傳
"cornerRadius": 1, // 默認無圓角,可不傳
"font": 16, // 默認16號字體,可不傳
"position": 0, // 默認0,可不傳, 0-左側顯示,1-右側顯示
"imagePosition": 0, // 0-圖片在左,文字在右,默認, 1- 圖片在右,文字在左, 2-圖片在上,文字在下,
4.自定義導航欄
例如滿足導航欄分段選擇的需求:
navigationBarTitleComponent | String | 無 | 對應的自定義Component的名稱 |
Component由原生自行實現,并暴露API與Weex進行交互 |
Weex對于iOS的支持比較友好,然而Android 端無法顯示陰影。 雖然文檔有明確指出此問題,但是Android sdk卻提供相關方法。也許是阿里的工程師嘗試解決,但效果并不理想。比較明顯的一點是,如果列表中的item使用陰影, 在列表滑動的時候會把陰影殘留在最初繪制的位置。Android的同學一直在嘗試解決此問題,最終也沒達到一個理想的效果。最后的降級方案是通過圖片來替代陰影,以下是Weex官方的注釋:
目前僅 iOS 支持 box-shadow 屬性,Android 暫不支持,可以使用圖片代替。
每個元素只支持設置一個陰影效果,不支持多個陰影同時作用于一個元素。
4. 網絡請求
Weex提供了
Stream
模塊來完成網絡請求,如果依賴于該模塊,請求頭、簽名等配置以及請求結果校驗都必須在Weex端完成,這對于一個全量Weex App而言無可厚非。但很多App的核心業務是使用Native完成,甚至會嵌套很多Web頁面,我們必須將所有的請求統一至Native,讓Weex更關心的是UI的呈現而非底層業務。因此我們提供了自己的網絡請求模塊,Weex端調用Native提供的方法,并通過參數來決定請求,一些可選的參數:
參數 | 類型 | 必填 | 描述 |
path | String | 是 | 請求的路徑 |
method | String | 否 | 請求的方式,默認為’GET‘,支持’POST‘、’DELETE‘等 |
params | Map | 否 | 請求所需的參數 |
timeout | Number | 否 | 請求超時時間 |
customHost | String | 否 | 自定義請求的Host |
callback | Function | 是 | 請求的回調 |
請求示例:
// 1. 獲取原生Module
const nativeStream = weex.requireModule("nativeStream")
// 2. 設置基本參數
const options = {
path:"/....",
method: "POST",
params: {
id: "123"
}
}
// 3. 發起請求
nativeStream.fetch(options, (res) => {
if (res.code === 0) {
succesCallback(res)
return
}
failCallback(res)
}
5. 圖片加載
通過上圖我們可以知道,一個簡單的圖片顯示流程,其實并不簡單,其中最為關鍵的是在進行第5步時所選擇方案。先上傳圖片對于程序員而言是最為便捷的方案,但是比較影響用戶體驗,圖片本應該在需要上傳的時候進行上傳,而非因為技術隘口而干擾業務。
轉換為Base64可以提升用戶體驗,但是卻比較影響性能。在iOS端,將一張1M的圖片轉換為Base64所需要的時間≥45ms,第6、7步所消耗的時間則是30ms左右,這種時間消耗隨圖片大小以倍數增加。
綜上所述,我們設計了一種本地化方案,為每一個添加的圖片生成一個唯一性的ID,Native負責圖片的存儲、加載。
6. 刷新組件
由于Weex所提供的
1.屬性
屬性 | 類型 | 必填 | 描述 |
showRefresh | Bool | 否 | 是否添加下拉刷新 |
showLoading | Bool | 否 | 是否添加上拉刷新 |
refreshAtCreated | Bool | 否 | 是否在初次顯示的時候,自動進行下拉刷新 |
refresh 事件:當 、
loading 事件:當 、
"list"
c
:show-refresh="true"
class="list"
@refresh="refreshList"
@loading="loadMoreList">
beginRefresh 事件:開始下拉刷新,由Weex調用。
beginLoading 事件:開始上拉刷新,由Weex調用。
endRefresh 事件:結束下拉刷新,由Weex調用。
endLoading 事件:結束上拉刷新,由Weex調用。
this.$refs.list.beginRefresh()
this.$refs.list.endRefresh()
3.refreshAtCreated屬性如果不使用該屬性,則需要在created或者mounted函數中手動調用刷新的方法以觸發下拉刷新,然而在某些Android設備上面出現了白屏的情況。Weex對此做出了解釋:
和瀏覽器不同的是,Weex 的渲染流程是異步的,而且渲染出來的結果都是原生系統中的 View,這些數據都無法被 javascript 直接獲取到。因此在 Weex 上,Vue 的 mounted 生命周期在當前組件的 virtual-dom (Vue 里的 VNode) 構建完成后就會觸發,此時相應的原生視圖未必已經渲染完成。
7. 截屏
Weex中完成截屏,只需要獲取到對應的視圖層即可,Weex頁面在渲染時會為每一個組件生成一個唯一的ID,在JavaScript中更直觀的體現是ref,盡管Weex并不存在真正的DOM,但其依然支持ref的使用。具體的做法如下:
// 1. 標簽生命ref
"poster">
// 2. 獲取到該element,實際上是一個Map
const poster = this.$refs.poster
// 3. 獲取Map中對應的ref
saveViewShot({ ref: poster.ref })
// 4. 獲取Component
// iOS
WXComponent *component = [weexInstance componentForRef:ref];
// Android
WXComponent component = WXSDKManager.getInstance().getWXRenderManager().getWXComponent(mWXSDKInstance.getInstanceId(), ref);
// 5. 獲取View,進行
8. 優雅的彈窗
這是一個很簡單的彈框需求,視圖漸漸變大最后全屏展示。然而基于Weex現有的能力是無法實現的,第一點:Weex頁面默認的布局是從導航欄下面開始的,第二點:路由的跳轉方式并不能直接支持此種彈出方式,頁面默認是從右向左推出。
為了實現彈框的功能,我們需要四個步驟:
1. Native定義PopView組件
1. Weex搭建頁面,并以PopView為基礎進行布局
2. 全屏呈現頁面并隱藏導航欄
3. 執行動畫
原生定義好PopView組件后,Weex頁面可以這樣布局:
"pop-view"> 測試彈框
結合第三點所提出的配置文件,我們將2、3步驟的控制放在配置文件當中,最后寫出的配置文件為:
{
navigationBarHidden: true, // 隱藏導航欄
transition: {
property:"scale", // popView的尺寸將由(0,0)變為顯示大小
duration: 2, // 動畫時間,單位(秒)
},// 轉場顯示,并規定popView的顯示動畫
}
這只是最為簡單的例子,更復雜的動畫需要客戶端支持即可。
五、To Be Continued
以上概括了Weex接入的心路歷程,以及在實踐中遇到的基本問題,表明了Weex在團隊中的運用已經暢通并日趨規范化,但是更深入的性能優化、熱更新等需要我們繼續前行,以下是下一期文章將涉及的知識點:
熱更新
資源預加載
配置文件動態化
Weex資源打包自動化,自動加入終端倉庫
加推科學院公眾號
mp.weixin.qq.com/s/LxdQ6Eq2R…
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/6889.html
摘要:問題,你可以在中文討論板塊提交問題,地址。文字展現必須使用標簽關于端的點透事件需要在上層視圖上加上,如果上層視圖有事件,多加一個中間層,把加在空事件視圖上關于事件注意僅支持和,暫不支持。事件會在頁面就要關閉時被觸發。 好吧,我知道你來看這個文章,一定是遇到坑了,所以,把這幾個放在最開始吧 現在,如果你的團隊的技術棧是react,請嘗試這個吧,跟react很像,如果你的團隊一直使用rea...
閱讀 3579·2021-08-02 13:41
閱讀 2460·2019-08-30 15:56
閱讀 1527·2019-08-30 11:17
閱讀 1187·2019-08-29 15:18
閱讀 591·2019-08-29 11:10
閱讀 2683·2019-08-26 13:52
閱讀 520·2019-08-26 13:22
閱讀 2963·2019-08-23 15:41