Weex 從無到有開發(fā)一款上線應(yīng)用 1

iOS調(diào)試Demo
WeexDemo

初始化APPFrame

終端創(chuàng)建 Weex工程
weex init xxx_weex
安裝IDE插件

WebStorm安裝Weex語法支持和插件摩钙。由于最開始Weex文件為.we茸歧,現(xiàn)在Weex已經(jīng)完美支持Vue倚舀,所以現(xiàn)在也是用Vue開發(fā)。
具體安裝步驟如下


打開偏好設(shè)置.png
plugins.png

同樣的下載Vue.js
安裝后重啟WebStorm赴背。
由于WebStorm還不能直接創(chuàng)建Vue文件山宾,所以需要先添加一下Vue文件模板笔咽。操作如下

添加Vue文件模板.png

模板內(nèi)容如下

<template>
    <div class="view">
    </div>
</template>

<script>
    let stream = weex.requireModule('stream')
    let modal = weex.requireModule('modal')
    let navigator = weex.requireModule('navigator')
    let globalEvent = weex.requireModule('globalEvent');
    let apiHost = ''
    export default {
        data () {
            return {

            }
        },
        methods: {

        },
        created () {

        },
        mounted()
        {

        },
        components: {
            
        }
    }
</script>

<style scoped>
    .view {
        width: 750px;
        height: 1334px;
        background-color:#fff ;
    }
</style>
新建AppFrame.Vue文件
AppFrame.png

到這里Weex開發(fā)環(huán)境已經(jīng)完成。接下來我們就要開始擴展組件和模塊了蔫巩。

擴展原生AppFrame Component

由于Weex開發(fā)的是單個頁面谆棱,也沒有系統(tǒng)的ViewController 和 NavigationController以及TabBarController,那么我們怎么開始一個架設(shè)一個動態(tài)的應(yīng)用框架呢快压?這就需要我們書寫一個原生的AppFrame組件。
因為是從無到有開發(fā)础锐,我們就新建一個Xcode工程嗓节。
如果是已有工程那么就創(chuàng)建一個新的Target或者Project,或者新建工程通過pod導(dǎo)入皆警。
創(chuàng)建一個 WXComponent子類拦宣。
然后重寫-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance方法,
方法實現(xiàn)如下

-(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]) {
        
        [self firstInitAPPFrameWithAttributes:attributes];
        
    }
    return self;
}

類文件如下圖


AppFrameComponent.png
這樣做的思路:

tabbarItem的數(shù)據(jù)通過服務(wù)端下發(fā)的形式進行創(chuàng)建。
以及tabbarViewController.ViewControllers也通過服務(wù)端下發(fā)的形式進行創(chuàng)建信姓。
-------------2017.05.24 明晚再寫-----------------
如果App設(shè)計是最常用的TabbarUI鸵隧,那么下發(fā)的tabbarItem的數(shù)據(jù)結(jié)構(gòu)如下:

 {
//                            標(biāo)題
                            title:'',
//                            沒選中字體顏色
                            normalTitleColor:'999999',
//                            選中字體顏色
                            selectedTitleColor:'FF3C00',
//                            背景顏色
                            tintColor:'FF3C00',
//                            選中圖片
                            selectedImage:'',
//                            沒選中圖片
                            image:''
}

當(dāng)然有非常規(guī)的UI那么同樣需要配置的數(shù)據(jù)有這些,其他就需要針對應(yīng)用來自己設(shè)計數(shù)據(jù)結(jié)構(gòu)意推。
導(dǎo)航欄也同樣需要考慮系統(tǒng)的是否滿足需求豆瘫,不滿足的那么就在當(dāng)前頁面做一個假的導(dǎo)航欄(這樣有一定的缺陷,比如我們再通話中使用app那么假的導(dǎo)航欄需要處理成新的高度)菊值。下邊是針對系統(tǒng)導(dǎo)航欄的下發(fā)數(shù)據(jù)

