初識(shí) weex

什么是 weex

weex 是阿里開(kāi)源的一套三端通用方案,只要編寫一次代碼就能運(yùn)行在 iOS、Android 和 Web 上。這里是官網(wǎng)的介紹:
Weex 是一套簡(jiǎn)單易用的跨平臺(tái)開(kāi)發(fā)方案,能以 web 的開(kāi)發(fā)體驗(yàn)構(gòu)建高性能、可擴(kuò)展的 native 應(yīng)用,為了做到這些,Weex 與 Vue 合作,使用 Vue 作為上層框架,并遵循 W3C 標(biāo)準(zhǔn)實(shí)現(xiàn)了統(tǒng)一的 JSEngine 和 DOM API,這樣一來(lái),你甚至可以使用其他框架驅(qū)動(dòng) Weex,打造三端一致的 native 應(yīng)用。
但是實(shí)際上,weex 是從阿里的 WeeApp 發(fā)展而來(lái)的,看開(kāi)源之后 github 上面提交的記錄,大部分工作都是 Vue 框架解析成原生組件的代碼,原生組件早就寫完了。

但是,由于這種版本升級(jí)似的改動(dòng),就造成了一些坑。一開(kāi)始編寫 weex 應(yīng)用的源碼文件擴(kuò)展名是.we,后來(lái),weex 跟 Vue.js 合作直接使用 .vue 文件作為源碼,編寫的方式也改為了 Vue 的格式。
局限在于,并不是所有 Web 特性都支持的,也可以說(shuō)支持的特性很少并且坑很多。
官方的特性支持列表
目前 weex 正處于向 Vue 遷移的過(guò)程,許多文檔和示例代碼都比較舊,bug 也比較多,還不能作為正式的生產(chǎn)工具,最近 weex 正式成為了 Apache 孵化項(xiàng)目,希望能夠成為 ReactNative 這樣的成熟項(xiàng)目。

安裝開(kāi)發(fā)環(huán)境

安裝過(guò)程還是非常友好的,按照官方的教程做就可以了:
Weex 官方提供了 weex-toolkit 的腳手架工具來(lái)輔助開(kāi)發(fā)和調(diào)試。首先,你需要 Node.js 和 weex-toolkit安裝 Node.js 方式多種多樣,最簡(jiǎn)單的方式是在 Node.js 官網(wǎng) 下載可執(zhí)行程序直接安裝即可。對(duì)于 Mac,可以使用 Homebrew 進(jìn)行安裝:

brew install node

安裝完成后,可以使用以下命令檢測(cè)是否安裝成功:

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

通常,安裝了 Node.js 環(huán)境,npm 包管理工具也隨之安裝了。因此,直接使用 npm 來(lái)安裝 weex-toolkit。
但是如果想用 .vue 的模板來(lái)編寫,需要按照說(shuō)明下載 beta 版本的才行!否則下載的環(huán)境生成的項(xiàng)目模板是 .we 的。
weex-toolkit 目前僅有最新的 beta 版本開(kāi)始才支持初始化 Vue 項(xiàng)目,使用前請(qǐng)確認(rèn)版本是否正確。

$ npm install -g weex-toolkit@beta

如果提示權(quán)限錯(cuò)誤(permission error),使用 sudo 關(guān)鍵字進(jìn)行安裝

$ sudo cnpm install -g weex-toolkit@beta

準(zhǔn)備調(diào)試工具

調(diào)試需要 iOS/Android 真機(jī)和 Chrome 瀏覽器。阿里已經(jīng)編寫了一個(gè)原生 app 叫做 "Playground App",可以下載使用,但是最好的辦法還是自己搭建一個(gè) app 畢竟最終還是要把 weex 整合到 app 中才行的。
iOS 的集成很簡(jiǎn)單:

  1. 使用 cocoapods 安裝 WXDevtool。
  2. 初始化 weex 頁(yè)面.
- (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 運(yùn)行起來(lái)之后,會(huì)把代碼打包成一個(gè) js bundle, 只要加載上就可以了,無(wú)論是從 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"]]];
}

