最近研究了下React Native 與 Weex , 整理了份對比~ 最終選擇的阿里霸霸的Weex!
本文主要是記錄和分享我在實際開發Weex項目中的所見所學(2018年初), 僅是我個人的理解, 并是一定是完全正確的, 也希望有大神指點學習??
已經2019年啦,目前Weex已經更新到1.3.11, WeexSDK目前更新到0.20.1 ,Weex已不再屬于阿里, 貢獻給Apache去迭代維護了,最新Weex官網, 變化不小,依然在努力。Weex的開發可以說每個人有每個人的理解,項目的場景和開發的習慣也不一樣,以至于很多個人或者團隊在Weex基礎上又開源了很多不同的分支,比如Weex Plus、Weex Box、WEIUI等等,這些可以幫助開發者省去很多時間去理解和填坑,不過有利也有弊嘛。總之Weex的原理依然沒有變。
Weex項目界面展示:
我對Weex的理解:
通過Weex,可以寫一套Vue代碼渲染在Native和Web兩端, 可以熱更新. 在Native端的效果近似原生. 可以這樣理解, 在Weex項目中, 你寫的Vue文件最終會編譯成兩種類型的文件夾,
1.通過啟動項目的命令: npm run serve (或者通過打包命令 npm run build:native), 可以時時編譯到項目目錄下的dist文件夾內 , 目錄結構跟src目錄下的結構一致, 會把.Vue文件編譯成.js文件這些.js文件是Native端Weex SDK加載所需要的文件, 每個Vue文件對應一個.js文件, 所以把這些js文件放在服務器上, Native端加載js地址就可以了, 達到熱更新效果~
2.通過命令: npm run build:prod 可以打包生成release文件夾, 該文件夾內是web環境訪問所需的.html文件, 每個Vue文件對應一個.html文件, 瀏覽器打開就是你寫的對應頁面了
Weex SDK是Native端所需要集成的, 用于將.js文件渲染到Native端的手機上, 官方Demo可以通過 weex add platform ios || weex add platform android 去下載, 看下代碼不難發現, SDK其實就是將.js文件(路徑)渲染成一個View, 然后放到當前視圖控制器中, Demo中有提供這個Controller, 接收一個Url, 這個Url可以是網絡的js路徑,也可以是本地的js路徑.所以關于頁面適配, 其實只要Native端適配當前視圖控制器的View就可以了
雖說支持Vue語法, 但在實際開發的過程中, 其實并不像開發Vue項目那樣隨意, 區別和限制要求還是挺多的. 說下最典型的頁面調整路由吧, Vue屬于單頁應用, 通過Router來管理頁面, 怎么跳轉都是一個頁面. 而Weex并不是單頁應用, 可以理解成Weex每個頁面都是一個新初始化的Vue頁面. 所以目前我還不知道Weex如何配置全局變量~
Weex比較適合做一些列表信息展示, 如果經常封裝組件的話, 開發起來會很快, 這是Weex的優勢, 但涉及到比較復雜的功能(地圖, 拍照攝像等)就需要費點心思了, 甚至需要Native端去支持, Weex提供了Module, Component 等橋梁, 可通過Weex向Native傳值和回調, Native可以注冊原生Component給Weex使用, 你也可以使用<Web>標簽來加載html用來顯示比較復雜的圖表功能(如上面雷達圖)
在開發人員上面, 雖說是Web開發, 但是我覺得最好有 iOS 或 Android 開發經驗, 這樣的話理解Weex運轉起來會非常得心應手, 也可以讓公司的移動端開發小伙伴一起參與Weex的學習~ , Weex不像React Native那樣有非常多的組件, 很多參考資料, 活躍的社區. Weex實際開發中所遇到的問題基本需要自己去思考, 官網上Weex的組件大多數都是很久之前的代碼了, 其實原理就是Native端寫好提供的, 跟你注冊Module給Native端他們實現是一樣的, 所以在選擇上面請多考慮. 這里推薦 Weex-Ui . 釘釘也有Weex-Ui官方群提供交流和學習!
如何創建工程請仔細閱讀Weex官網要好好看看哦
下面就來細說下工程里的一些內容了
搜索關鍵詞:
工程運行
全局方法的配置
頁面跳轉
降級方案
網絡請求stream
圖片加載
關于獲取標簽對象
動態修改標簽樣式
自定義字體樣式
跨域相關
打包部署
交互相關
JS緩存機制
CSS樣式不支持記錄
標簽樣式不支持記錄
點擊事件失效
<list>列表中的<input>顯示錯誤
運行 weex add platform ios 出現錯誤
工程運行
weex create xxxx 后先看下最新的效果吧~
cd 項目目錄下 npm start
啟動項目,瀏覽器中可以看到:
可以看到左側是內容在手機中顯示的樣式, 右側二維碼是提供weex官方軟件"weexplayground"(各大應用市場下載)掃碼在手機中查看的 (需要連接同一個無線網哦~,因為服務是部署在本地的,只有同一ip地址才能訪問), 所以網上的weex Demo基本都有二維碼~ 這樣可以在真機上查看效果.(在實際開發中我基本不用官方APP去看在手機上的效果, 因為開發過程我需要跟Native端交互,會用到Module, 而官方APP中是不會注冊這個Module 的, 所以看不到效果. 我在開發中首先是看在瀏覽器的效果, 瀏覽器中沒有問題我再打包在手機上看效果)
可以說preview.html其實就是用來看下在手機尺寸中展示的效果的, 在項目中web/preview.html中可以找到, 這里是Web環境并非Native環境呦,
<iframe id="preview" src="/" frameborder="0"></iframe>
還記得之前地址的后綴么?page=index.js
這個index.js就是src下對應index.vue編譯出的結果,所以這個加載的結果就是src目錄下index.vue中的內容~ 如果需要查看其它頁面樣式直接改成src目錄下的相對地址就可以了~
全局方法的配置
我目前的處理方式是把所有全局方法寫在一個.js文件里,其他頁面手動導入這個js文件中的方法
// src/mixins/index.js
export default {
methods: {
$push () {...}
}
}
其他頁面引入這個js文件中的方法:
import globalMethods from '../mixins/index.js'
export default {
mixins: [globalMethods],
created () {
}
}
頁面跳轉
在Weex中使用Vue 這篇文章中有介紹如何使用和使用注意事項, 需要好好看一下!
其中關于使用 Vuex 和 vue-router ,感覺官方其實并不推薦使用vue-router了, 更推薦使用 navigator 來管理頁面實例
按官方的例子使用navigation進行跳轉
methods: {
jump (e) {
navigator.push({
url: '/view_one/index_two',
animated: 'true'
})
}
}
發現在Web端url是可以寫相對地址的,跳轉沒有問題.
但是在Native端,頁面加載是失敗的,應該是因為Native端加載的是js文件, 所以Native端url需要寫完整的js路徑
這里重點說下我在跳轉方面的處理
曾經iOS , WeexSDK是可以看到源碼的, 現在不行了哈, 我看了下navigator的寫法,其實就是使用系統的跳轉, 把傳過來的Url給Controller去渲染,這樣的話Native端當然需要完整的js路徑咯, 所以我建議原生上面的跳轉最好自定義, 這樣的話導航欄的隱藏/顯示/標題等等都可以自己控制, 很簡單, 注冊個Module, 定義一個方法就可以了
// iOS代碼
@implementation WXNavigationModule
WX_EXPORT_METHOD_SYNC(@selector(push:))
WX_EXPORT_METHOD_SYNC(@selector(pop))
/**
頁面跳轉
@param params 參數
*/
- (void)push:(NSDictionary *)params {
if ([params isKindOfClass:[NSDictionary class]]) {
NSString *h5Url = params[@"h5Url"];
NSString *fallbackUrl = params[@"fallbackUrl"];
[BLRouterService.shared gotoWeexWithH5Url:h5Url fallbackUrl:fallbackUrl barHidden:[params[@"barHidden"] boolValue]];
}
}
/**
頁面返回
*/
- (void)pop {
dispatch_async(dispatch_get_main_queue(), ^{
[BLRouterService.shared.navigationController popViewControllerAnimated:true];
});
}
Web環境我依然使用Weex自帶的navigator, 所以我根據平臺做了區分:
// js 代碼
// push操作 barHidden代表是否隱藏導航欄 默認隱藏
$push (path, barHidden) {
if (this.$isNativePlateform() && weex.supports && weex.supports('@module/navigation.push')) {
weex.requireModule('navigation').push({
h5Url: this.$getPushPath(true, path),
fallbackUrl: this.$getPushPath(false, path),
barHidden: barHidden,
animated: true
})
} else {
weex.requireModule('navigator').push({
url: this.$getPushPath(false, path),
animated: 'true'
})
}
},
/**
* 是否native
*/
$isNativePlateform () {
var platformType = this.$getPlatformType()
return (platformType === 'android' || platformType === 'iOS')
},
// 獲取平臺類型
$getPlatformType () {
if (weex.config.env.platform === 'iOS') {
return 'iOS'
} else if (weex.config.env.platform === 'android') {
return 'android'
} else if (weex.config.env.platform === 'Web') {
return 'web'
} else {
return 'undefine'
}
},
還有一點需要注意的就是這個跳轉路徑的拼接:
- Native端加載的是.js文件, 這個文件在本地的dist目錄下, 相對views的文件
- Web端加載的是.html文件 本地測試的時候,通過控制臺的接口請求可以看到加載的其實是頁面相對views的路徑.html
http://192.168.1.49:12581/views/etc/home.html
所以使用跳轉的時候傳的path最好是相對views文件的相對路徑,比如這樣
this.$push('views/order/order-detail?applyId=' + this.dataItem.applyId)
這樣的話拿到這個path后處理下路徑和參數, 通過平臺判斷否拼上/dist, 末尾是.js 還是 .html 等, 就這可以完美在Web和Native端跳轉了
降級方案
所謂的降級方案可以理解成如果發現Native端的SDK無法展示或者展示出現問題, 則使用Web去展示. 也就是說當Native端SDK在渲染頁面時發生了錯誤回調, 我們就需要寫一個WebView去展示該頁面對應的H5地址了, WebView加載的可不是js了, 而是頁面對應的.html (Weex寫出的頁面打包好后,最終會在release文件夾內生成對應的.html文件用于Web端展示,加載的地址可以由Web傳給Native端記錄下來, 也可以根據服務端部署的位置自己調整).
iOS的Native端錯誤回調如下:
_instance.onFailed = ^(NSError *error) {
#if DEBUG
if ([[error domain] isEqualToString:@"1"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableString *errMsg=[NSMutableString new];
[errMsg appendFormat:@"ErrorType:%@\n",[error domain]];
[errMsg appendFormat:@"ErrorCode:%ld\n",(long)[error code]];
[errMsg appendFormat:@"ErrorInfo:%@\n", [error userInfo]];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"render failed" message:errMsg delegate:weakSelf cancelButtonTitle:nil otherButtonTitles:@"ok", nil];
[alertView show];
});
}
#endif
};
也就是說這里我們需要手動將創建一個WebView加載到當前控制器去展示了,Weex渲染的View就可以移除了.
請注意: 降級情況下雖然也在Native端, 但這時候注冊Module 不會再起作用了,
這時候想要交互就屬于純html與WebView的交互了, Native端去給WebView注冊JS吧
通過版本號手動降級控制:
官方推薦使用模塊降級, 也就是說用第三方plugin去判斷.
群里的大神推薦使用webpack-plugin-downgrade 這個插件,
按照它的意思我在weex/configs/webpack.common.conf.js
這里添加plugin 并使用, 請注意 new DowngradePlugin({}) 一定要放在第一位,不然會報錯
const plugins = [
new DowngradePlugin({
condition: {
// Any condition is matched will be downgraded.
ios: {
osVersion: '>1.0',
appVersion: '>1.0.0',
weexVersion: '>1',
deviceModel: ['iPhone5,1']
},
android: {
osVersion: '>1.0',
// Check condition with multiple app.
// The `MY_APP_A` and `MY_APP_B` is WXEnvironment's appName param.
appVersion: {
MY_APP_A: '>1.0.0',
MY_APP_B: '>3.0.0'
},
weexVersion: '>1',
deviceModel: ['G-2PW2100']
}
}
}),
/*
* Plugin: BannerPlugin
* Description: Adds a banner to the top of each generated chunk.
* See: https://webpack.js.org/plugins/banner-plugin/
*/
new webpack.BannerPlugin({
banner: '// { "framework": "Vue"} \n',
raw: true,
exclude: 'Vue'
})
]
OK, 只要滿足condition中的判斷條件, 當加載頁面時就會對應回調_instance.onFailed
失敗方法~
iOS中設置appName 和 appVersion 很方便 官網說的很清楚~
Android比較"耐人尋味", 最終在群里詢問大神找到了答案:
網絡請求stream
現在stream并不屬于weex自帶的網絡請求庫了, 屬于一種plugin, 在entry.js中的注釋部分有說明,所以需要 npm i weex-vue-stream --save , 然后在entry.js中導入和添加:
import stream from 'weex-vue-stream'
// install the plugins.
weex.install(stream)
圖片加載
方法應該有很多,網上也有一些
《Weex 加載 .xcassets 中的圖片資源》
《可能是史上最全的weex踩坑攻略》- 故事六: 圖片加載
《weex android iOS 加載本地圖片》
官方推薦的圖片加載的方法WXImgLoaderDefaultImpl ,這里如果要替換SDWebImage版本或者方法請注意返回的對象要實現<WXImageOperationProtocol>方法, 不然會閃退, 具體可參考文章之前遇到的問題即解決.
因為要考慮到三端統一與熱更新, 我就把所以圖片放在服務上了 ,這就不存在加載iOS / Android 本地的圖片了, 所以圖片的路徑也要是完整的路徑 (所有圖片路徑我也得做處理) ,當然這樣做也有不好的地方, 加載慢什么的, 因為Native端會做圖片緩存, 所以就沒怎么考慮了
關于獲取標簽對象
<div ref='container'></div>
const dom = weex.requireModule('dom')
mounted () {
this.$nextTick(() => {
var _this = this
setTimeout(function () {
dom.getComponentRect(_this.$refs.container, containerOption => {
// getComponentRect 官方使用時沒有加延遲 但這個方法有時候我發現獲取的size都是0,所以遇到這樣的情況可以加個延遲,
})
}, 300)
})
},
動態修改標簽樣式
網上找的一種方法~
const animation = weex.requireModule('animation')
mounted () {
this.$nextTick(() => {
var _target = this.$refs.target
animation.transition(_target, {
styles: {
backgroundColor: 'red'
},
duration: 0, // 變換用時,可設置為0
timingFunction: 'ease', // 變換的動畫,同理css3變換
delay: 0 // 延時執行
})
})
}
如果需要重新渲染,我使用的方法是vue動態style
v-bind:style="{ height: viewHeight + 'px'}"
animation動態修改標簽樣式在手機上不支持修改坐標(fixed: top,left,right,bottom) ,需要使用:style, 而且dom.getComponentRect這個方法寫在Created() 方法里在手機上獲取不到dom, 需要價格setTimeout延遲獲取
自定義字體樣式
字體具體我沒實現過,群里朋友分享的
官方addRule介紹
跨域相關
weex默認是支持跨域的 —— 可參見這篇文章
但是在電腦瀏覽器上會出現跨域問題,在手機或者模擬器上不會~
如果想在電腦瀏覽器中實現跨域方法,可以用 weex工程自帶的跨域插件去設置~
在config.js 中 添加如下配置, 重啟服務即可降/api自動代理成http://testapi.demo.cn
proxyTable: {
'/api': {
target: 'http://testapi.demo.cn',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
打包部署
打包Web頁面:
npm run build:prod
是打包到生產環境的命令, 這里需要注意下,默認是會產生巨大的.map文件的, 一般部署是不需要的,所以我們需要配置下打包時不編譯.map文件
其實也就是在配置文件中搜索下map關鍵字 對應的看下是否是相關控制,然后取消調就可以了
成功打包后會在目錄下生成release文件:
可以看到對應的vue文件最終會生成一個html, 我們只要訪問這個html就可以看到內容了, 所以部署的時候只要將release/web文件夾部署到服務器上即可~
打包Native頁面:
npm run build:native
后會在根目錄下生成dist文件, 這個文件中會對應生成.js文件,一般將整個dist目錄放在服務器中,Native端加載即可
打包圖片:
哈哈,目前還不知道圖片如何打包, 只是把圖片手動拖到服務器中, 還在研究...
交互相關
Weex 到 Weex 之間:
如果是父子頁面關系: refs
如果是跨頁面: BroadcastChannel
Weex 到 Native:
Module
Native 到 Weex:
在頁面加載時通過Native端render渲染時傳入option參數, 或者globalEvent 還可以由Weex發起然后使用CallBack回調
下面主要記錄下Weex中Web展示頁面和Weex原生展示的頁面之間的交互,怎么理解呢,場景如下:
因為要顯示echart表,所以在native端我是這樣處理的, 我單獨用HTML寫了一個展示echart頁面, 這個頁面就是web環境了, 我在其他頁面用<web>去加載這個HTML, 現在遇到的問題就是傳參和參數監聽.而目前唯一的橋梁就是<web>展示的src的URL, 我通過使用錨點http://baidu.com#params
進行參數傳遞并且不會刷新<web>, 這個方法在iOS是沒有問題的,但是在Android上遇到了修改錨點<web>依然被刷新(因為src檢測到URL變化了).
解決方案: Android去單獨判斷, 加載完URL后,這樣改變url:
<web src="javascript:window.changeHighLightText('xxx')">
然后在對應的HTML頁面用window注入這個方法,即可達到傳值效果.bingo~
javascript:
的方法在很多瀏覽器中都被禁止, 因為涉及到安全性,但是在Android上目前依然是可行的, 沒辦法的辦法了, 但這種方法并不能保證所有Android都使用, 也許有些系統會禁止這種方法~ (iOS使用這種是行不通的)
postMessage 向當前 Web 頁面發送數據 不過該方法目前僅支持Android
這里有個方法可以參考 ,通過<web>加載的h5頁面向weex頁面發送消息
JS緩存機制
CSS樣式不支持記錄:
1.padding margin 不支持縮寫 ,類似這種,
padding: 0 20px 30px 0;
但是如果四個值一樣可以,類似這種
margin: 10px;
2.不支持百分比
3.display布局僅支持relative | absolute | fixed | sticky 可參考這偏文章
- border 樣式差異官方說明
border 目前不支持類似這樣 border: 1 solid #ff0000; 的組合寫法。
5.不支持display:none, 可以使用 v-if 代替,或者用 opacity:0; 來模擬
值得注意的是,當opacity的只小于等于0.01時,native控件便會消失,但視圖會被渲染,占位空間還在,但用戶無法進行交互操作,點擊時會發生點透效果。
6.陰影(box-shadow), 僅支持iOS 不支持Android (吐血...)
標簽樣式不支持記錄
- 支持 v-if , 不支持 v-show , 有些標簽不支持 v-else v-else-if (比如text標簽不支持,但div標簽支持)
點擊事件失效
官方明確了一些不支持@click='function()'
方式的標簽
自定義的組件需要
<customComponent @click.native='customClick()'></customComponent>
<list>列表中的<input>顯示錯誤
在<list>列表里, 如果<cell>標簽中存在<input>輸入框, 當列表滾動時,如果<input>框滑出列表再滑回來會出現無法看到值的情況, 這是<list>列表復用時產生的Bug, 解決方法: 下面的評論中我也有給出我的建議
運行 weex add platform ios 出現錯誤
error: unknown option `--telemetry'