欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入折腾 Weex,知乎日报客户端开发

程序员文章站 2024-03-16 10:19:10
...
深入折腾了一下 Weex,做了一个知乎日报的客户端,同时实现了一种目前 Weex 尚未提供的 Native 页面切换思路。

最后效果如下:

深入折腾 Weex,知乎日报客户端开发
深入折腾 Weex,知乎日报客户端开发
这个 demo 主要实践 4 件事情:

1. 模块注册
2. 组件注册
2. Native 方法调用
4. Native 页面切换 & 参数传递

整个 app 只有两个页面,都是纯 Weex 编写的,对于第一个页面非常的简单,布局类似于 iOS 的 TableView
<template>
  <scroller>
    <container repeat="{{list}}" news_id="{{news_id}}" οnclick="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 交互方面提供更多有用的方法。