注意銷毀,不要造成內(nèi)存泄露。

- (void)dealloc{
    [self.wxInstance destroyInstance];
}
  1. 修改 weex 狀態(tài)
    參考阿里的 demo,在自己的 app 里面也及時(shí)修改 weex 狀態(tài),狀態(tài)的改變會(huì)直接回調(diào)到 javascript 的相應(yīng)函數(shù)中。
- (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 進(jìn)行編寫的。

初始化項(xiàng)目

直接看官方文檔好了,這部分講解的很清楚。
《Weex 快速上手》
使用 weex-toolkit v1.0.4 按照官方文檔很容易就配置好項(xiàng)目了。
只要使用 weex init 初始化項(xiàng)目,然后進(jìn)入項(xiàng)目目錄,輸入 npm install 配置依子模塊就好了。
各種配置文件已經(jīng)準(zhǔn)備好,幾乎不用修改。

使用 weex 組件和 css 編寫頁(yè)面

weex 三端通用的原理。


flow-2017331

看官方文檔上的圖。
源代碼會(huì)編譯成 jsbundle 然后分別發(fā)送給三端解析。
H5 端直接使用 Vue.js 解析預(yù)置的 H5 組件。iOS 端與 android 端直接把組件解析成原生實(shí)現(xiàn)。
因?yàn)閷?duì) css 的解析,需要原生來(lái)做,所以 weex 支持的 css 布局和特性是比較少的。
具體使用可以參考官方文檔。

在 weex 中支持的比較好的布局方式是 flex 布局,block 布局就經(jīng)常存在表現(xiàn)不一致的情況。

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

使用限制

  • 只支持單個(gè)類名選擇器,不支持關(guān)系選擇器,也不支持屬性選擇器。
  • 默認(rèn)是組件級(jí)別的作用域,沒(méi)有全局樣式。
  • 不支持樣式繼承(因?yàn)橛凶饔糜蚋綦x)。
  • 考慮到樣式的數(shù)據(jù)綁定,樣式屬性暫不支持簡(jiǎn)寫。

組件的使用

組件使用只要參考官方文檔就可以了,雖然一開(kāi)始寫起來(lái)有些別扭,例如<a>標(biāo)簽中不能包含文字,但是按照文檔寫也能完成任務(wù)。

有些控件可能不太符合要求,例如<video>標(biāo)簽,在 iOS 上面的實(shí)現(xiàn)是一款開(kāi)源的播放器,自帶的 UI 可能會(huì)不符合要求,這種可以自己修改 native 的實(shí)現(xiàn)。

自定義組件

Weex 自定義組件有兩種方式,一種是使用 Vue 的組件定制方式,另一種是三端分別實(shí)現(xiàn)相應(yīng)組件。
在擴(kuò)展 Weex 組件時(shí),如果只使用了 Weex 提供的內(nèi)置組件,并且使用的都是 Weex 支持的樣式,那么就和普通的自定義組件無(wú)異,不需要 Native 端再有相應(yīng)的實(shí)現(xiàn)。如果你定制組件時(shí)不得不用到目前 Weex 不支持的標(biāo)簽和樣式,在這種情況下才是真正的“擴(kuò)展”了 Weex 的組件,你還需要在 Android 和 iOS 中有相應(yīng)的實(shí)現(xiàn),不然會(huì)導(dǎo)致渲染異常。
Weex HTML 擴(kuò)展