{
//                            導(dǎo)航欄標(biāo)題
                            title:'',
//                            透明導(dǎo)航欄的字體顏色
                            clearTitleColor:'ffffff',
//                            高斯模糊導(dǎo)航欄的字體顏色
                            blurTitleColor:'000000',
//                            左邊選項按鈕
                            leftItemsInfo:
                                [
                                    {
//                                        原生調(diào)取 weex方法名
                                        aciton:'',
//                                        渲染 按鈕的鏈接  如果以http開頭 會渲染網(wǎng)絡(luò)JSBundle 反之 渲染本地JSBundle
                                        itemURL:''
                                    }
                                ],
//                            右邊選項按鈕
                            rightItemsInfo:
                                [
                                    {
                                        aciton:'',
                                        itemURL:host + dirctoryPath + 'DemoRefreshRightItem.js',
//                                        設(shè)計圖中container的大小
                                        frame:'{{0, 0}, {32, 16}}'
                                    }
                                ],
//                            把導(dǎo)航欄變成透明的
                            clearNavigationBar:true,
//                            把導(dǎo)航欄隱藏
                            hiddenNavgitionBar:false,
//                            導(dǎo)航欄背景顏色
                            navigationBarBackgroundColor:'',
//                            導(dǎo)航欄背景圖片
                            navgationBarBackgroundImage:'',
//                            自定義標(biāo)題視圖的JSBundle地址 如果以http開頭 會渲染網(wǎng)絡(luò)JSBundle 反之 渲染本地JSBundle
                            customTitleViewURL:'',
//                        這個是當(dāng)前導(dǎo)航欄棧頂?shù)腏SBundleURL
                            rootViewURL:host + dirctoryPath + 'index.js',
                        }
渲染過程

根據(jù)應(yīng)用的設(shè)計我們需要作出不同的AppFrame調(diào)整外驱,這個是毋庸置疑。再次回到原生APPFrameComponent類中腻窒。這里大體說一下WXSDKInstance在iOS端的渲染過程昵宇,具體的還是要看源碼,每個人讀的時候都有自己的見解認識儿子。
1.從- (void)renderWithURL:(NSURL *)url開始瓦哎,[WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy]處理URLRequest,_mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request]; [_mainBundleLoader start];下載JSBundle源柔逼。不贅述如何處理的URLRequest蒋譬,看一下在下載之前都處理什么(代碼有缺失,詳盡請看源碼)愉适。

- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
    WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
    [self _renderWithRequest:request options:options data:data];
}
- (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
{
    NSURL *url = request.URL;
    _scriptURL = url;
    _jsData = data;
    NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new];
    
    if (!newOptions[bundleUrlOptionKey]) {
        newOptions[bundleUrlOptionKey] = url.absoluteString;
    }
    // compatible with some wrong type, remove this hopefully in the future.
    if ([newOptions[bundleUrlOptionKey] isKindOfClass:[NSURL class]]) {
        WXLogWarning(@"Error type in options with key:bundleUrl, should be of type NSString, not NSURL!");
        newOptions[bundleUrlOptionKey] = ((NSURL*)newOptions[bundleUrlOptionKey]).absoluteString;
    }
    _options = [newOptions copy];
  //獲取網(wǎng)站主頁地址
    if (!self.pageName || [self.pageName isEqualToString:@""]) {
        self.pageName = [WXUtility urlByDeletingParameters:url].absoluteString ? : @"";
    }
    //這里會配置一些請求信息 比如手機系統(tǒng) 手機型號 app名字 屏幕信息等
    request.userAgent = [WXUtility userAgent];
    //告訴監(jiān)視器WXMonitor開始下載JSBundle
    WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
    __weak typeof(self) weakSelf = self;
    _mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];;
    _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //這里省略源碼犯助,此處驗證請求回調(diào)數(shù)據(jù)是否有效 并告知WXMonitor請求結(jié)果
        NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (!jsBundleString) {
            WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
            return;
        }
        WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
        WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
        [strongSelf _renderWithMainBundleString:jsBundleString];
    };
    _mainBundleLoader.onFailed = ^(NSError *loadError) {
//此處省略源碼 處理報錯信息
    };
    [_mainBundleLoader start];
}

