Weex的简要学习与记录

虽说学习并试用了weex,但是博客却写不出太多的内容

2017-02-26 | 阅读

weex

Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能、可扩展的 native 应用,为了做到这些,Weex 与 Vue 合作,使用 Vue 作为上层框架,并遵循 W3C 标准实现了统一的 JSEngine 和 DOM API,这样一来,你甚至可以使用其他框架驱动 Weex,打造三端一致的 native 应用。

环境搭建

对于weex,我们主要使用weex-toolkit这个工具,安装不应该直接使用npm install -g weex-toolkit ,因为官方渠道的工具可能不是最新版本,最好还是去github 下载源码,然后直接使用npm install -g编译安装最新版本的工具。

而项目的搭建和一些特性的使用,建议学习官方示例, 这里有许多最佳实践 。

项目结构创建 :

weex init projectname

可以观察项目结构:

app.js为编译入口,weex.html为h5端显示入口,package.json为依赖配置文件,webpack.config.jsonwebpack配置文件,src中放置源码文件,而build中为编译后的输出文件。

开启服务器 :

npm run serve

开启webpackwatch编译模式:

npm run dev

webpack配置文件中,我们可以发现 :

var webConfig = getBaseConfig()
webConfig.output.filename = '[name].web.js'
webConfig.module.loaders[1].loaders.push('vue')

var weexConfig = getBaseConfig()
weexConfig.output.filename = '[name].weex.js'
weexConfig.module.loaders[1].loaders.push('weex')

weex将源文件编译成两种文件,.web.js为使用vue引擎解析,用于浏览器,而.weex.js为使用weex引擎解析,适用于iOSAndroid端。

集成到iOS中

集成到iOS中比较简单。 使用cocoapods添加添加依赖:

pod 'WeexSDK'

但是直接集成,居然不是源码形式,导致很多问题,所以我们需要源码集成,将ios/sdk目录拷贝到项目目录中,在podfile中编写本地依赖 :

pod 'WeexSDK', :path=>'./sdk/' 

但是由于weexpodspec写的有些问题,我们需要先简单修改一下podspec文件 ,将下一行内容删除 :

s.user_target_xcconfig  = { 'FRAMEWORK_SEARCH_PATHS' => "'$(PODS_ROOT)/WeexSDK'" }

pod install之后, 在使用weex之前,需要初始化weex文件,如在AppDelegate.m中添加:

//business configuration
[WXAppConfiguration setAppGroup:@"AliApp"];
[WXAppConfiguration setAppName:@"WeexDemo"];
[WXAppConfiguration setAppVersion:@"1.0.0"];
//init sdk enviroment   
[WXSDKEngine initSDKEnviroment];
//register custom module and component,optional
[WXSDKEngine registerComponent:@"MyView" withClass:[MyViewComponent class]];
[WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];
//register the implementation of protocol, optional
[WXSDKEngine registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)];
//set the log level    
[WXLog setLogLevel: WXLogLevelAll];

要加载weex页面,我们需要一个ViewController作为容器,在ViewController中初始化weex容器 :

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _instance = [[WXSDKInstance alloc] init];
    _instance.viewController = self;
    _instance.frame = self.view.frame;
    self.view.backgroundColor = [UIColor whiteColor];
    
    
    __weak typeof(self) weakSelf = self;
    _instance.onCreate = ^(UIView *view) {
        [weakSelf.weexView removeFromSuperview];
        weakSelf.weexView = view;
        [weakSelf.view addSubview:weakSelf.weexView];
    };
    
    _instance.onFailed = ^(NSError *error) {
        //process failure
    };
    
    _instance.renderFinish = ^ (UIView *view) {
        //process renderFinish
    };
    [_instance renderWithURL:_url options:@{@"bundleUrl":[self.url absoluteString]} data:nil];
}

在退出时,需要销毁weex instance

- (void)dealloc
{
    [_instance destroyInstance];
}

也可以直接使用Weex中默认提供的WXBaseViewController

原理探究

Weex将开发者的编写的JS代码,编译成一个weex支持的JS bundle, 在客户端使用JavaScript引擎来执行js bundle,在iOS中使用JavaScriptCore,在Android中使用v8内核,将界面代码转换为Natvie界面,而逻辑依旧在引擎中运行,通过JS和Native的交互,实现响应,实现了性能与效果与原生相同,但逻辑与界面使用可以动态下发的js来控制。

weex 内置 components

  • <a> , 跳转
  • <indicator> : 指示器,一般是几个点,与slider组合做图片轮播用。
  • <switch> : 开关按钮
  • <text> : 文本框
  • <textarea> : 多行输入框
  • <video> : 视频播放器
  • <web> : 网页
  • <div> : 所有组件的父类。
  • <image> : 图片
  • <input> : 单行输入
  • <list> : 列表 , 也是最核心的组件,对于RN 和weex其最重要的就是 list的性能问题。
  • <cell> : cell是 list的子组件,由cell这个单元组成了list.
  • <refresh><loading>是在下拉刷新时的刷新和加载提示。
  • loading-indicator
  • <scroller> : 滚动视图
  • <slider> : 图片滚动

自定义module

对于 js构建的组件,使用vue的形式,且这是最推荐的组件构建方式。

