深入折騰Weex,知乎日報客戶端開發

鍾穎發表於2016-05-12
深入折騰了一下 Weex,做了一個知乎日報的客戶端,同時實現了一種目前 Weex 尚未提供的 Native 頁面切換思路。

最後效果如下:



這個 demo 主要實踐 4 件事情:

1. 模組註冊
2. 元件註冊
2. Native 方法呼叫
4. Native 頁面切換 & 引數傳遞

整個 app 只有兩個頁面,都是純 Weex 編寫的,對於第一個頁面非常的簡單,佈局類似於 iOS 的 TableView

<template>
  <scroller>
    <container repeat="{{list}}" news_id="{{news_id}}" onclick="onclick">
      <container class="cell">
        <image class="thumb" src="{{src}}"></image>
        <text class="title">{{title}}</text>
      </container>
      <text class="separator"></text>
    </container>
  </scroller>
</template>

然後我們使用 this.$sendHttp 發起 http 請求,這裡知道,Weex 僅僅是一個渲染引擎,對於 http 請求,mtop 請求,甚至圖片載入等功能,是需要業務注入實現類的。比如 WeexDemo 裡面的 sendHttp 就是注入了 WXStreamModule 模組,然後在實現裡面使用了 NSURLConnection,這個設計非常不錯,在具體的業務當中,你可以把實現程式碼替換成任何你想要的 HTTP 庫。

[WXSDKEngine registerModule:@"image" withClass:[WXImageModule class]];
[WXSDKEngine registerModule:@"stream" withClass:[WXStreamModule class]];
[WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];

這就是 Weex 註冊模組的原理,其中圖片模組是用 SDWebImage 實現的。我們同樣也可以將其他的 Native 方法註冊到模組裡面,例如:

WX_EXPORT_METHOD(@selector(log:));

- (void)log:(NSString *)text {
    NSLog(@"LOG: %@", text);
}

然後在 JavaScript 程式碼裡面只需要用 this.$call(“event”, “log”, “Hello, World!”); 就能在 Native 裡面輸出 Hello World! 這提供了強大的擴充套件能力。

首頁的展示是較為簡單的,用的是 Weex 自帶的 ImageText,但是詳情頁是一個 Web 頁面,貌似我還沒找到 Weex 提供的 WebView 渲染元件。(但是奇怪的是,我自建 WXWebViewComponent 的時候提示 Duplicated Symbol,難道是還未實現麼),所以我們需要自建一個 WXComponent,用於 WebView 渲染。

當然我們也可以使用 Native 程式碼開啟一個 WebView,不過這樣顯然不如純 Weex 實現有趣。

#import "WXHtmlNodeComponent.h"
#import <WebKit/WebKit.h>

@interface WXHtmlNodeComponent()

@property (nonatomic, strong) NSString *URL;
@property (nonatomic, strong) NSString *html;
@property (nonatomic, strong) WKWebView *webView;

@end

@implementation WXHtmlNodeComponent

WX_CUSTOM_ATTRIBUTE(url, URL, NSString)
WX_CUSTOM_ATTRIBUTE(html, html, NSString)

+ (Class)viewClass {
    return [WKWebView class];
}

- (void)applyPropertiesToView:(UIView *)view {
    [super applyPropertiesToView:view];
    WKWebView *webView = (WKWebView *)view;
    if (self.html.length > 0) {
        [webView loadHTMLString:self.html baseURL:nil];
    } else if (self.URL.length > 0) {
        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.URL]]];
    }
}

這個 Component 支援 urlhtml 兩種 attribute,意味著可以從 url 或者 html 程式碼渲染他。我們把它註冊到 webnode 這個名字上面,就能在詳情頁使用它了:

<template>
  <container>
    <WebNode url="{{context.url}}" class="webnode"></WebNode>
  </container>
</template>

<script>
  module.exports = {
    data: {
      context: "{{context}}"
    }
  }
</script>

<style>
  .webnode {
    height: 1222;
    flex: 1;
  }
</style>

然後最最關鍵的事情來了,當使用者點選一行的時候,我們要從首頁跳到詳情頁,而這兩個頁面都是 Weex 的(目前貌似 Weex 沒有提供這樣的能力)。

但是沒有關係,我們有 Module,這裡我實現了一套邏輯,包括了本地頁面 Push 和 Weex 頁面上下文傳遞邏輯。

- (void)push:(NSDictionary *)params {
    ViewController *controller = [[ViewController alloc] init];
    controller.URL = [NSURL URLWithString:params[@"url"]];
    controller.context = params[@"context"];
    UINavigationController *navigator = [(AppDelegate *)[[UIApplication sharedApplication] delegate] navigator];
    [navigator pushViewController:controller animated:YES];
}

然後在首頁 onclick 的時候,只要把 context 帶到 Module 裡面的 push 方法,往下透傳過下一個 Weex 頁面,就能完成下個頁面的初始化。

onclick: function(e) {
  var news_id = e.target.attr["news_id"];
  var context = this;
  var api = this.api(news_id);
  this.get(api, function(json) {
    var params = {
      url: "next weex url",
      context: {
        url: json.share_url,
        title: json.title
      }
    };
    context.$call("event", "push", params);
  });
}

具體來說,這裡面使用了一個資料繫結上面的技巧,我把 weex 頁面上的 data 欄位放置了一個 “{{context}}”

<script>
  module.exports = {
    data: {
      context: "{{context}}"
    }
  }
</script>

這是一個佔位符,會在 Native Code 裡面被 Push 傳進來的 context 替換(這是一個 NSDictionary),處理的過程只是做一次字串替換

#import "WXHelper.h"
#import "JSONHelper.h"

@implementation WXHelper

+ (NSString *)scriptWithURL:(NSURL *)URL context:(NSDictionary *)context {
    
    NSString *script = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:nil];
    
    if (context == nil || ![context isKindOfClass:[NSDictionary class]]) {
        return script;
    }
    
    return [script stringByReplacingOccurrencesOfString:@""{{context}}""
                                             withString:JSONStringWithObject(context) ?: @""];
}

@end

然後我們的上下文就會被繫結到下一個 weex 頁面,其實根據這個思路,我們可以做更多的 Weex 和本地的互動,甚至把它做成 Framework 方便使用。

整個流程如下:

首頁 weex 渲染 –> onclick –> module 傳遞上下文到下一個 weex –> weex 自定義元件 WebView 渲染

以上就是整個 demo 的完整思路。

最後

Weex 的模組註冊、元件註冊非常的有用,給業務擴充套件自己的能力,自定義自己的功能提供了強大的基礎。
當然也希望 Weex 可以在 Native 互動方面提供更多有用的方法。


相關文章