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