对于native构建的组件 :

  1. 自定义的module类 必须实现 WXModuleProtocol
  2. 必须添加宏WX_EXPORT_METHOD, 它可以被weex识别,它的参数是 JavaScript调用 module指定方法的参数
  3. 添加@synthesized weexInstance,每个moudle对象被绑定到一个指定的实例上
  4. Module 方法会在UI线程中被调用,所以不要做太多耗时的任务在这里,如果要在其他线程执行整个module 方法,需要实现WXModuleProtocol- (NSThread *)targetExecuteThread的方法,这样,分发到这个module的任务会在指定的线程中运行
  5. Weex 的参数可以是 String 或者Map
  6. Module 支持返回值给 JavaScript中的回调,回调的类型是WXModuleCallback,回调的参数可以是String或者Map

     @implementation WXEventModule
     @synthesize weexInstance;
         WX_EXPORT_METHOD(@selector(openURL:callback))
     - (void)openURL:(NSString *)url callback:(WXModuleCallback)callback
     {
         NSString *newURL = url;
         if ([url hasPrefix:@"//"]) {
             newURL = [NSString stringWithFormat:@"http:%@", url];
         } else if (![url hasPrefix:@"http"]) {
             newURL = [NSURL URLWithString:url relativeToURL:weexInstance.scriptURL].absoluteString;
         }
         UIViewController *controller = [[WXDemoViewController alloc] init];
         ((WXDemoViewController *)controller).url = [NSURL URLWithString:newURL];
         [[weexInstance.viewController navigationController] pushViewController:controller animated:YES];
         callback(@{@"result":@"success"});
     }
     @end
    

另外,0.10.0 开始支持同步模块 API 调用,您可以使用宏 WX_EXPORT_METHOD_SYNC 导出模块方法,这些方法可以使 JavaScript 接受从 native 返回的值,它只能在 JS 线程被调用。

需要在初始化weex的时候显式调用registerModule:withClass来注册自己的module

使用时 :

var eventModule = weex.requireModule('event'); 
eventModule.openURL('url',function(ret) {   
    nativeLog(ret);
});

自定义component

自定义component继承于WXComponent,需要显式的通过registerComponent:withClass:方法进行注册,同时定义组件标签名。

我们可以为组件添加属性 :

@interface WXImageComponent ()
@property (nonatomic, strong) NSString *imageSrc;
@property (nonatomic, assign) UIViewContentMode resizeMode;
@end

添加方法:

WX_EXPORT_METHOD(@selector(focus))
WX_EXPORT_METHOD(@selector(blur))


-(void)focus
{
    if(self.inputView) {
        [self.inputView becomeFirstResponder];
    }
}

-(void)blur
{
    if(self.inputView) {
        [self.inputView resignFirstResponder];
    }
}

需要注意的是, componentmodule所注册的方法不同,其走的js与native交互流程不同,所以组件注册的方法不支持回调。

初始化时,我们为组件的一些属性赋予初始值 :

@implementation WXImageComponent
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
    if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
        _imageSrc = [WXConvert NSString:attributes[@"src"]];
        _resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
}
    return self;
}
@end

我们在loadView中注册该组件真正的UI :

- (UIView *)loadView
{
	return [[WXImageView alloc] init];
}

viewDidLoad方法中对UI进行初始化。

updateAttributes时,更新对应的属性,并更新UI:

- (void)updateAttributes:(NSDictionary *)attributes
{
    if (attributes[@"src"]) {
        _imageSrc = [WXConvert NSString:attributes[@"src"]];
        // Do your image updating logic
    }
    if (attributes[@"resize"]) {
        _resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
        self.view.contentMode = _resizeMode;
    }
}

需要注意的是, 使用 Native注册的组件与Vue注册的组件,表现不同。 native注册的组件是全局可用的,而vue注册的组件还要声明才可以使用。Native注册的组件不是一个vue对象,如$on等vue的属性和方法不存在。

weex中 js代码的命名方式和oc代码的命名方式

会发生交互的,主要有两个地方,一个是 oc属性 <-> css样式属性,

当自定义的组件的属性,可以放在css中进行配置,而OC中的命名规范是驼峰式,对于一个参数命名为 placeholderFontSize ,在css中为 placeholder-font-size.

而在OC属性和 html组件的属性转换时,则可以区分大小写,如果在html中定义属性 placeholderFontSize, 在oc中读到的也是这个名字placeholderFontSize.

一处是OC函数 <-> JS函数:

对于编写的module或者componenetJS暴露的接口,而这里函数转换,JS函数将OC函数的第一段内容作为函数名,无视之后的内容,如:

WX_EXPORT_METHOD(@selector(scrollToElement:options:))

在JS中调用时,调用函数为 scrollToElement(a,b)

学习weex的总结

简单来说,学习了Weex后,我觉得,weex还是需要继续发展一段时间的。而Vue.js确实是一个靠谱且高效的组件化框架,值得学习。

Weex提出的理想太大,三端统一 , 但这个难度太大,各个平台都有自己的特性,牺牲自己的特性而适应一个大而全的平台,是不合适的,也难以实现的。 Weex提出了这样的目标,但是并没有实现。