初识 weex

作者: Tatinic | 来源:发表于2017-04-18 16:31 被阅读667次

    什么是 weex

    weex 是阿里开源的一套三端通用方案,只要编写一次代码就能运行在 iOS、Android 和 Web 上。这里是官网的介绍:
    Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能、可扩展的 native 应用,为了做到这些,Weex 与 Vue 合作,使用 Vue 作为上层框架,并遵循 W3C 标准实现了统一的 JSEngine 和 DOM API,这样一来,你甚至可以使用其他框架驱动 Weex,打造三端一致的 native 应用。
    但是实际上,weex 是从阿里的 WeeApp 发展而来的,看开源之后 github 上面提交的记录,大部分工作都是 Vue 框架解析成原生组件的代码,原生组件早就写完了。

    但是,由于这种版本升级似的改动,就造成了一些坑。一开始编写 weex 应用的源码文件扩展名是.we,后来,weex 跟 Vue.js 合作直接使用 .vue 文件作为源码,编写的方式也改为了 Vue 的格式。
    局限在于,并不是所有 Web 特性都支持的,也可以说支持的特性很少并且坑很多。
    官方的特性支持列表
    目前 weex 正处于向 Vue 迁移的过程,许多文档和示例代码都比较旧,bug 也比较多,还不能作为正式的生产工具,最近 weex 正式成为了 Apache 孵化项目,希望能够成为 ReactNative 这样的成熟项目。

    安装开发环境

    安装过程还是非常友好的,按照官方的教程做就可以了:
    Weex 官方提供了 weex-toolkit 的脚手架工具来辅助开发和调试。首先,你需要 Node.js 和 weex-toolkit安装 Node.js 方式多种多样,最简单的方式是在 Node.js 官网 下载可执行程序直接安装即可。对于 Mac,可以使用 Homebrew 进行安装:

    brew install node
    

    安装完成后,可以使用以下命令检测是否安装成功:

    $ node -v
    v6.3.1
    $ npm -v
    3.10.3
    

    通常,安装了 Node.js 环境,npm 包管理工具也随之安装了。因此,直接使用 npm 来安装 weex-toolkit。
    但是如果想用 .vue 的模板来编写,需要按照说明下载 beta 版本的才行!否则下载的环境生成的项目模板是 .we 的。
    weex-toolkit 目前仅有最新的 beta 版本开始才支持初始化 Vue 项目,使用前请确认版本是否正确。

    $ npm install -g weex-toolkit@beta
    

    如果提示权限错误(permission error),使用 sudo 关键字进行安装

    $ sudo cnpm install -g weex-toolkit@beta
    

    准备调试工具

    调试需要 iOS/Android 真机和 Chrome 浏览器。阿里已经编写了一个原生 app 叫做 "Playground App",可以下载使用,但是最好的办法还是自己搭建一个 app 毕竟最终还是要把 weex 整合到 app 中才行的。
    iOS 的集成很简单:

    1. 使用 cocoapods 安装 WXDevtool。
    2. 初始化 weex 页面.
    - (void)viewWillAppear:(BOOL)animated{
        self.wxInstance = [[WXSDKInstance alloc]init];
        self.wxInstance.viewController = self;
        self.wxInstance.frame = self.view.bounds;
        __weak typeof(self) weakSelf = self;
        self.wxInstance.onCreate = ^(UIView* view){
            [weakSelf.weexView removeFromSuperview];
            weakSelf.weexView = view;
            [view removeFromSuperview];
            [weakSelf.view addSubview:view];
        };
        
    
        self.wxInstance.onFailed = ^(NSError* error){
            weakSelf.wxInstance.viewController = nil;
        };
        self.wxInstance.renderFinish = ^(UIView* view){
            [weakSelf updateInstanceState:WeexInstanceAppear];
            NSLog(@"%@",view);
        };
    //weex 运行起来之后,会把代码打包成一个 js bundle, 只要加载上就可以了,无论是从 weburl 还是从 fileurl 加载都是很方便的.    
    //    [self.wxInstance renderWithURL:[NSURL URLWithString:@"http://192.168.25.85:8088/weex/foo.js"] options:@{@"bundleUrl":@"http://192.168.25.85:8088/weex/foo.js"} data:nil];
        [self.wxInstance renderWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"foo" ofType:@"js"]]];
    }
    

    注意销毁,不要造成内存泄露。

    - (void)dealloc{
        [self.wxInstance destroyInstance];
    }
    
    1. 修改 weex 状态
      参考阿里的 demo,在自己的 app 里面也及时修改 weex 状态,状态的改变会直接回调到 javascript 的相应函数中。
    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
        [self updateInstanceState:WeexInstanceAppear];
    }
    
    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        [self updateInstanceState:WeexInstanceDisappear];
    }
    
    - (void)updateInstanceState:(WXState)state
    {
        if (self.wxInstance && self.wxInstance.state != state) {
            self.wxInstance.state = state;
            
            if (state == WeexInstanceAppear) {
                [[WXSDKManager bridgeMgr] fireEvent:self.wxInstance.instanceId ref:WX_SDK_ROOT_REF type:@"viewappear" params:nil domChanges:nil];
            }
            else if (state == WeexInstanceDisappear) {
                [[WXSDKManager bridgeMgr] fireEvent:self.wxInstance.instanceId ref:WX_SDK_ROOT_REF type:@"viewdisappear" params:nil domChanges:nil];
            }
        }
    }
    

    这部分是参考 Demo 进行编写的。

    初始化项目

    直接看官方文档好了,这部分讲解的很清楚。
    《Weex 快速上手》
    使用 weex-toolkit v1.0.4 按照官方文档很容易就配置好项目了。
    只要使用 weex init 初始化项目,然后进入项目目录,输入 npm install 配置依子模块就好了。
    各种配置文件已经准备好,几乎不用修改。

    使用 weex 组件和 css 编写页面

    weex 三端通用的原理。


    flow-2017331

    看官方文档上的图。
    源代码会编译成 jsbundle 然后分别发送给三端解析。
    H5 端直接使用 Vue.js 解析预置的 H5 组件。iOS 端与 android 端直接把组件解析成原生实现。
    因为对 css 的解析,需要原生来做,所以 weex 支持的 css 布局和特性是比较少的。
    具体使用可以参考官方文档。

    在 weex 中支持的比较好的布局方式是 flex 布局,block 布局就经常存在表现不一致的情况。

    • 支持基本的盒模型。
    • 支持 position 定位布局。
    • 支持使用 flexbox 布局。

    使用限制

    • 只支持单个类名选择器,不支持关系选择器,也不支持属性选择器。
    • 默认是组件级别的作用域,没有全局样式。
    • 不支持样式继承(因为有作用域隔离)。
    • 考虑到样式的数据绑定,样式属性暂不支持简写。

    组件的使用

    组件使用只要参考官方文档就可以了,虽然一开始写起来有些别扭,例如<a>标签中不能包含文字,但是按照文档写也能完成任务。

    有些控件可能不太符合要求,例如<video>标签,在 iOS 上面的实现是一款开源的播放器,自带的 UI 可能会不符合要求,这种可以自己修改 native 的实现。

    自定义组件

    Weex 自定义组件有两种方式,一种是使用 Vue 的组件定制方式,另一种是三端分别实现相应组件。
    在扩展 Weex 组件时,如果只使用了 Weex 提供的内置组件,并且使用的都是 Weex 支持的样式,那么就和普通的自定义组件无异,不需要 Native 端再有相应的实现。如果你定制组件时不得不用到目前 Weex 不支持的标签和样式,在这种情况下才是真正的“扩展”了 Weex 的组件,你还需要在 Android 和 iOS 中有相应的实现,不然会导致渲染异常。
    Weex HTML 扩展

    - (instancetype)initWithRef:(NSString *)ref
                           type:(NSString*)type
                         styles:(nullable NSDictionary *)styles
                     attributes:(nullable NSDictionary *)attributes
                         events:(nullable NSArray *)events
                   weexInstance:(WXSDKInstance *)weexInstance{
        if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
            if ([attributes objectForKey:@"content"]) {
                id content = [attributes objectForKey:@"content"];
                if ([content isKindOfClass:[NSString class]]) {
                    self.title = (NSString*)content;
                }
            }
            return self;
        }
        return nil;
    }
    
    
    - (void)viewDidLoad{
        [super viewDidLoad];
        if (self.title == nil) {
            self.title = [NSString string];
        }
        [self showHUDProgressingBarWithTitle:self.title];
    }
    
    - (void)viewWillUnload{
        [self dismissHUDProgressingBar];
        [super viewWillUnload];
    }
    
    - (void)showHUDProgressingBarWithTitle:(NSString *)title {
        CGSize screenSize = self.view.bounds.size;
        if(!self.hudView){
            self.hudView = [[UIView alloc] initWithFrame:CGRectMake((screenSize.width - DEFAULT_HUD_VIEW_WIDTH) / 2,
                                                                    (screenSize.height - DEFAULT_HUD_VIEW_HEIGHT) / 2,
                                                                    DEFAULT_HUD_VIEW_WIDTH, DEFAULT_HUD_VIEW_HEIGHT)];
            
            self.hudView.backgroundColor = [UIColor clearColor];
            UIFont* titleFont = [UIFont systemFontOfSize:DEFAULT_HUD_TITLE_FONTSIZE];
            CGFloat titleHeight = [title sizeWithAttributes:@{
                                                              NSFontAttributeName : titleFont
                                                              }].height;
            self.indicatorView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
            self.indicatorView.center = CGPointMake(self.hudView.bounds.size.width/2, self.hudView.bounds.size.height/2);
            self.hudViewTitle = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, DEFAULT_HUD_VIEW_WIDTH, titleHeight)];
            self.hudViewTitle.center = CGPointMake(self.hudView.bounds.size.width/2, self.indicatorView.center.y + (self.indicatorView.bounds.size.height/2 + self.hudViewTitle.bounds.size.height/2 + 12));
            self.hudViewTitle.font = titleFont;
            self.hudViewTitle.textColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
            self.hudViewTitle.textAlignment = NSTextAlignmentCenter;
            [self.hudView addSubview:self.indicatorView];
            [self.hudView addSubview:self.hudViewTitle];
            [self.view addSubview:self.hudView];
        }
        self.hudView.frame = CGRectMake((screenSize.width - DEFAULT_HUD_VIEW_WIDTH) / 2,
                                        (screenSize.height - DEFAULT_HUD_VIEW_HEIGHT) / 2,
                                        DEFAULT_HUD_VIEW_WIDTH, DEFAULT_HUD_VIEW_HEIGHT);
        self.hudView.hidden = NO;
        self.hudViewTitle.text = title;
        [self.indicatorView startAnimating];
        [self.view bringSubviewToFront:self.hudView];
    }
    
    
    - (void)dismissHUDProgressingBar {
        if (self.hudView) {
            [self.indicatorView stopAnimating];
            self.hudView.hidden = YES;
        }
    }
    
    
    1. 自定义组件类,继承 WXComponent 对象
    2. 使用- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance 初始化对象并接收参数。
    3. 在 - (void)viewDidLoad 中初始化自定义 UI。不要在 - (void)viewWillLoad 初始化 UI 会导致- (void)viewWillLoad 和 - (UIView *)loadView 反复调用,最终出现 StackOverFlow。原因还需要查看一下 Weex 源码来确定。
    4. 必要的情况下,使用- (void)fireEvent:(NSString *)eventName params:(nullable NSDictionary *)params 向 Vue.js 框架发送事件。
      首先,在 Android 里肯定是可以监听到“返回”按钮的点击事件的,其实只要实现 Activity 里的 onBackPressed 接口就可以了,它会在当前视图里点击返回按钮时执行。在 weex-hackernews Andorid 项目里的 MainActivity.java 中,就实现了 onBackPressed 接口:
    public void onBackPressed() {
        Log.e("USER ACTION", "BACK");
        WXSDKManager.getInstance().fireEvent(mWXSDKInstance.getInstanceId(), "_root", "androidback");
    }
    

    在这个方法里,通过 WXSDKManager.getInstance() 取到了当前页面的实例,然后调用 fireEvent 接口给根视图派发 androidback 事件,事件名是可以自定义的。在 Weex Runtime 中会接收到这个事件,会传递给 Vue.js 框架,并且触发最外层组件的 androidback 事件,最终会找到 back 方法并执行。(这里说的 Weex Runtime 是前端代码实现的,比 Vue.js 更底层一些)。
    在 Android 中派发原生事件

    1. 为 HTML 编写适合 Web 端特性的组件。参考 weex-html5 扩展开发指引 文章比较老,是 we 文件的示例。

    如果编写的组件使用 weex 已有的组件就可以拼装而成,直接使用 Vue.js 的编写单文件组件的方式编写就可以了

    开始进行调试

    由于 Weex 开发工具还在快速变化,所以文档上写的不怎么清除。
    如果是调试单独的 vue 文件,直接使用 weex debug 命令就好了。
    但是此时程序的入口其实是 weex.html -> 单个 vue 文件。所以存在着 Vue.use 添加全局插件无效的情况。
    文档上说,使用 weex debug [目录] -e [入口文件] 可以调试目录,但是测试没有成功,访问 url 找不到文件。
    使用 weex debug 相关命令后,会自动打开浏览器窗口。


    DingTalk20170331103639-2017331

    如果用官方的 playground 调试,可以扫描右侧二维码,如果自己项目中调试,可以直接填写链接。
    把 jsbundle 直接打包进 app 中也可以,总之,读取 jsbundle 就好。

    DingTalk20170331104943-2017331

    有手机连接后,会显示这个调试界面,手机和 Nodejs 服务会使用 websocket 通信,挺好用的。

    最后说一下目录调试。

    说是调试,其实是直接运行。因为没有搞清楚如何用 weex debug 命令调试目录,于是就直接运行了编译好的 jsbundle。
    直接输入 npm run dev 命令和 npm run serve,如果8080端口没有占用,那么访问 localhost:8080/weex.html 就打开了 app.web.js 这个 bundle。
    在 app 里面访问 serviceip:8080/dist/app.weex.js 就可以打开这个 bundle。

    Hanks10100 大神写的比较系统的介绍 weex 文章
    使用 Weex 和 Vue 开发原生应用 —— 0 项目介绍和文章目录

    详细全面的基于vue2.0Weex接入过程(Android视角)

    相关文章

      网友评论

        本文标题:初识 weex

        本文链接:https://www.haomeiwen.com/subject/jgfqzttx.html