- (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 對(duì)象
  2. 使用- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance 初始化對(duì)象并接收參數(shù)。
  3. 在 - (void)viewDidLoad 中初始化自定義 UI。不要在 - (void)viewWillLoad 初始化 UI 會(huì)導(dǎo)致- (void)viewWillLoad 和 - (UIView *)loadView 反復(fù)調(diào)用,最終出現(xiàn) StackOverFlow。原因還需要查看一下 Weex 源碼來(lái)確定。
  4. 必要的情況下,使用- (void)fireEvent:(NSString *)eventName params:(nullable NSDictionary *)params 向 Vue.js 框架發(fā)送事件。
    首先,在 Android 里肯定是可以監(jiān)聽(tīng)到“返回”按鈕的點(diǎn)擊事件的,其實(shí)只要實(shí)現(xiàn) Activity 里的 onBackPressed 接口就可以了,它會(huì)在當(dāng)前視圖里點(diǎn)擊返回按鈕時(shí)執(zhí)行。在 weex-hackernews Andorid 項(xiàng)目里的 MainActivity.java 中,就實(shí)現(xiàn)了 onBackPressed 接口:
public void onBackPressed() {
    Log.e("USER ACTION", "BACK");
    WXSDKManager.getInstance().fireEvent(mWXSDKInstance.getInstanceId(), "_root", "androidback");
}

在這個(gè)方法里,通過(guò) WXSDKManager.getInstance() 取到了當(dāng)前頁(yè)面的實(shí)例,然后調(diào)用 fireEvent 接口給根視圖派發(fā) androidback 事件,事件名是可以自定義的。在 Weex Runtime 中會(huì)接收到這個(gè)事件,會(huì)傳遞給 Vue.js 框架,并且觸發(fā)最外層組件的 androidback 事件,最終會(huì)找到 back 方法并執(zhí)行。(這里說(shuō)的 Weex Runtime 是前端代碼實(shí)現(xiàn)的,比 Vue.js 更底層一些)。
在 Android 中派發(fā)原生事件

  1. 為 HTML 編寫適合 Web 端特性的組件。參考 weex-html5 擴(kuò)展開(kāi)發(fā)指引 文章比較老,是 we 文件的示例。

如果編寫的組件使用 weex 已有的組件就可以拼裝而成,直接使用 Vue.js 的編寫單文件組件的方式編寫就可以了

開(kāi)始進(jìn)行調(diào)試

由于 Weex 開(kāi)發(fā)工具還在快速變化,所以文檔上寫的不怎么清除。
如果是調(diào)試單獨(dú)的 vue 文件,直接使用 weex debug 命令就好了。
但是此時(shí)程序的入口其實(shí)是 weex.html -> 單個(gè) vue 文件。所以存在著 Vue.use 添加全局插件無(wú)效的情況。
文檔上說(shuō),使用 weex debug [目錄](méi) -e [入口文件] 可以調(diào)試目錄,但是測(cè)試沒(méi)有成功,訪問(wèn) url 找不到文件。
使用 weex debug 相關(guān)命令后,會(huì)自動(dòng)打開(kāi)瀏覽器窗口。


DingTalk20170331103639-2017331

如果用官方的 playground 調(diào)試,可以掃描右側(cè)二維碼,如果自己項(xiàng)目中調(diào)試,可以直接填寫鏈接。
把 jsbundle 直接打包進(jìn) app 中也可以,總之,讀取 jsbundle 就好。

DingTalk20170331104943-2017331

有手機(jī)連接后,會(huì)顯示這個(gè)調(diào)試界面,手機(jī)和 Nodejs 服務(wù)會(huì)使用 websocket 通信,挺好用的。

最后說(shuō)一下目錄調(diào)試。

說(shuō)是調(diào)試,其實(shí)是直接運(yùn)行。因?yàn)闆](méi)有搞清楚如何用 weex debug 命令調(diào)試目錄,于是就直接運(yùn)行了編譯好的 jsbundle。
直接輸入 npm run dev 命令和 npm run serve,如果8080端口沒(méi)有占用,那么訪問(wèn) localhost:8080/weex.html 就打開(kāi)了 app.web.js 這個(gè) bundle。
在 app 里面訪問(wèn) serviceip:8080/dist/app.weex.js 就可以打開(kāi)這個(gè) bundle。

Hanks10100 大神寫的比較系統(tǒng)的介紹 weex 文章
使用 Weex 和 Vue 開(kāi)發(fā)原生應(yīng)用 —— 0 項(xiàng)目介紹和文章目錄

詳細(xì)全面的基于vue2.0Weex接入過(guò)程(Android視角)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容