Weex入門與進階指南

摘要: 前言 相比較於React Native的“Learn once, write anywhere”,Weex的口號是“Write once, run everywhere”。考慮到React Native比較任性的向下兼容性,我們也引入了Weex做一番瞭解。

前言

相比較於React Native的“Learn once, write anywhere”,Weex的口號是“Write once, run everywhere”。考慮到React Native比較任性的向下兼容性,我們也引入了Weex做一番瞭解。

本文主要分爲以下幾個部分:

  1. 構建Hello World程序;
  2. 集成到現有的iOS工程中;
  3. 使用Weex的高級特性;
  4. 如何爲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 

此時,瀏覽器會打開一個新的標籤頁展示helloweex.we的執行效果:

_2016_07_08_2_34_04

注意到此時地址欄的內容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>

_2016_07_08_2_47_03

接着我們添加上第三組成部分:腳本(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>

這樣一來,當我們點擊文本的時候會出現如下效果:

_2016_07_08_2_50_33

更多語法相關內容可以參考官方文檔

二、集成到iOS工程

2.1 概述

上面是從前端的角度來初步看Weex的基礎效果,對於客戶端來講,這類框架的一個優勢就是能夠結合Native代碼發揮作用。比如在人手緊張的情況下可以一次開發,然後應用在不同平臺終端上。

所以,這裏討論下如何將其集成到現有的iOS工程項目當中。

  1. 參考官方文檔,我們先從GitHub下載Weex源碼
  2. 解壓後將目錄下的ios/sdk複製到現有的iOS工程目錄下,並根據相對路徑更新既有工程的podfile,然後執行pod update將Weex iOS SDK集成進既有的iOS項目中;
  3. 在iOS Native代碼中初始化Weex SDK,然後創建出要展示Weex程序的ViewController,具體見如下描述;

2.2 在iOS應用上運行Weex程序

如何集成的文檔中,前面說的比較清楚,但是在初始化Weex環境渲染Weex實例這兩個小節中,可能是由於代碼是從比較大的項目源碼中摘錄出來的,所以存在一些不必要或沒有上下文的代碼。

這裏描述下在開發調試階段運行Weex程序。

2.2.1 確定要運行的Weex程序

創建一個WeexDebugViewController,進行如下佈局:

_2016_07_08_3_19_12

通過填入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和程序文件名:

_2016_07_08_3_47_33

_2016_07_08_3_47_43

到此,將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 來運行查看效果:

_2016_07_08_4_28_01

可以在瀏覽器裏看到這次多了一張圖片。但是如果是運行在Native端,圖片則得不到展示:

_2016_07_08_4_37_08

這是由於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

3.2.3 註冊Handler

[WXSDKEngine registerHandler:[WeexImageDownloader new] withProtocol:@protocol(WXImgLoaderProtocol)];

這樣一來,再次運行程序就可以看到圖片了:

_2016_07_08_5_45_09

這樣設計的好處主要是考慮了不同App依賴的網絡庫或者圖片下載緩存庫不同,避免Weex強依賴於一些第三方庫,遵循依賴抽象而不是具體的原則。

BTW,我個人感覺Weex縮寫成WXWeexImageLoaderProtocol縮寫成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. 需要遵循WXModuleProtocol協議;
  2. 需要合成(synthesizeweexInstance屬性;
  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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章