2.開始渲染JSBundle,
首先會驗證WXSDKInstance的一些信息是否有誤维咸,并打出相應(yīng)Log也切。
接下來會創(chuàng)建WXRootView調(diào)用instanceonCreate(UIView * view){ } block。
重新調(diào)用一下引擎默認配置(

{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self _registerDefaultComponents];
        [self _registerDefaultModules];
        [self _registerDefaultHandlers];
    });
}```)
通過```WXSDKManager```取得```WXBridgeManager```實例腰湾,調(diào)用```- (void)createInstance:(NSString *)instance template:(NSString *)temp   options:(NSDictionary *)options data:(id)data
```還是會驗證參數(shù)是否有效雷恃,看渲染堆```instanceIdStack```中是否含有該```instanceId```,有的話看是否有在渲染的```instanceId```,有就添加任務(wù)费坊,沒有就把該id置頂創(chuàng)建倒槐,讓```WXBridgeContext```實例去渲染(該操作驗證并必須在```WXBridgeThread```)。到此就開始了JS引擎的渲染附井,可想而知會生成DOM樹讨越,然后逐個解析UI Component(js call native 創(chuàng)建以及渲染UI)會從```WXComponent```的```initWithRefxxxxx```開始两残。
######應(yīng)用BasicWeexViewController的實現(xiàn)
不多贅述,按照官方給的思路把跨,很容易就能封裝出人弓。這是我項目中的[XMWXViewController](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/ViewControllers/XMWXViewController.m)
######AppFrameComponent 實現(xiàn)
接上,AppFrame要做的就是將下發(fā)的tabbarItem 和 navigationItem形成框架 和UI着逐。不同UI設(shè)計對用不同的AppFrameComponent 實現(xiàn)崔赌。
下面是最常用最簡單(我項目中的[XMWXAPPFrameComponte](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/WeexComponent/APPFrame/Basic/XMWXAPPFrameComponte.m))的實現(xiàn):

pragma mark - private method

/**
初始化APP框架

@param attributes 返回的RenderInfo
*/
-(void)firstInitAPPFrameWithAttributes:(NSDictionary *)attributes
{
dispatch_async(dispatch_get_main_queue(), ^{
//設(shè)置APP
UIApplication * application = [UIApplication sharedApplication];
UITabBarController * tabarViewController = [[UITabBarController alloc] init];
// tabarViewController.view.alpha = 0;
UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[((UIResponder *)application.delegate) setValue:window forKey:@"window"];

    window.rootViewController = tabarViewController;
    
    window.backgroundColor = [UIColor whiteColor];
    
    [window makeKeyAndVisible];
    
    [self handleTabbarViewControllers:attributes tabarController:tabarViewController];
});

}
/**
創(chuàng)建tabbar.items
@param attributes component 下發(fā)數(shù)據(jù)
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
@return 創(chuàng)建的UITabBarItem集合
*/
-(NSMutableArray <UITabBarItem *> *)handleTabbarItems:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSString * tabItemsDictJsonString = [WXConvert NSString:attributes[XMWXAPPFrameComponteTabbarItemsKey]];

NSArray * tabItemsInfoArray = [NSJSONSerialization JSONObjectWithData:[tabItemsDictJsonString dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:nil];

NSMutableArray * tabarItems = [NSMutableArray array];

[tabItemsInfoArray enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    XMWXTabbarItem * xmItem = [XMWXTabbarItem itemWithDict:obj];
    UITabBarItem * item = [[UITabBarItem alloc] init];
    
    item.title = xmItem.title;
    
    if (xmItem.tintColor.length) {
        [tabarController.tabBar setTintColor:colorWithHexString(xmItem.tintColor, 1.f)];
    }
    
    if (xmItem.normalTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.normalTitleColor, 1.f)} forState:(UIControlStateNormal)];
    }
    if (xmItem.selectedTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.selectedTitleColor, 1.f)} forState:(UIControlStateSelected)];
    }
    if ([xmItem.image hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.image] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            [tabarController.tabBar setItems:tabarItems];
        }];
    }else
    {
        item.image = xmwx_imageForSetting(xmItem.image);
    }
    
    if ([xmItem.selectedImage hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.selectedImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

            [tabarController.tabBar setItems:tabarItems];
        }];
        
    }else
    {
        item.selectedImage = xmwx_imageForSetting(xmItem.selectedImage);
    }
    
    [tabarItems addObject:item];
}];
return tabarItems;

}
/**
渲染TabbarViewController

@param attributes component 下發(fā)數(shù)據(jù)
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
*/
-(void)handleTabbarViewControllers:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSMutableArray <UITabBarItem *> * tabbarItems = [self handleTabbarItems:attributes tabarController:tabarController];
NSArray * viewControllerItems = [NSJSONSerialization JSONObjectWithData:[[WXConvert NSString:[attributes objectForKey:XMWXAPPFrameComponteViewControllerItemsKey]] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];

NSMutableArray * viewControllers = [NSMutableArray array];

[viewControllerItems enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    XMWXNavigationItem * navigationItem = [XMWXNavigationItem infoWithDict:obj];
    
    XMWXViewController * viewController = [[XMWXViewController alloc] init];
    
    viewController.renderInfo = navigationItem;
    if (navigationItem.rootViewURL.length > 0) {
        if ([navigationItem.rootViewURL hasPrefix:@"http"]) {
            viewController.renderURL = [NSURL URLWithString:navigationItem.rootViewURL];
        }else
        {
            NSString * path = [[NSBundle mainBundle] pathForResource:navigationItem.rootViewURL ofType:@""];
            if (path) {
                viewController.renderURL = [NSURL fileURLWithPath:path];
            }
        }
    }
    
    XMWXViewController * __weak weakViewController = viewController;
    viewController.instance.onCreate = ^(UIView * view)
    {
        XMWXViewController * __strong vc = weakViewController;
        [vc.view addSubview:view];
    };
    viewController.instance.frame = viewController.view.bounds;
    viewController.instance.onLayoutChange = ^(UIView *view)
    {
        XMWXViewController * __strong vc = weakViewController;
        vc.instance.frame = vc.view.bounds;
    };
    
    UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:viewController];
    nav.tabBarItem = [tabbarItems objectAtIndex:idx];
    [viewControllers addObject:nav];
}];

[tabarController setViewControllers:viewControllers animated:YES];

}
/**
數(shù)據(jù)更改的時候調(diào)用
@param attributes component屬性數(shù)據(jù)
*/
-(void)updateAttributes:(NSDictionary *)attributes
{
if ([UIApplication sharedApplication].keyWindow.rootViewController) {
[self handleTabbarItems:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
[self handleTabbarViewControllers:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
}else
{
[self firstInitAPPFrameWithAttributes:attributes];
}

}

到此原生Component已經(jīng)完成。
那么我們需要回到我們初始化Weex環(huán)境的方法中加入```    //通過配置這個Component參數(shù)來配置程序框架HTML標(biāo)簽名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"xxxAPPFrameComponte")];```
######開始書寫[AppFrame.Vue](https://github.com/jiaowoxiaoming/app_weex/blob/master/app_weex/src/Components/Frame/AppFrame.vue)
回到WebStorm耸别,加入如下代碼

