Weex原理之带你去蹲坑

 本篇将节操满满的安利Weex(˶‾᷄ ⁻̫ ‾᷅˵),不一样的角度推荐你入坑,官网有的我们不拖泥,这里将给你补充官方没有的,深入到蹲坑给你排忧解难,总会给你点惊喜,内容越后越干,请紧张的往下看。

两个字,讲究

***** 本文配套,超完整 Weex 项目推荐 : GSYGithubAppWeex *****

一、简介

 有对比才有伤害,说到Weex,难免让人联系React Native。虽同为跨平台移动端解决方案,拥JavaScript妄一统天下,单两者的设计理念其实截然不同。

 这里先介绍下两者的差异,给徘徊在 React Native 和 Weex 之间的人,理解更适合哪些场景。

类型 React Native Weex
性能 较好 较弱
上手难度 稍高 容易
核心理念 React Vue
框架程度 较重 较轻
特点 适合开发整体App 适合单页面
社区 丰富,Facebook维护 略残念,目前托管apache
支持 Android、IOS Android、IOS、Web
适应性 原生开学习成本低 Web开发学习成本低
JS引擎 JSCore V8

 作为两个框架的深度体验者,个人总结出上面的对比,其中可以看出:

  • React Native更适合开发完整的App,因为它的性能较好,第三方插件丰富,社群活跃并且维护较好,文档完整等(本篇主角是Weex好吧魂淡(#゚Д゚))。

  • Weex更适合开发单页面集成,这也是阿里的业务特性。
    当然Weex也可以开发完整的多页面App,同时我也是这么用过,不过效果对比React Native,显然强差人意。

  • Weex胜在容易上手,基于Vue的设计模式,类MVVM的实现,也让前端能更无缝的实现一些高性能的App业务。

  • Weex兼容Android、IOS、Web三端,在单页面的实现上,它有着React Native无法睥睨的先天优势。

  • Weex的社群,个人觉得还是弱,资料不足,文档简单,第三方支持太弱。和React Native一样支持带原生功能的插件开发,但是,支持太少了,这也提高了后期的开发门槛。同时,一个小问题很容易让入初学者,三过门而不入,作为一个发布了两年的框架,还是比较让人吐槽的。

这就是,所谓的前言吧

二、原理

 这里简要说明下Weex在android下的分层以及原理。

 Weex主要包括三大部分:JS BridgeRenderDom,分别对应WXBridgeManagerWXRenderManagerWXDomManager 。通过WXSDKManager统一管理。其中JS BridgeDom运行在独立的HandlerThread中,而Render运行在UI线程。JS Bridge主要用来和 JS 端实现进行双向通信,比如把js端的dom结构传递给Dom线程。Dom主要是用于负责dom的解析、映射、添加等等的操作,最后通知UI线程更新。而Render负责在UI线程中对dom实现渲染。

 如下图,是生成dom,dom的解析,映射,添加,渲染的流程。

图片来自网络

 如上可知,因为JS端运行于独立的单线程中,所以为了保证运行的流畅性,一般需要避免在JS端执行耗时操作,比如:网络请求,图片加载等,其实都是在原生端完成,js端执行的是发起一个请求和响应一个结果。同时因为原生端与JS端是通过JS Bridge通讯,所以也需要尽量避免大数据和频繁的通讯,导致响应的延迟。

 原生端的dom的加载解析映射,也是性能的一大瓶颈。一般而言,Weex在Web端生成的,是通过webpack的webConfig打包成单页面的index.web.js文件;而在原生端,一般会通过webpack的weexEntry配置成多页面形式:即每一个需要独立的.vue的页面,最终会被打包成一个.js文件。所以打开每个页面时加载对应的js文件,这很好的减小了需要加载的文件大小,提高了dom的解析效率。最后,Weex默认打的js只包含业务js代码,基础js库已经被包含在weex sdk中,也使得体积会小很多。

三、入门

1、配置环境

 程序员就要从配置环开始,Weex 环境搭建 ,点击链接,只要你要一个稳定的网络,参考官网搭建环境,也就一杯茶的功夫,take it easy。配置好之后,weex create testProject创建一个项目High起来吧。

2、快速入门

 weex的入门还是比较简单的,JavaScript、Vue了解下,即可预约的hello world。

 原生开发也许对vue接触不多,跨界有时候很容易望而却步,其实Vue本身,就是容易上手的框架,类似MVVM的模式(类似Android的DataBinding),很容让人理解上手,简单的说,你只关心数据,然后绑定到显示的控件,就是这么简单。

 一般通过 Vue官网 教程,30分快速撸一发,之后你就直接入门Weex了,对,Weex做的最彻底的就是,你直接使用 vue 写一个Web页面,之后再顺手编译成了 ios 和 android 的原生页面(尽管有些时候你需要在平台适配上花费心思)。

请忽略那个this,真的( ̄. ̄)

 如上图(请忽略那个this( ̄. ̄)),这就是一个极度简化的,用Vue写的Weex页面。效果是从显示Hello World ,一秒变I’m CarGuo,就是这么自信。

 在<template>中排布需要渲染的控件,在<style>中指定控件的样式(当然你也可以直接在<template>中),在<script>中写数据获取和处理逻辑等,是不是很简单, Don’t be shy,Let's fuck it !

 因为需要支持三端,Weex在Vue的基础上阉割了一些标签、css样式和事件,具体可见与 Web 平台的差异

 其中,在Android和IOS上,<text> <image>等标签,其实是被编译为原生控件,这就是上面所说的dom解析,同时你也可以在原生端,自定义控件或者功能模块,然后注册到weex中使用,实际上weex提供的基础控件和功能模块并不多,但却很容易拓展,具体可见 拓展原生端功能

(ps 也不知道阿里是怕做多错多,还是懒)

 说到这里,就需要说一说Weex的原生插件开发支持,这也是官方文档比较没整理好的原因,其实文档是有的:Weex插件开发文档,如Android插件大致流程就是:

  • weex plugin create命令创建插件。
  • 在android目录的library下通过@WeexComponent(控件)、@WeexModule(非UI功能)、@WeexAdapter(weex继承功能拓展)实现第三方支持。
  • 将library发布到maven (当然你也可以直接源码发布到npm)
  • 配置根目录的package.json然后发布到npm

 由此可见,weex可以很方便的提供原生功能的拓展支持,但是由于社群较为薄弱,导致第三方插件缺失,有(hen)些(duo)时候你可能不得不自己着手,开发原生端的功能支持,这就对于跨平台开发而言,特别前端开发而言,就稍(te)显(bie)不友好了。

目瞪狗带

题外话 :说到跨平台开发,也许你听说过cordova这位老大哥,它曾是早期的跨平台开发潮流,cordova提供丰富的原生插件和打包功能:通过webview把前端页面打包成一个App,通过插件提供前端需要的原生接口,交互通过webview的js接口支持。为什么说起它呢,是因为Weex中,你可以看到很多cordova的影子,类似weex platform add androidweex plugin add xxx都有些cordova的味道。如下图,你如今依旧可以在Weex中找,寻找到cordova的存在感。

cordova残留
3、其他推荐
Vuex 和 Vue-Router ,居家旅行必不可少。
  • Vuex类似Redux,如果你没听说Redux不要紧,也不要怂,简单了说,Vuex就是单页面下,帮你管理数据的框架。数据都存在Vuex的store中,你操作store更新数据,然后将store绑定到界面。它的用处在于可以在多个vue组件间,方便的同步数据,更新界面。

  • Vue-Router也是用于单页面下,指定跳到某个页面的管理工具,路由嘛,浅显易懂。

iconfont :矢量图标,少不了iconfont,通过字库生成图标,资源丰富,绝对值得推荐。
weex-ui: weex中难得的良心官方封装库。
eros :eros 不是框架,是基于 weex 封装、面向前端的 vue 写法的一整套 APP 开源解决方案,是由本木医疗大前端团队经过大量实践沉淀而出。。

四、深入填坑

1、ES6、ES7

  说到 Javascript ,ES6、ES7必须了解下。Weex中默认就有对其支持,但是对于async、await等,还需要如下一些简单配置,然后 have fun 。

//命令行安装
npm install --save-dev babel-plugin-transform-runtime

//然后在.babelrc文件中加入
{
"presets": [
"es2015",
"stage-0"
],
"plugins": [ [
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
]]
}
2、多页面

 Weex默认是单页面效果,也就是Android中一个Activity的概念,而单页面效果在原生上,跳转一多效果就会web了。既然叫native,怎么可能如此too young,所以这个时候,就需要修改默认的webpack,让其支持naive多页面了ı╮(╯▽╰)╭。

 首先,要知道Weex真正运行的是,通过entry.js作为入口文件文件,通过webpack,将.vue文件打包成index.js进行使用。Look,多页面的重点,就是将独立页面的.vue文件,生成多个js文件。

入口js

 如上图,参考entry.js文件,创建一个SecondPageEntry.js,作为SecondPage.vue的入口,用于webpack生成SecondPage.js页面。

 什么?webpack没听说过怎么办,No problem,你只需简单的修改,一知半解完全可以胜任。如下图,我们主要需要修改webpack.common.conf.js文件,

webpack.common.conf.js

 可以看出,webpack.common.conf.js中,其实是区分了webConfig和weexConfig的不同打包方式。如下图,其中weexEntry就是我们需要修改的地方,可以看到本来已经有index和entry.js存在了。

 最后我们需要通过navigator来实现跳转,我们需要知道,要跳转的js文件在哪里,如下代码演示,如何实现navigotor的native跳转,完整兼容三端跳转请移步demo项目。

 //获取当前js文件所在完整路径
 let bundleUrl = this.$getConfig().bundleUrl;
 bundleUrl = String(bundleUrl);
 let nativeBase;
 //android一般位于file://assets目录下
 let isAndroidAssets = bundleUrl.indexOf('file://assets/') >= 0;
 //ios一般位于一般带有file开头,带有WeexDemo.app
 let isiOSAssets = bundleUrl.indexOf('file:///') >= 0 && bundleUrl.indexOf('WeexDemo.app') > 0;
 if (isAndroidAssets) {
     nativeBase = 'file://assets/dist/';
 } else if (isiOSAssets) {
     nativeBase = bundleUrl.substring(0, bundleUrl.lastIndexOf('/') + 1);
 } else {
    let host = 'localhost:8080';
    let matches = /\/\/([^\/]+?)\//.exec(bundleUrl);
    if (matches && matches.length >= 2) {
        host = matches[1];
    }
    nativeBase = 'http://' + host + '/index.html?page=./dist/';
 }
 return nativeBase;

3、样式sass\scss

sass,后期必不可少的利器。

 当你的weex项目不断变大,一些样式共享,公共颜色,大小尺寸等的管理,就是你需要面对的问题。

 这时候sass和scss就可以起到很大的作用。最大优点是,它可编程,支持定义变量,而且不像阉割后的css一样,var()这种写法无法在native下得到支持,这时候sass的效果绝对让你回味无穷。

 使用sass也十分简单,简单配置下webpack,sass的语法也十分容易上手,只需十分钟了解下就可以愉快的享用这块糖了。

  • 先安装sass依赖:
npm install node-sass;
npm install sass-loader; //依赖node-sass
  • 之后webpack.common.conf.js中配置loader,如下图,在两个module处,增加红框配置。
  • 最后用 import 引入的sass文件进行加载,详细可查看demo工程。
//也可以 lang="scss"
<style lang="sass">
//导入写好的文件
@import "./style.scss";
</style>

五、蹲坑

 其实就是问题集锦,记录一些开发过程中遇到的问题,相信你会喜欢:

  • 1、关于vue的<scrpit>标签内,weex.requireModule(包括插件weex-ui)中,在全局获取返回null的原因,是因为entry.js中的router对象,不能用import 和 export default,只能用require 和 module.exports 配合。

  • 2、es6一些语法问题,如async和await,可以用"babel-plugin-transform-runtime",在.babelrc中设置。

{
"presets": [
"es2015",
"stage-0"
],
"plugins": [ [
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
]]
}
  • 3、export default 和require混合使用的时候,会多一个default对象,比如this.$store.default.state这样才对的问题。

  • 4、自定义的js文件类中,不能使用全局的weex.requireModule

  • 5、使用weex-ui的tabbar结合是,<list>必须有高度,或者overflow属性为scroll才能滑动,而且overflow的位置必须是不会影响其他页面位置。

  • 6、全屏默认height 1334 和 width 750,但是记得减去32大概高度的statusbar。

  • 7、list的loadmore,必须给list设置高度样式,才能在web中正常触发。

  • 8、text的</text>结束标签换行,在debug下可能会出现样式问题。

  • 9、生命周期在web中与android等不同,比如activated等。

  • 10、()=> {}对于this可能获取存在的不同,尽量用function(){}。

  • 11、多页即创建多个类似entry.js的入口文件,在webpack下配置weex的打开生成的js文件,用于navigator跳转,通过url传值。

  • 12、android多页面打开失败

android.os.FileUriExposedException问题:

在你的Application中添加:
if (Build.VERSION.SDK_INT>=18) {
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
}

ActivityNotFoundException问题:

 <activity
         android:name=".xxxxxx"
         android:label="@string/app_name"
         android:screenOrientation="portrait"
         android:theme="@style/AppTheme.NoActionBar">
     <intent-filter>
         <action android:name="com.taobao.android.intent.action.WEEX"/>

         <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="com.taobao.android.intent.category.WEEX"/>
         <action android:name="android.intent.action.VIEW"/>

         <data android:scheme="http"/>
         <data android:scheme="https"/>
         <data android:scheme="file"/>
         <data android:scheme="wxpage" />
     </intent-filter>
 </activity>
  • 13、多页面生成js时,import的时候,需要指定.vue后缀的。

  • 14、如果是webstorm,记得对.temp dist node_modules platforms几个文件夹,右键设置excluded,避免一直indexing和硬盘资源消耗。

  • 15、@viewappear="onappear" @viewdisappear ="ondisappear" 类似onPause和onResume

  • 16、ios实时看log,可以先增加

-(void)redirectConsoleLog{
#ifdef DEBUG
    NSString *documentDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSLog(@"documentPath : %@",documentDir);

    //重定向NSLog
    NSString* logPath = [documentDir stringByAppendingPathComponent:@"console.log"];
    freopen([logPath fileSystemRepresentation], "a+", stderr);
#endif
}
//调用
[self redirectConsoleLog];

然后在Devices下,找到对应的模拟器号码,在再Application下,搜索console.log,跟踪执行

tail -f
/Users/your name/Library/Developer/CoreSimulator/Devices/FDEACA11-D84E-4E8F-A6B8-26239559A928/data/Containers/Data/Application/9394D6CC-6B4A-4200-A13D-0CBE6F2BB67A/Documents/console.log

最后

1、文章配套,超完整 Weex 项目了解下:https://github.com/CarGuo/GSYGithubAppWeex

2、react native相关文章

从Android到React Native开发(一、入门)

从Android到React Native开发(二、通信与模块实现)

从Android到React Native开发(三、自定义原生控件支持)

从Android到React Native开发(四、打包流程和发布为Maven库)

还记得我吗

推荐阅读更多精彩内容