
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
原文地址:https://yq.aliyun.com/articles/57554
前言
相比較於React Native的“Learn once, write anywhere”,Weex的口號是“Write once, run everywhere”。考慮到React Native比較任性的向下兼容性,我們也引入了Weex做一番了解。
本文主要分為以下幾個部分:
- 構建Hello World程序;
- 集成到現有的iOS工程中;
- 使用Weex的高級特性;
- 如何為Weex做貢獻;
一、Weex入門
1.1 Hello Weex
參考官方教程,我們需要先安裝Node。在Mac上也可以通過Homebrew直接進行安裝:brew install node
。
接着我們需要安裝Weex CLI:npm install -g weex-toolkit
,並確保版本號大於0.1.0:
$ weex --version info 0.3.4
至此,准備工作已經到位,我們可以開始編寫Weex程序了。
創建一個名為helloweex.we
的文件,並編寫以下代碼:
<template> <div> <text>Hello Weex</text> </div> </template>
通過命令行在helloweex.we
文件所在的目錄下執行如下命令:
$ weex helloweex.we
info Fri Jul 08 2016 14:30:31 GMT+0800 (CST)WebSocket is listening on port 8082 info Fri Jul 08 2016 14:30:31 GMT+0800 (CST)http is listening on port 8081
- 1
- 2
- 3
- 1
- 2
- 3
此時,瀏覽器會打開一個新的標簽頁展示helloweex.we
的執行效果:
注意到此時地址欄的內容http://127.0.0.1:8081/weex_tmp/h5_render/?hot-reload_controller&page=helloweex.js&loader=xhr
包含着hot reload
字樣,所以可以自然聯想到當我們在源文件做修改並保存后,該頁面會自動刷新展示效果。
1.2 基礎結構
上面的示例只是一個非常簡單的雛形,而一個比較完整的Weex程序包括三個部分:模板(Template)、樣式(Style)和腳本(Script)。
比如我們可以利用上文提到的hot reload
,修改文本的顏色並實時查看效果:
<template> <div> <text class="title">Hello Weex</text> </div> </template> <style> .title { color: red; } </style>
接着我們添加上第三組成部分:腳本(Script):
<template> <div> <text class="title" onclick="onClickTitle">Hello Weex</text> </div> </template> <style> .title { color: red; } </style> <script> module.exports = { methods: { onClickTitle: function (e) { console.log(e); alert('title clicked.'); } } } </script>
這樣一來,當我們點擊文本的時候會出現如下效果:
更多語法相關內容可以參考官方文檔。
二、集成到iOS工程
2.1 概述
上面是從前端的角度來初步看Weex的基礎效果,對於客戶端來講,這類框架的一個優勢就是能夠結合Native代碼發揮作用。比如在人手緊張的情況下可以一次開發,然后應用在不同平台終端上。
所以,這里討論下如何將其集成到現有的iOS工程項目當中。
- 參考官方文檔,我們先從GitHub下載Weex源碼。
- 解壓后將目錄下的
ios/sdk
復制到現有的iOS工程目錄下,並根據相對路徑更新既有工程的podfile,然后執行pod update
將Weex iOS SDK集成進既有的iOS項目中; - 在iOS Native代碼中初始化Weex SDK,然后創建出要展示Weex程序的ViewController,具體見如下描述;
2.2 在iOS應用上運行Weex程序
在如何集成的文檔中,前面說的比較清楚,但是在初始化Weex環境
和渲染Weex實例
這兩個小節中,可能是由於代碼是從比較大的項目源碼中摘錄出來的,所以存在一些不必要或沒有上下文的代碼。
這里描述下在開發調試階段運行Weex程序。
2.2.1 確定要運行的Weex程序
創建一個WeexDebugViewController
,進行如下布局:
通過填入IP和文件名來定位我們要運行的Weex程序。此外,還可以結合weex helloweex.we --qr -h {ip or hostname}
命令來生成二維碼,進行掃描演示,不過解析二維碼還是為了獲取到Weex程序所在位置。
2.2.2 初始化Weex SDK
開發調試階段我們可以先將Weex SDK的初始化放在這個WeexDebugViewController
中:
- (void)initWeex {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXAppConfiguration setAppGroup:@"AliApp"]; [WXAppConfiguration setAppName:@"WeexDemo"]; [WXAppConfiguration setAppVersion:@"1.0.0"]; [WXSDKEngine initSDKEnviroment]; [WXLog setLogLevel:WXLogLevelVerbose]; }); }
2.2.3 運行Weex程序的ViewController
點擊ShowWeex
按鈕時,我們可以根據兩個輸入框的內容拼接出要運行的Weex程序的位置,然后將其賦值給用來渲染Weex實例的WeexShowcaseViewController
:
- (void)showWeex { NSString *str = [NSString stringWithFormat:@"http://%@:8081/%@", self.ipField.text, self.filenameField.text]; WeexShowcaseViewController *vc = [WeexShowcaseViewController new]; vc.weexUri = [NSURL URLWithString:str]; [self.navigationController pushViewController:vc animated:YES]; }
接着我們來看看WeexShowcaseViewController
的源碼:
#import <WeexSDK/WeexSDK.h> @interface WeexShowcaseViewController () @property (nonatomic, strong) WXSDKInstance *weexSDK; @end @implementation WeexShowcaseViewController - (void)dealloc { [_weexSDK destroyInstance]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.weexSDK.viewController = self; self.weexSDK.frame = self.view.frame; [self.weexSDK renderWithURL:self.weexUri]; __weak typeof(self) weakSelf = self; self.weexSDK.onCreate = ^(UIView *view) { [weakSelf.view addSubview:view]; }; self.weexSDK.renderFinish = ^(UIView *view) { ; }; self.weexSDK.onFailed = ^(NSError *error) { NSLog(@"weexSDK onFailed : %@\n", error); }; } - (WXSDKInstance *)weexSDK { if (!_weexSDK) { _weexSDK = [WXSDKInstance new]; } return _weexSDK; }
2.2.4 運行起來
回到終端上,切換到helloweex.we
文件所在的目錄,將Weex的dev server跑起來:
$ weex -s .
info Fri Jul 08 2016 15:38:59 GMT+0800 (CST)http is listening on port 8081 info we file in local path . will be transformer to JS bundle please access http://30.9.112.173:8081/
然后在Native上填入對應的IP和程序文件名:
到此,將Weex集成到現有iOS工程中算初步告一段落。
三、Weex進階
當集成工作完成后,會發覺現有功能不足以滿足業務需求,所以Weex支持開發者做一些擴展。
3.1 實現Weex接口協議
之前的helloweex.we
示例中只有一個文本元素,現在再添加一個圖片元素:
<template> <div> <image class="thumbnail" src="http://image.coolapk.com/apk_logo/2015/0817/257251_1439790718_385.png"></image> <text class="title" onclick="onClickTitle">Hello Weex</text> </div> </template> <style> .title { color: red; } .thumbnail { width: 100; height: 100; } </style> <script> module.exports = { methods: { onClickTitle: function (e) { console.log(e); alert('title clicked.'); } } } </script>
然后再執行:$ weex helloweex.we
來運行查看效果:
可以在瀏覽器里看到這次多了一張圖片。但是如果是運行在Native端,圖片則得不到展示:
這是由於Weex SDK沒有提供圖片下載能力,需要我們來實現。
3.2 實現圖片下載協議WXImgLoaderProtocol
這個基本可以參考官方文檔來實現。
3.2.1 定義圖片下載Handler
#import <WeexSDK/WeexSDK.h> @interface WeexImageDownloader : NSObject <WXImgLoaderProtocol> @end
3.2.2 實現協議接口
這個類必須遵循WXImgLoaderProtocol
協議,並實現該協議定義的接口:
#import "WeexImageDownloader.h" #import <SDWebImage/SDWebImageManager.h> @implementation WeexImageDownloader - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock { return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, finished); } }]; } @end
[WXSDKEngine registerHandler:[WeexImageDownloader new] withProtocol:@protocol(WXImgLoaderProtocol)];
這樣一來,再次運行程序就可以看到圖片了:
這樣設計的好處主要是考慮了不同App依賴的網絡庫或者圖片下載緩存庫不同,避免Weex強依賴於一些第三方庫,遵循依賴抽象而不是具體的原則。
BTW,我個人感覺Weex
縮寫成WX
,WeexImageLoaderProtocol
縮寫成WXImgLoaderProtocol
,不是很好看。
3.2 自定義UI組件
如果Weex的內置標簽不足以滿足要求時,我們可以自定義Native組件,然后暴露給.we文件使用。
比如我們可以定義一個WeexButton
,繼承自WXComponent
,然后將其注冊進Weex SDK:
[WXSDKEngine registerComponent:@"weex-button" withClass:[WeexButton class]];
這樣一來,我們就可以在.we文件中使用這個標簽了:
<weex-button class="button" title="hello"></weex-button>
標簽中的屬性我們可以在初始化函數中獲得:
- (instancetype)initWithRef:(NSString *)ref type:(NSString*)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance { self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { _title = [WXConvert NSString:attributes[@"title"]]; } return self; }
通過這些屬性,我們可以在組件生命周期中修改組件的樣式,比如設置按鈕的title:
- (void)viewDidLoad { [super viewDidLoad]; self.innerButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; self.innerButton.frame = self.view.bounds; [self.view addSubview:self.innerButton]; [self.innerButton setTitle:self.title forState:UIControlStateNormal]; [self.innerButton addTarget:self action:@selector(onButtonClick:) forControlEvents:UIControlEventTouchUpInside]; }
3.3 自定義模塊
除了UI組件之外,有些時候我們希望JS層面能夠調用Native的一些功能,比如通過JS代碼讓Native打開一個特定的ViewController。這時候,我們可以自定義一個模塊向JS層面暴露API:
@synthesize weexInstance; WX_EXPORT_METHOD(@selector(call:withParam:callback:)) - (void)call:(NSString *)api withParam:(NSDictionary *)param callback:(WXModuleCallback)callback {
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
注意點如下:
1. 需要遵循WXModuleProtocol
協議;
2. 需要合成(synthesize
)weexInstance
屬性;
3. 使用WX_EXPORT_METHOD
來暴露API;
4. 使用WXModuleCallback
進行回調;
完成以上編碼后,向Weex SDK注冊:[WXSDKEngine registerModule:
,就可以在.we文件中使用了:
<script> module.exports = { methods: { onClickTitle: function (e) { var mymodule = require('@weex-module/mymodule'); mymodule.call('api', {}, function(ret) { }); } } } </script>
四、為Weex做貢獻
由於Weex剛開源不久,如果開發者發現一些問題或者需要改善的地方,可以直接在GitHub上進行fork,修改完后提交Pull Request。