<template>
<AppFrame id='AppFrame' :tabarItems="tabbarItemsJsonString" :viewControllerItems="viewControllerItemsString"></AppFrame>
</template>這樣就可以使用我們擴展的AppFrameComponenttabbarItemsJsonStringviewControllerItemsString```需要添加下面代碼

<script>
    export default {
        data () {
            return {
                tabbarItemsJsonString:JSON.stringify(
                    [{上邊的TabbarItem數(shù)據(jù)}]),
                viewControllerItemsString:JSON.stringify(
                    [{上邊的navigationBarItem數(shù)據(jù)}]),
            };
        },
        methods: {}
    }
</script>

完整的AppFrame.Vue
到這里其實就可以編譯我們的Xcode工程了健芭。

開發(fā)中的聯(lián)動調(diào)試

那么如何看我們的效果呢?可能從一開始就應(yīng)該跟大家說如何聯(lián)動調(diào)試秀姐,但是我覺得讀到這里大家并沒有開始著手做慈迈,只是看,所以也就沒有中間如何聯(lián)動調(diào)試的步驟省有,大家真的上手做的時候痒留,中間是少不了的,到時候大家再看下面內(nèi)容蠢沿。
建議大家還是使用WebStorm狭瞎,本文就是基于此IDE開發(fā)的Weex Project。
還有這里涉及到單頁面的調(diào)試還是應(yīng)用的整體調(diào)試搏予。單頁面調(diào)試還是用Weex官方提供的Playground,如何進行單頁面的調(diào)試弧轧,Weex文檔說明了雪侥。那么如何進行整個應(yīng)用的聯(lián)動調(diào)試呢?
其實就是實時將Weex project 打包精绎,然后用部署到本機服務(wù)器速缨。通過掃碼或者本地寫死AppFrame的renderURL。
Weex沒有提及這樣的調(diào)試代乃,這里就詳細說明旬牲。
在Weex Project中找到package.json文件更改serve后的端口(或者直接使用8080,可以更改為8081什么的)。操作如圖

更改端口.png

然后在WebStorm中打開終端(使用終端App的話需要先cd到項目目錄下)執(zhí)行

npm install

npm run serve

成功如下圖


run serve.png

創(chuàng)建一個dist目錄
然后在終端輸入

weex compile src dist

上邊命令是將src目錄下全部生成JSBundle文件
下面命令是針對某一個Vue文件生成JSBundle

weex compile src/xxx.vue dist

Weex打包JSBundle.png

這個時候我們就可以將
http://本機IP:8083/dist/components/Frame/AppFrame.js
轉(zhuǎn)化成二維碼搁吓,用XMWeex編譯的App掃描生成的二維碼或者將你自己現(xiàn)在開發(fā)的加一個二維碼掃描原茅,甚至你也可以寫死,直接渲染上述地址堕仔。
如此方式實現(xiàn)AppDelegate
代碼摘要:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    UITabBarController * tabarViewController = [[UITabBarController alloc] init];
    
    UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window = window;
    window.rootViewController = tabarViewController;
    
    
    window.backgroundColor = [UIColor whiteColor];
    [window makeKeyAndVisible];
    [WXAppConfiguration setAppGroup:@"application"];
    [WXAppConfiguration setAppName:@"application"];
    [WXAppConfiguration setAppVersion:@"1.0"];
    
    //init sdk enviroment
    [WXSDKEngine initSDKEnvironment];
    [WXSDKEngine registerModule:@"XMWXModule" withClass:NSClassFromString(@"XMWXModule")];
    [WXSDKEngine registerHandler:[XMWXWebImage new] withProtocol:@protocol(WXImgLoaderProtocol)];
    //通過配置這個Component參數(shù)來配置程序框架HTML標(biāo)簽名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"XMWXAPPFrameComponte")];
    
#if TARGET_IPHONE_SIMULATOR//模擬器
    NSString * renderURL = @"http://192.167.0.3:8083/dist/components/Frame/AppFrame.js";
    //    NSString * renderURL = [NSString stringWithFormat:@"%@%@",host,@"AppFrame.weex.js"];
    [self instance:renderURL];
    
#elif TARGET_OS_IPHONE//真機
    XMWXScanViewController * scanVC = [[XMWXScanViewController alloc] init];
    tabarViewController.viewControllers = @[scanVC];
#endif
    
    [WXLog setLogLevel:WXLogLevelError];
    return YES;
}
-(WXSDKInstance *)instance:(NSString *)renderURLString
{
    if (!_instance) {
        _instance = [[WXSDKInstance alloc] init];
        [[NSURLCache sharedURLCache] removeAllCachedResponses];
//
        [_instance renderWithURL:[NSURL URLWithString:renderURLString]];
        
    }
    return _instance;
}

這個時候你已經(jīng)能得到類似如下的App框架擂橘。

AppFrame.jpg

如果讀到這,你會發(fā)現(xiàn)其實我們的這個AppFrame的頁面并沒有開發(fā)摩骨。其實渲染出的就是一個ViewController通贞。
那么下面我們要做的就是開發(fā)每一個模塊朗若。循序漸進,從
下一篇Weex 從無到有開發(fā)一款上線應(yīng)用 2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昌罩,一起剝皮案震驚了整個濱河市哭懈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茎用,老刑警劉巖遣总,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绘搞,居然都是意外死亡彤避,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門夯辖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琉预,“玉大人,你說我怎么就攤上這事蒿褂≡裁祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵啄栓,是天一觀的道長娄帖。 經(jīng)常有香客問我,道長昙楚,這世上最難降的妖魔是什么近速? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮堪旧,結(jié)果婚禮上削葱,老公的妹妹穿的比我還像新娘。我一直安慰自己淳梦,他們只是感情好析砸,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爆袍,像睡著了一般首繁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陨囊,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天客叉,我揣著相機與錄音惰蜜,去河邊找鬼洒忧。 笑死浙于,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闲先,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼状土,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伺糠?” 一聲冷哼從身側(cè)響起蒙谓,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎训桶,沒想到半個月后累驮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡舵揭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年谤专,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片午绳。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡置侍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拦焚,到底是詐尸還是另有隱情蜡坊,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布赎败,位于F島的核電站秕衙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏僵刮。R本人自食惡果不足惜据忘,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搞糕。 院中可真熱鬧勇吊,春花似錦、人聲如沸寞宫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辈赋。三九已至,卻和暖如春膏燕,著一層夾襖步出監(jiān)牢的瞬間钥屈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工坝辫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篷就,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓近忙,卻偏偏與公主長得像竭业,于是被迫代替她去往敵國和親智润。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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