初识weex
现在公司的项目是基于weex开发的,我负责的前端业务也是采用Vue,早先学了点Android的东西,前段时间一直在学习Vue的源码,于是现在打算来理一理weex相关的知识,后面也可以跟客户端的同学探讨一下。
参考:
weex官方文档,文档有点粗糙
可能是史上最全的weex踩坑攻略,这个可以避免很多坑,强烈推荐
环境
开发环境
踩坑记
npm大法好,全局安装weex-toolkit
即可。使用weex -v
查看版本:
v1.1.0-beta.7
- weexpack : v0.4.7-beta.26
- weex-builder : v0.2.13-beta.4
- weex-previewer : v1.3.13-beta.10
由于初学,也不需要使用weex init
创建项目,下面开箱即食
- 新建目录
- 新建
demo1.we
文件,打开内置组件文档,复制个官方demo进去 weex demo1.we
文件,然后就可以在浏览中预览文件了
会发现.we
文件和.vue
文件的格式貌似是一模一样的~打开万能的webstrom,会提示安装*.we
文件插件,找到weex
插件,然后安装,即可以在侧边栏直接预览页面(这让我想起了HBuilder,然而侧边栏调试并没有浏览器中好用~),总之有代码提示和语法高亮就够了。
上面的过程非常简单,然而我却跳进了一个坑:为了测试内置组件(比如text
),新建了一个text.we
,然后执行weex text.we
,在控制台会发现堆栈溢出了~,去搜issue才发现原来是we文件命名与web组件名相同导致的。
然后又测试了事件绑定,文档上面采用的是Vue的@click="handleClick"
这样的简写方法,但是我在web下测试会报错
@change' is not a valid attribute name
需要调整成。然后查了半天没找到如何查看weex的版本,貌似onclick="handleClick"
这样的形式,是不是我版本的问题weex-toolkit
和weex
版本是独立的。后来发现在控制台输出weex.config
才发现,weexVersion
才v0.5.0
,所以说,这个内置的预览不靠谱,上面这趟的这些坑都是脑子进的水,老老实实建个新项目吧。
开发环境
weex内置了一套开发环境
npm run dev & npm run serve
查看webpack.config.js
可以发现,实际存在webConfig
和weexConfig
两套配置,这是因为在打包的时候,客户端使用weex-loader
进行处理,而web端使用vue-loader
进行处理。
在webpack.dev.js
开发模式下,使用的是webConfig
。这里还有不少坑(比如自动生成的入口文件),后面会提到
Android环境
由于目前iOS方面我只是了解一点点基本语法,因此先从Android开始学习,后面再进行iOS相关的处理。
根据官方文档的要求,搭建对应版本的android环境,添加相关依赖
compile 'com.android.support:recyclerview-v7:25.3.0'
compile 'com.android.support:appcompat-v7:25.1.1'
compile 'com.alibaba:fastjson:1.1.45'
compile 'com.taobao.android:weex_sdk:0.9.5@aar'
compile 'com.github.bumptech.glide:glide:3.7.0'
ImageAdapter
这里我选择使用了Glide
实现图片的加载
public class ImageAdapter implements IWXImgLoaderAdapter {
@Override
public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) {
Glide.with(view.getContext())
.load(url)
.into(view);
}
}
实现自定义Application
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
InitConfig config=new InitConfig.Builder().setImgAdapter(new ImageAdapter()).build();
WXSDKEngine.initialize(this,config);
}
}
记得一定要在manifests
中设置自定义的application
<application
android:name=".App" // 这个一定要加上
>
</application>
加载对应的js文件
执行npm run build
之后打包获得的JS文件位于dist/index.js
,将其拷贝到Android项目的assets
目录下。由于操作比较频繁可以定义一个脚本,直接加载服务器资源即可
"scripts": {
"copy:android": "cp dist/index.js ../android/app/src/main/assets/index.js"
}
然后在对应活动中通过实例化WXSDKInstance
对象
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
mWXSDKInstance = new WXSDKInstance(this);
mWXSDKInstance.registerRenderListener(this);
// mWXSDKInstance.render("WXSample", WXFileUtils.loadAsset("index.js",this), null, null, -1, -1, WXRenderStrategy.APPEND_ASYNC);
mWXSDKInstance.renderByUrl("WXSample", "http://10.0.2.2:8081/dist/index.js", null, null, -1, -1, WXRenderStrategy
.APPEND_ASYNC);
}
其中8081
是weex脚手架开启的服务器,记得在manifests
中配置网络权限。在项目中更常用的做法是加载服务器上的JS文件,实现客户端的热更新,这里涉及到一系列的缓存问题,我们先把demo跑起来
语法
样式
由于在打包时会将css转换成对应的json语法,加之官方文档解释为了提高解析性能,因此与传统的CSS还是有一些区别的。下面是几个需要注意的地方
- 部分样式都不支持简写,如
border
,background
position
不支持z-index
,默认靠后的元素层级较高- 文本样式不支持继承,后代选择器无效
- 默认以
750 * 1334
尺寸,跟小程序的rpx单位比较相似。这导致在浏览器已像素预览正常,而在客户端发现所有尺寸都小了一倍 transform
和transiton
的支持有限
组件
weex内置了一些组件
div
中不能直接添加文本,文本只能在text
中添加。官方建议div嵌套不宜过深- a链接只是默认调用了event模块的
openURL
,却没有实现相关的方法,所以需要自己实现WXEventModule
并注册 - Android下默认
overflow:hidden
,为了实现内容滚动只能使用scroller
组件 - image用来加载图片,需要原生实现图片加载器,且需要在样式中手动设置宽高,否则不会显示图片。image组件的功能比较强大,包括占位图和下载图片等
- web组件用来加载网页,可以使用
webview
内置模块来控制webview的行为,不过功能比较弱,暂时没有找到对webview组件相关设置(如允许弹窗等)
模块
weex内置了一些模块,用于封装底层的api,下面先介绍了几个比较常用的模块
model
模块用于提供与原生类似的模态框navigator
模块用于控制weex页面的跳转,在下面的路由章节中有对该模块的使用stream
模块提供了网络请求相关接口
大部分模块使用的是回调函数形式,我们可以对其封装,使其Promise化。比如下面对于stream模块的封装
let stream = weex.requireModule('stream')
let baseUrl = 'http://10.0.2.2:9999/'
let get = function(url){
return new Promise((resolve, reject)=>{
stream.fetch({
method: 'GET',
url: baseUrl+url,
type:'json'
}, function(ret) {
if(!ret.ok){
reject(ret)
}else{
let data = ret.data;
resolve(data)
}
});
})
}
export default {
get
}
在下面的原生交互章节再去了解实现自定义模块和组件。
开发环境
样式表
预编译器 在style
上添加lang="scss"
指定css语法,但是在build会报错
Module not found: Error: Can't resolve 'scss-loader'
解决办法是将lang="sass"
,然后@import './index.scss'
字体图标
在之前的微信小程序项目中,通过将ttf字体转换成base64文件,然后引入样式表中就可以愉快地使用字体图标了。
但是在weex中不支持这种形式,其原因是不支持after
伪元素,直接引入字体然后在text
组件中使用对应的字体编码是可以渲染的~这个就很蛋疼了
关于字体图标的使用,这里有一个讨论
服务器
为了方便测试,我使用的是Koa搭建静态资源和接口服务器,位于/server
下,服务器主要用于提供静态资源(如图标)和数据接口。
在开发的时候,使用的是npm run server
开启的express服务器,这样可以方便同步查看效果。在项目上线的时候,也需要将JSBundle
部署到静态资源服务器上
模块化
由于使用webpack进行打包,因此我们可以很方便地使用CommonJS或ES6模块来组织代码,常见的比如网络请求、路径配置等功能,都可以按照具体业务需求进行模块化处理
路由
Weex推荐使用单页独立开发的,即每个vue文件对应一个页面,在build
的时候会将每个vue文件都打包成对应的JSBundle
文件(在webpack.config.js
中可以看见其原理是遍历/src
文件夹)。这样可以使页面更加精简,方便维护和增量更新。
这是否意味着官方不建议使用vue-router
来实现页面跳转呢~
native->weex
由于weex页面可以作为单个组件嵌入应用,因此原生跳转到weex页面,实际上就是原生之之间的跳转,在Andorid中,通过startActivity
来实现页面跳转。
weex->native
// todo
weex->weex
weex内置了navigator
模块,用来实现weex
页面与weex
页面之间的跳转。
其原理是通过拦截器,来确定需要显示的Activity
,然后加载对应的JSBundle
资源,渲染weex页面。具体的使用可以参考这里
声明拦截器
声明一个用于处理weex跳转的拦截器
<activity
android:name=".WeexActivity">
<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>
声明活动
这里专门新建了一个Activity处理weex页面的跳转,实际上也可以在同一个Activity内处理。
public class WeexActivity extends AppCompatActivity implements IWXRenderListener {
private static String TEST_URL = "http://10.0.2.2:8081/dist/index.js";
private WXSDKInstance mWXSDKInstance;
private FrameLayout mContainer;
private Uri mUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weex);
mContainer = (FrameLayout) findViewById(R.id.container);
mWXSDKInstance = new WXSDKInstance(this);
mWXSDKInstance.registerRenderListener(this);
String url = this.getJSPath();
if (url.isEmpty()){
url = TEST_URL;
}
HashMap<String, Object> options = this.getWeexOpts(url);
mWXSDKInstance.renderByUrl("WXSample", url,options,null, WXRenderStrategy.APPEND_ONCE);
}
private HashMap<String, Object> getWeexOpts(String jsPath){
HashMap<String, Object> options = new HashMap<String, Object>();
options.put(WXSDKInstance.BUNDLE_URL, jsPath);
return options;
}
// 从intent中获取js地址
private String getJSPath(){
Uri uri = getIntent().getData();
Bundle bundle = getIntent().getExtras();
if (uri != null) {
mUri = uri;
}
if (bundle != null) {
String bundleUrl = bundle.getString("bundleUrl");
if (!TextUtils.isEmpty(bundleUrl)) {
mUri = Uri.parse(bundleUrl);
}
}
return mUri.toString();
}
// ...
}
原生交互
weex提供了实现原生扩展的接口,包括模块扩展和组件扩展,参考文档
模块扩展
实现在原生实现相关的模块
public class MyModule extends WXModule {
@JSMethod(uiThread = true)
public void showToast(String msg){
Toast.makeText(mWXSDKInstance.getContext(),msg,Toast.LENGTH_LONG).show();
}
}
然后在自定义application中注册模块
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
InitConfig config=new InitConfig.Builder().setImgAdapter(new ImageAdapter()).build();
WXSDKEngine.initialize(this,config);
// 注册相关模块
try {
WXSDKEngine.registerModule("myModule", MyModule.class);
}catch (WXException e){
e.printStackTrace();
}
}
}
然后在weex中引用相关模块,其中通过模块名进行关联
weex.requireModule('@weex-module/myModule').showToast('Hello Native')
这里遇见的一个问题是模块注册失败,一查发现有不少类似的问题。
自定义组件
weex支持Vue的组件系统,此外,还支持通过原生扩展的组件系统,这里暂时没有进一步了解
小结
经过一段时间的折腾,自己体会到了weex的一些优点和劣势:
- 优点自然是跨平台,极大程度上降低开发成本
- 劣势是在三端的表现都如原生,且内置的功能有限;深入开发的成本也不低,还需要处理不同平台的差异,可能会提高后续维护成本
实际上我是在之前阅读Vue源码的时候,看见在渲染函数中生成的AST,在不同的运行平台下有不一样的处理方式,于是来了解下weex。后面有时间应该会去尝试下React Native
。
感觉这种跨平台构建前端应用是未来前端的发展趋势(包括微信小程序这种),因此还是应该围观一下。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。