突然断电后,在hbuilderx编辑器当前打开的文件会变成空白或乱码问题
问题如下,最近几天比较忙一些,笔记本经常忘记插上电源,以前还有提示,现在电池也不给力了,还没到低电量预警状态就自动断电了,问题就来了,在重启电脑后,断电前编辑器打开的文件变成了空白,内容都没有了
,使用sublime text打开此文件显示全是0000
,不过在编辑器中打开的其他处于非编辑状态的文件正常没有问题,出现此情况的hx版本为最新的20190522版本
,以前版本是否有问题不太清楚因为以前没有遇到,首次出现此问题时正在编辑一个重要文件,因为时间比较紧一直没有提交git保存,不能恢复的话多半天工作就白干了,还好在使用了diskgenius扫描了磁盘,发现了以前的数据,恢复到了断电前最后保存的内容,总算有惊无险,不知道其他同学有没有遇到过类似问题,以及官方对此问题有没有解决方案,仅在此记录一下,方便后面同样有问题的同学参考
问题如下,最近几天比较忙一些,笔记本经常忘记插上电源,以前还有提示,现在电池也不给力了,还没到低电量预警状态就自动断电了,问题就来了,在重启电脑后,断电前编辑器打开的文件变成了空白,内容都没有了
,使用sublime text打开此文件显示全是0000
,不过在编辑器中打开的其他处于非编辑状态的文件正常没有问题,出现此情况的hx版本为最新的20190522版本
,以前版本是否有问题不太清楚因为以前没有遇到,首次出现此问题时正在编辑一个重要文件,因为时间比较紧一直没有提交git保存,不能恢复的话多半天工作就白干了,还好在使用了diskgenius扫描了磁盘,发现了以前的数据,恢复到了断电前最后保存的内容,总算有惊无险,不知道其他同学有没有遇到过类似问题,以及官方对此问题有没有解决方案,仅在此记录一下,方便后面同样有问题的同学参考 收起阅读 »
uni-app全局水印
文章废话比较多,主要是想讲一下做这个功能所经历的一些事情,先上代码吧:
由于需要借助canvas生成图片,这里的代码是在应用的首页index.vue执行的,在App.vue无法实现,需要在index.vue中定义
代码写的比较乱,先将就着看,之后再整理
<!-- #ifdef APP-PLUS -->
<block v-if="showWatermark">
<canvas class="watermarkCans" canvas-id="watermarkCanvas"></canvas>
</block>
<!-- #endif -->
下面是js
initWatermark(msg) {
// #ifdef APP-PLUS
let _self = this;
let id = '1.23452384164.123412415';
if (plus.nativeObj.View.getViewById(id) !== null) {
plus.nativeObj.View.getViewById(id).close();
}
let canvasInAppPlusContext = uni.createCanvasContext('watermarkCanvas');
canvasInAppPlusContext.rotate(-30 * Math.PI / 180);
canvasInAppPlusContext.setFontSize(uni.upx2px(28));
canvasInAppPlusContext.setFillStyle('rgba(200, 200, 200, 0.50)');
canvasInAppPlusContext.setTextAlign('left');
canvasInAppPlusContext.setTextBaseline('middle');
canvasInAppPlusContext.fillText(msg, -25, uni.upx2px(170));
canvasInAppPlusContext.draw(false, function() {
uni.canvasToTempFilePath({
canvasId: "watermarkCanvas",
success: function(res) {
_self.showWatermark = false;
let path = res.tempFilePath;
uni.getSystemInfo({
success: function (res) {
//水印排列行数
let row = Math.floor(res.windowHeight / uni.upx2px(250));
let tarArr = [];
for(let i = 0; i < row; i++) {
for(let j = 0; j < 3; j++){
tarArr.push({
tag: 'img',
src: path,
position: {
top: (uni.upx2px(255) * i) + 'px',
left: (uni.upx2px(255) * j) + 'px',
width: uni.upx2px(255) + 'px',
height: uni.upx2px(255) + 'px'
}
});
}
}
var watermarkView = new plus.nativeObj.View(id, {
top:'70px',
left:'0px',
right: '0px',
bottom: '50px'
}, tarArr);
//拦截View控件的触屏事件,将事件穿透给下一层view
watermarkView.interceptTouchEvent(false);
watermarkView.show();
}
});
}
});
});
// #endif
// #ifdef H5
let id = '1.23452384164.123412415';
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
let can = document.createElement('canvas');
can.width = uni.upx2px(250);
can.height = uni.upx2px(250);
let cans = can.getContext('2d');
cans.rotate(-30 * Math.PI / 180);
cans.font = uni.upx2px(28) + 'px';
cans.fillStyle = 'rgba(200, 200, 200, 0.50)';
cans.textAlign = 'left';
cans.textBaseline = 'Middle';
cans.fillText(msg, -25, uni.upx2px(170));
let div = document.createElement('div');
div.id = id;
div.style.pointerEvents = 'none';
div.style.top = '44px';
div.style.left = '-40px';
div.style.bottom = '50px';
div.style.right = '0px';
div.style.position = 'fixed';
div.style.zIndex = '100000';
// div.style.width = document.documentElement.clientWidth + 'px'
// div.style.height = document.documentElement.clientHeight + 'px'
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
document.body.appendChild(div);
return id;
// #endif
}
前两天在研究一个全局水印的功能,需要达到以下的效果:
1、根据给出的文字生成水印
2、水印需要在全局生效,不能跳转页面后消失
3、H5端和APP端都要兼容
根据以上的需求,我研究了以下几种方案:
1、封装水印组件。
做一个漂浮的div,定义z-index层级和pointer-event属性和背景平铺重复,在需要的页面中使用组件。这个实现起来很简单,兼容H5和APP,但是有一个问题需要在每一个页面中去单独引入,不符合全局水印的定义,而且项目要提供给项目组使用,很明显不是一个完美的方案。
2、往页面中插入一个div
实现方式和第一点类似,优点是只能在H5端生效,APP端没有body,这个方案直接被pass了
3、用native.js分别实现安卓和IOS端的效果
但是由于对原生开发不了解,这个方案直接就实现不了了
4、直接丢给原生开发做,再去引入插件
这明显不是个好点子,而且研究了这么久不能半半途而废
尝试了很多方法之后我就思考:有没有这样一个顶层的视图容器的东西,可以让我去插入内容,还不会影响事件的穿透,基于这个想法,我又做了一下尝试
1、应用生命周期
由于对移动开发和vue都不熟,尝试了很多vue的方法,生命周期钩子,createElement,render等等,都失败了。
2、webview
我了解到uni-app在APP端实质上也是一个webview,每一个页面其实是这个webview的子webview,我想在应用的onLaunch里边去获取到顶层的webview,往webview中添加子窗口的方式实现,因为各种原因,实现了一半,但是感觉好像有了苗头
3、plus.nativeObj.View
也就是我最终的实现方式,研究了好久,发现了这个宝藏对象。
plus.nativeObj:可以管理系统原生对象。
plus.nativeObj.View:原生控件对象可用于在屏幕上绘制图片或文本内容,当控件不再使用时需要调用close方法销毁控件。
链接:http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View
尝试了之后发现可以在屏幕上绘制内容,并且在整个应用生效,开心到爆炸,但是!!又遇到了一个问题,这些内容会形成类似遮罩层的东西,导致事件无法穿透。研究了一番,找到了这样一个方法:view.interceptTouchEvent(false),它可以拦截View控件的触屏事件。
接下来,就是文字生成水印图片的问题了,我用的canvas绘制了一个水印,转换成图片实现。期间遇到了一个问题,APP端的uni.canvasToTempFilePath事件需要放在cans.draw()的回调里去实现。
文章废话比较多,主要是想讲一下做这个功能所经历的一些事情,先上代码吧:
由于需要借助canvas生成图片,这里的代码是在应用的首页index.vue执行的,在App.vue无法实现,需要在index.vue中定义
代码写的比较乱,先将就着看,之后再整理
<!-- #ifdef APP-PLUS -->
<block v-if="showWatermark">
<canvas class="watermarkCans" canvas-id="watermarkCanvas"></canvas>
</block>
<!-- #endif -->
下面是js
initWatermark(msg) {
// #ifdef APP-PLUS
let _self = this;
let id = '1.23452384164.123412415';
if (plus.nativeObj.View.getViewById(id) !== null) {
plus.nativeObj.View.getViewById(id).close();
}
let canvasInAppPlusContext = uni.createCanvasContext('watermarkCanvas');
canvasInAppPlusContext.rotate(-30 * Math.PI / 180);
canvasInAppPlusContext.setFontSize(uni.upx2px(28));
canvasInAppPlusContext.setFillStyle('rgba(200, 200, 200, 0.50)');
canvasInAppPlusContext.setTextAlign('left');
canvasInAppPlusContext.setTextBaseline('middle');
canvasInAppPlusContext.fillText(msg, -25, uni.upx2px(170));
canvasInAppPlusContext.draw(false, function() {
uni.canvasToTempFilePath({
canvasId: "watermarkCanvas",
success: function(res) {
_self.showWatermark = false;
let path = res.tempFilePath;
uni.getSystemInfo({
success: function (res) {
//水印排列行数
let row = Math.floor(res.windowHeight / uni.upx2px(250));
let tarArr = [];
for(let i = 0; i < row; i++) {
for(let j = 0; j < 3; j++){
tarArr.push({
tag: 'img',
src: path,
position: {
top: (uni.upx2px(255) * i) + 'px',
left: (uni.upx2px(255) * j) + 'px',
width: uni.upx2px(255) + 'px',
height: uni.upx2px(255) + 'px'
}
});
}
}
var watermarkView = new plus.nativeObj.View(id, {
top:'70px',
left:'0px',
right: '0px',
bottom: '50px'
}, tarArr);
//拦截View控件的触屏事件,将事件穿透给下一层view
watermarkView.interceptTouchEvent(false);
watermarkView.show();
}
});
}
});
});
// #endif
// #ifdef H5
let id = '1.23452384164.123412415';
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
let can = document.createElement('canvas');
can.width = uni.upx2px(250);
can.height = uni.upx2px(250);
let cans = can.getContext('2d');
cans.rotate(-30 * Math.PI / 180);
cans.font = uni.upx2px(28) + 'px';
cans.fillStyle = 'rgba(200, 200, 200, 0.50)';
cans.textAlign = 'left';
cans.textBaseline = 'Middle';
cans.fillText(msg, -25, uni.upx2px(170));
let div = document.createElement('div');
div.id = id;
div.style.pointerEvents = 'none';
div.style.top = '44px';
div.style.left = '-40px';
div.style.bottom = '50px';
div.style.right = '0px';
div.style.position = 'fixed';
div.style.zIndex = '100000';
// div.style.width = document.documentElement.clientWidth + 'px'
// div.style.height = document.documentElement.clientHeight + 'px'
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
document.body.appendChild(div);
return id;
// #endif
}
前两天在研究一个全局水印的功能,需要达到以下的效果:
1、根据给出的文字生成水印
2、水印需要在全局生效,不能跳转页面后消失
3、H5端和APP端都要兼容
根据以上的需求,我研究了以下几种方案:
1、封装水印组件。
做一个漂浮的div,定义z-index层级和pointer-event属性和背景平铺重复,在需要的页面中使用组件。这个实现起来很简单,兼容H5和APP,但是有一个问题需要在每一个页面中去单独引入,不符合全局水印的定义,而且项目要提供给项目组使用,很明显不是一个完美的方案。
2、往页面中插入一个div
实现方式和第一点类似,优点是只能在H5端生效,APP端没有body,这个方案直接被pass了
3、用native.js分别实现安卓和IOS端的效果
但是由于对原生开发不了解,这个方案直接就实现不了了
4、直接丢给原生开发做,再去引入插件
这明显不是个好点子,而且研究了这么久不能半半途而废
尝试了很多方法之后我就思考:有没有这样一个顶层的视图容器的东西,可以让我去插入内容,还不会影响事件的穿透,基于这个想法,我又做了一下尝试
1、应用生命周期
由于对移动开发和vue都不熟,尝试了很多vue的方法,生命周期钩子,createElement,render等等,都失败了。
2、webview
我了解到uni-app在APP端实质上也是一个webview,每一个页面其实是这个webview的子webview,我想在应用的onLaunch里边去获取到顶层的webview,往webview中添加子窗口的方式实现,因为各种原因,实现了一半,但是感觉好像有了苗头
3、plus.nativeObj.View
也就是我最终的实现方式,研究了好久,发现了这个宝藏对象。
plus.nativeObj:可以管理系统原生对象。
plus.nativeObj.View:原生控件对象可用于在屏幕上绘制图片或文本内容,当控件不再使用时需要调用close方法销毁控件。
链接:http://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.View
尝试了之后发现可以在屏幕上绘制内容,并且在整个应用生效,开心到爆炸,但是!!又遇到了一个问题,这些内容会形成类似遮罩层的东西,导致事件无法穿透。研究了一番,找到了这样一个方法:view.interceptTouchEvent(false),它可以拦截View控件的触屏事件。
接下来,就是文字生成水印图片的问题了,我用的canvas绘制了一个水印,转换成图片实现。期间遇到了一个问题,APP端的uni.canvasToTempFilePath事件需要放在cans.draw()的回调里去实现。
uni.canvasToTempFilePath的坑
昨天做一个将canvas转图片的功能,发现以下问题:
1、在APP端,uni.canvasToTempFilePath方法需要放在convasContext.draw的回调里面,否则会一直报canvas is empty
2、在H5端,需要convasContext.draw与uni.canvasToTempFilePath同步执行,也就是不能放在回调里
3、在APP端uni.canvasToTempFilePath返回的路径是一个临时的图片路径
4、在H5端,生成的是base64路径
昨天做一个将canvas转图片的功能,发现以下问题:
1、在APP端,uni.canvasToTempFilePath方法需要放在convasContext.draw的回调里面,否则会一直报canvas is empty
2、在H5端,需要convasContext.draw与uni.canvasToTempFilePath同步执行,也就是不能放在回调里
3、在APP端uni.canvasToTempFilePath返回的路径是一个临时的图片路径
4、在H5端,生成的是base64路径
uniapp配置request请求的networktimeout
在uniapp中,默认的networktimeout是6000ms,也就是六秒。虽然我在使用时等了好久才进入到fail方法体内,具体也没去测量,总之就是很影响用户体验。那么怎么更改请
求中的networktimeout属性呢?
在uniapp中,networktimeout是属于配置项里面的内容,所以得在配置项里面对request进行配置。
① 点击项目目录下的manifest.json会进入到配置页面,而默认页面只能更改appid,名称,版本号等少量内容,这时需要点击左下方的源码视图进行配置。
② 点击源码视图进入之后你会发现一堆的配置文件。具体参考https://uniapp.dcloud.io/collocation/manifest
③ 根据官方提供的配置说明https://uniapp.dcloud.io/collocation/manifest?id=networktimeout 对照着源码试图进行查看。我们会发现并没有networktimeout这一配置。那么我们
需要增加这一项配置。
④ 在与name同级的地方新增networkTimeout属性,由于该属性下还具有更多的参数可以指定如(connectSocket,uploadFile,downloadFile),所以采用大括号的形式进行
赋值。如
"name" : "firstapp",
"appid" : "__UNI__A0A853F",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"networkTimeout":{
"request":3000
},
........
设置其他的请求超时时间也同理。
新手入门,自学中,总结得有错的地方请指正,别骂我,我怂...
在uniapp中,默认的networktimeout是6000ms,也就是六秒。虽然我在使用时等了好久才进入到fail方法体内,具体也没去测量,总之就是很影响用户体验。那么怎么更改请
求中的networktimeout属性呢?
在uniapp中,networktimeout是属于配置项里面的内容,所以得在配置项里面对request进行配置。
① 点击项目目录下的manifest.json会进入到配置页面,而默认页面只能更改appid,名称,版本号等少量内容,这时需要点击左下方的源码视图进行配置。
② 点击源码视图进入之后你会发现一堆的配置文件。具体参考https://uniapp.dcloud.io/collocation/manifest
③ 根据官方提供的配置说明https://uniapp.dcloud.io/collocation/manifest?id=networktimeout 对照着源码试图进行查看。我们会发现并没有networktimeout这一配置。那么我们
需要增加这一项配置。
④ 在与name同级的地方新增networkTimeout属性,由于该属性下还具有更多的参数可以指定如(connectSocket,uploadFile,downloadFile),所以采用大括号的形式进行
赋值。如
"name" : "firstapp",
"appid" : "__UNI__A0A853F",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"networkTimeout":{
"request":3000
},
........
设置其他的请求超时时间也同理。
新手入门,自学中,总结得有错的地方请指正,别骂我,我怂...
收起阅读 »失踪人口从apicloud正式回归dcloud
dcloud一下简称D公司
我工作中第一个正式用的IDE编辑器是D公司的hbuider。
虽然这之前也用过sublimit和zend和PHP风暴等等,但总体来说hbuider更适合我,
以及目前开发PHP后端仍然再用hbuider。
三年前因为需要开发App,
用了D公司之后发现问题大了去了,
因为我是PHP人员,不会安卓和苹果的第三方SDK打包,
所以就用了apicloud,
时至今日,我发现D公司终于为我等这种人解决了第三方SDK的难题,
而且uni-app的强大,从新让我跪舔。
dcloud一下简称D公司
我工作中第一个正式用的IDE编辑器是D公司的hbuider。
虽然这之前也用过sublimit和zend和PHP风暴等等,但总体来说hbuider更适合我,
以及目前开发PHP后端仍然再用hbuider。
三年前因为需要开发App,
用了D公司之后发现问题大了去了,
因为我是PHP人员,不会安卓和苹果的第三方SDK打包,
所以就用了apicloud,
时至今日,我发现D公司终于为我等这种人解决了第三方SDK的难题,
而且uni-app的强大,从新让我跪舔。
关于dataset的不同表现方式和解决方式
先看一波简单源代码:
<template>
<view @tap="tapFunc" data-param="param">
点我
</view>
</template>
<script>
export default {
data() {
return {
param: {
a : 1,
b : 2
}
}
},
onLoad() {
},
methods: {
tapFunc(e){
// e.currentTarget.dataset.param = ????; 在H5环境中需要从新赋值即可解决 e.currentTarget.dataset.param = this.param;
}
}
}
</script>
<style>
</style>
请注意,方法tapFunc里面的e.currentTarget.dataset.param获取到的dataset是根据环境不同的,
H5环境获取到的是字符串[objct],不能进行任何操作,单纯只是完全的字符串[objct],
微信小程序环境中获取到的objct对象,没问题。
所以在H5环境中需要从新赋值即可解决 e.currentTarget.dataset.param = this.param;
先看一波简单源代码:
<template>
<view @tap="tapFunc" data-param="param">
点我
</view>
</template>
<script>
export default {
data() {
return {
param: {
a : 1,
b : 2
}
}
},
onLoad() {
},
methods: {
tapFunc(e){
// e.currentTarget.dataset.param = ????; 在H5环境中需要从新赋值即可解决 e.currentTarget.dataset.param = this.param;
}
}
}
</script>
<style>
</style>
请注意,方法tapFunc里面的e.currentTarget.dataset.param获取到的dataset是根据环境不同的,
H5环境获取到的是字符串[objct],不能进行任何操作,单纯只是完全的字符串[objct],
微信小程序环境中获取到的objct对象,没问题。
所以在H5环境中需要从新赋值即可解决 e.currentTarget.dataset.param = this.param;
关于页面栈中getCurrentPages()方法,H5和微信小程序的不同表现
H5没问题。
微信小程序中返回的是微信小程序页面栈,
其里面有一个$vm,才是真实的页面栈。
所以统一页面栈获取方法是
let pages = getCurrentPages();
// #ifdef MP-WEIXIN
return pages[pages.length - 1].$vm;
// #endif
return pages[pages.length - 1];
H5没问题。
微信小程序中返回的是微信小程序页面栈,
其里面有一个$vm,才是真实的页面栈。
所以统一页面栈获取方法是
let pages = getCurrentPages();
// #ifdef MP-WEIXIN
return pages[pages.length - 1].$vm;
// #endif
return pages[pages.length - 1];
收起阅读 »
h5显示图片正常,app与小程序图片不显示
问题说明:
H5页面图片显示正常,app端与小程序端不显示图片
我怀疑是v-bind:src="item.url"不适用用手机端或者小程序。但是查了所有文档都没找到证据。
最后比对了几个页面得出结论,如果在图片组件前面了加了过滤器filter,那么图片就会不显示,只有h5端显示正常。
替换方案:使用计算方法替换过滤器。
所以,是过滤器不支持手机端和小程序端的原因吗???
根据官方回复说,自定义组件才能使用filter(做笔记)。
问题说明:
H5页面图片显示正常,app端与小程序端不显示图片
我怀疑是v-bind:src="item.url"不适用用手机端或者小程序。但是查了所有文档都没找到证据。
最后比对了几个页面得出结论,如果在图片组件前面了加了过滤器filter,那么图片就会不显示,只有h5端显示正常。
替换方案:使用计算方法替换过滤器。
所以,是过滤器不支持手机端和小程序端的原因吗???
根据官方回复说,自定义组件才能使用filter(做笔记)。
收起阅读 »uni-app subNVue 原生子窗体开发指南
此功能需要 HBuilderX 版本 1.9.10+, 不支持非自定义组件模式。
需求背景
在我们的开发中,经常会遇到各种层级覆盖和原生界面自定义的问题:
- 覆盖原生导航栏、
tabbar
的弹出层组件。比如侧滑菜单盖不住地图、视频、原生导航栏,比如popup
盖不住tabbar
。 - 弹出层内部元素可滚动,
- 在地图、视频等组件上的添加复杂覆盖组件:比如直播视频上覆盖滚动的聊天记录。
在小程序中只能用 cover-view
来解决。App中,开发者希望有更强的解决方案。
当然在App端使用nvue是不存在前端元素无法覆盖原生元素的层级问题的,但app-vue页面仍然需要面对复杂的层级问题:
- app-vue的
cover-view
不支持嵌套、只能在video
、map
上使用、样式和控件少; plus.nativeObj.view
虽然更灵活,但易用性比较差、没有动画、不支持内部内容滚动。
既然uni-app已经支持 nvue
的原生渲染,我们何不做一个subNVue
,来替代 cover-view
,实现更强的功能?
顾名思义,subNVue
是 vue
页面的子窗体,它不是全屏页面,就是用于解决 vue
页面中的层级覆盖和原生界面自定义用的。它也不是组件,就是一个原生子窗体
在新版的hello uni-app里,接口-界面-原生子窗体新增了subNVue
示例。包括了4个 subNVue
示例:
- 顶部原生的渐变背景色导航栏(注:此示例其实已过期,HBuilderX 2.6.6起pages.json自带的titleNView已经可以实现渐变背景色和更多自定义能力,性能是高于subnvue方案的)
- 侧滑菜单,可以盖住原生视频
- 弹出一个原生的
popup
,并且内部内容可滚动 - 视频上覆盖一个滚动聊天记录
有了 subNVue
,插件市场的一些插件就没有意义了,比如这个原生增强提示框插件,完全可以用 subNVue
替代,免去原生插件打包的麻烦。
在通信方面: subNVue
页面可以和 vue
页面进行通信,来告知 vue
页面用户执行的操作。或者通过 vue
页面对 subNVue
进行数据和状态的更新。 subNVue
除了与 vue
页面进行通信,还 可以与 nvue
页面进行通信。
使用 subNVue 子窗体的页面结构
我们建议 subNVue
子窗体与引用该子窗体的vue页面放在同一目录下,新建 subNVue
目录包含这些 subNVue
子窗体,例如:
|-- pages
|-- index // index 目录
| |-- subNVue // subNVue 目录
| |-- nav.nvue // 自定义导航栏
| |-- popup.nvue // 弹出层子窗体
|-- index.vue // index 页面
当然你也可以提供公共的 subNVue
子窗体,供多个 vue
页面引用,此时我们建议放在 最外层与 pages
文件同级的 platform\app-plus\subNVue
下。(只是建议,不是约束。不管放哪里,只要 pages.json
里引用了,都会编译到App端)
使用 subNVue 子窗体的 pages.json 配置
在 pages.json
中,新增了 subNVues
节点, 与 titleNView
在同一级别。支持配置 subNVue
子窗体的相关属性。配置结构如下:
subNVues:
- id: [String], 全局唯一,不能重复。
- path: [String], subNVue 子窗体的路径。
- type: [String], 内置的特殊子窗体类型,弹出(popup)和导航(navigationBar)。
- style: [Object], 配置子窗体的位置,背景等样式属性。
代码示例:
{
"pages": [{
"path": "pages/index/index", //首页
"style": {
"app-plus": {
"subNVues":[{
"id": "concat", // 唯一标识
"path": "pages/index/subnvue/concat", // 页面路径
/*"type": "popup", 这里不需要*/
"style": {
"position": "absolute",
"dock": "right",
"width": "100rpx",
"height": "150rpx",
"background": "transparent"
}
}]
}
}
}]
}
关于 subNVue
更多详细的配置见: 完整配置
注意事项:
id
属性是全局唯一的,path
路径只能是nuve
页面路径type
属性目前只有导航栏 (navigationBar
) 和弹出层 (popup
) 类型,且级别最高,一旦设置type
为navigationBar
或popup
,position
和dock
的值都会被忽略。position
为原生子窗体的定位方式。dock
表示原生子窗体的停靠位置,只有当position
值为dock
时才生效,如top
,bottom
,right
,left
等。- 在配置中可以使用 upx 单位,方便你进行响应式布局。
subNVue 子窗体书写
subNVue
子窗体引用的是 nvue
页面。所以只需要书写 nvue
页面。
需要注意的是,nvue
与 vue
页面的开发注意事项。两者开发起来还是有一些区别。
相关参考
- 使用
nvue
开发注意事项:https://uniapp.dcloud.io/use-weex - 使用
vue
开发注意事项:https://uniapp.dcloud.io/use
怎么在页面中使用 subNVue 子窗体
在 pages.json
中增加完配置,也写好了 subNVue
子窗体,接下来就是在 vue
/nvue
页面中使用了。 在 vue
和 nvue
页面中使用方式是一样的,这里以 vue
页面为例进行说明:
在页面中打开和关闭 subNVue 子窗体
// 通过 id 获取 nvue 子窗体
const subNVue = uni.getSubNVueById('map_widget')
// 打开 nvue 子窗体
subNVue.show('slide-in-left', 300, function(){
// 打开后进行一些操作...
//
});
// 关闭 nvue 子窗体
subNVue.hide('fade-out', 300)
动态修改 subNVue 子窗体位置,大小
subNVue.setStyle({
top: '100px',
left: '20px',
width: '100px',
height = '50px',
})
subNVue 子窗体与 vue/nvue 页面通信
无论是页面与页面,子窗体与子窗体之间,如果没有了彼此之间的通信,都只是孤立的散件而已。 nvue
子窗体与使用子窗体的 vue
/nvue
页面之间,可以互相发送和传递消息,进而实现彼此之间的互相更新和表现协调。 在 vue
和 nvue
中进行通信的方式一致,这里仍然以 vue
页面为例:
推荐使用页面通讯完成与子窗体通讯(新增)
关于页面通讯的内容详见: 页面通讯指南
通讯实现方式
// 在 subNVue/vue 页面注册事件监听方法
// $on(eventName, callback)
uni.$on('page-popup', (data) => {
vm.title = data.title;
vm.content = data.content;
})
// 在 subNVue/vue 页面触发事件
// $emit(eventName, data)
uni.$emit('page-popup', {
title: '我是一个title',
content: '我是data content'
});
使用页面通讯时注意事项: 要在页面卸载前,使用 uni.$off 移除事件监听器。
旧的通讯方式(推荐上述使用页面通讯机制)
vue
页面中监听 subNVue
子窗体的消息和向 subNVue
子窗体传递消息
// 获取要通信的 subNVue 子窗体
const subNVue = uni.getSubNVueById('map_widget')
// vue 向 subNVue 子窗体发送消息
// postMessage(<Object>)
subNVue.postMessage({
type: 'message',
title: '我是来自 vue 页面的消息',
content: 'Hello, map_widget'
});
// vue 监听 subNVue 子窗体传递的消息
subNVue.onMessage((res) => {
const data = res.data;
// 执行一些操作
});
subNVue
子窗体监听 vue
页面的消息和向 vue
页面发送消息
// 获取当前 subNVue 子窗体
// 可以使用 getSubNVueById 查找的方式,但推荐使用下面的方式
const subNVue = uni.getCurrentSubNVue();
// subNVue 子窗体向 vue 页面发送消息
// postMessage(<Object>)
subNVue.postMessage({
type: 'message',
title: '我是来自 subNVue 子窗体的消息',
content: 'Hello, map_widget'
});
// subNVue 子窗体监听 vue 页面传递的消息
subNVue.onMessage((res) => {
const data = res.data;
// 执行一些操作
});
总结
基本的使用方式和场景已经介绍完了, 对于使用 subNVue
在更多的应用场景中去实现更多的功能,就需要大家去不断的尝试和创新了。
当然如果一些简单的需求,如果 cover-view 已经能搞定,那也没必要使用subNVue
,毕竟能跨端,内存占用也更低。
强大的东西往往也意味着消耗更多内存,为了保证更好的性能体验,一个vue页面不要加载太多 subNVue
子窗体,建议控制在三个以内。
注意事项:
在使用 subNVue 子窗体的页面中,同时满足下面两种情形时:
-
页面包含 map, video 之类的原生组件
-
页面使用了 type 为 navigationBar 的 subNVue 子窗体
原生组件可能会出现错位的问题,目前可以使用以下方法进行解决:
- 将此类元素放在页面的 onReady 中进行渲染。
- 采用延时的策略,保证元素在页面渲染后,再去定位位置。
此功能需要 HBuilderX 版本 1.9.10+, 不支持非自定义组件模式。
需求背景
在我们的开发中,经常会遇到各种层级覆盖和原生界面自定义的问题:
- 覆盖原生导航栏、
tabbar
的弹出层组件。比如侧滑菜单盖不住地图、视频、原生导航栏,比如popup
盖不住tabbar
。 - 弹出层内部元素可滚动,
- 在地图、视频等组件上的添加复杂覆盖组件:比如直播视频上覆盖滚动的聊天记录。
在小程序中只能用 cover-view
来解决。App中,开发者希望有更强的解决方案。
当然在App端使用nvue是不存在前端元素无法覆盖原生元素的层级问题的,但app-vue页面仍然需要面对复杂的层级问题:
- app-vue的
cover-view
不支持嵌套、只能在video
、map
上使用、样式和控件少; plus.nativeObj.view
虽然更灵活,但易用性比较差、没有动画、不支持内部内容滚动。
既然uni-app已经支持 nvue
的原生渲染,我们何不做一个subNVue
,来替代 cover-view
,实现更强的功能?
顾名思义,subNVue
是 vue
页面的子窗体,它不是全屏页面,就是用于解决 vue
页面中的层级覆盖和原生界面自定义用的。它也不是组件,就是一个原生子窗体
在新版的hello uni-app里,接口-界面-原生子窗体新增了subNVue
示例。包括了4个 subNVue
示例:
- 顶部原生的渐变背景色导航栏(注:此示例其实已过期,HBuilderX 2.6.6起pages.json自带的titleNView已经可以实现渐变背景色和更多自定义能力,性能是高于subnvue方案的)
- 侧滑菜单,可以盖住原生视频
- 弹出一个原生的
popup
,并且内部内容可滚动 - 视频上覆盖一个滚动聊天记录
有了 subNVue
,插件市场的一些插件就没有意义了,比如这个原生增强提示框插件,完全可以用 subNVue
替代,免去原生插件打包的麻烦。
在通信方面: subNVue
页面可以和 vue
页面进行通信,来告知 vue
页面用户执行的操作。或者通过 vue
页面对 subNVue
进行数据和状态的更新。 subNVue
除了与 vue
页面进行通信,还 可以与 nvue
页面进行通信。
使用 subNVue 子窗体的页面结构
我们建议 subNVue
子窗体与引用该子窗体的vue页面放在同一目录下,新建 subNVue
目录包含这些 subNVue
子窗体,例如:
|-- pages
|-- index // index 目录
| |-- subNVue // subNVue 目录
| |-- nav.nvue // 自定义导航栏
| |-- popup.nvue // 弹出层子窗体
|-- index.vue // index 页面
当然你也可以提供公共的 subNVue
子窗体,供多个 vue
页面引用,此时我们建议放在 最外层与 pages
文件同级的 platform\app-plus\subNVue
下。(只是建议,不是约束。不管放哪里,只要 pages.json
里引用了,都会编译到App端)
使用 subNVue 子窗体的 pages.json 配置
在 pages.json
中,新增了 subNVues
节点, 与 titleNView
在同一级别。支持配置 subNVue
子窗体的相关属性。配置结构如下:
subNVues:
- id: [String], 全局唯一,不能重复。
- path: [String], subNVue 子窗体的路径。
- type: [String], 内置的特殊子窗体类型,弹出(popup)和导航(navigationBar)。
- style: [Object], 配置子窗体的位置,背景等样式属性。
代码示例:
{
"pages": [{
"path": "pages/index/index", //首页
"style": {
"app-plus": {
"subNVues":[{
"id": "concat", // 唯一标识
"path": "pages/index/subnvue/concat", // 页面路径
/*"type": "popup", 这里不需要*/
"style": {
"position": "absolute",
"dock": "right",
"width": "100rpx",
"height": "150rpx",
"background": "transparent"
}
}]
}
}
}]
}
关于 subNVue
更多详细的配置见: 完整配置
注意事项:
id
属性是全局唯一的,path
路径只能是nuve
页面路径type
属性目前只有导航栏 (navigationBar
) 和弹出层 (popup
) 类型,且级别最高,一旦设置type
为navigationBar
或popup
,position
和dock
的值都会被忽略。position
为原生子窗体的定位方式。dock
表示原生子窗体的停靠位置,只有当position
值为dock
时才生效,如top
,bottom
,right
,left
等。- 在配置中可以使用 upx 单位,方便你进行响应式布局。
subNVue 子窗体书写
subNVue
子窗体引用的是 nvue
页面。所以只需要书写 nvue
页面。
需要注意的是,nvue
与 vue
页面的开发注意事项。两者开发起来还是有一些区别。
相关参考
- 使用
nvue
开发注意事项:https://uniapp.dcloud.io/use-weex - 使用
vue
开发注意事项:https://uniapp.dcloud.io/use
怎么在页面中使用 subNVue 子窗体
在 pages.json
中增加完配置,也写好了 subNVue
子窗体,接下来就是在 vue
/nvue
页面中使用了。 在 vue
和 nvue
页面中使用方式是一样的,这里以 vue
页面为例进行说明:
在页面中打开和关闭 subNVue 子窗体
// 通过 id 获取 nvue 子窗体
const subNVue = uni.getSubNVueById('map_widget')
// 打开 nvue 子窗体
subNVue.show('slide-in-left', 300, function(){
// 打开后进行一些操作...
//
});
// 关闭 nvue 子窗体
subNVue.hide('fade-out', 300)
动态修改 subNVue 子窗体位置,大小
subNVue.setStyle({
top: '100px',
left: '20px',
width: '100px',
height = '50px',
})
subNVue 子窗体与 vue/nvue 页面通信
无论是页面与页面,子窗体与子窗体之间,如果没有了彼此之间的通信,都只是孤立的散件而已。 nvue
子窗体与使用子窗体的 vue
/nvue
页面之间,可以互相发送和传递消息,进而实现彼此之间的互相更新和表现协调。 在 vue
和 nvue
中进行通信的方式一致,这里仍然以 vue
页面为例:
推荐使用页面通讯完成与子窗体通讯(新增)
关于页面通讯的内容详见: 页面通讯指南
通讯实现方式
// 在 subNVue/vue 页面注册事件监听方法
// $on(eventName, callback)
uni.$on('page-popup', (data) => {
vm.title = data.title;
vm.content = data.content;
})
// 在 subNVue/vue 页面触发事件
// $emit(eventName, data)
uni.$emit('page-popup', {
title: '我是一个title',
content: '我是data content'
});
使用页面通讯时注意事项: 要在页面卸载前,使用 uni.$off 移除事件监听器。
旧的通讯方式(推荐上述使用页面通讯机制)
vue
页面中监听 subNVue
子窗体的消息和向 subNVue
子窗体传递消息
// 获取要通信的 subNVue 子窗体
const subNVue = uni.getSubNVueById('map_widget')
// vue 向 subNVue 子窗体发送消息
// postMessage(<Object>)
subNVue.postMessage({
type: 'message',
title: '我是来自 vue 页面的消息',
content: 'Hello, map_widget'
});
// vue 监听 subNVue 子窗体传递的消息
subNVue.onMessage((res) => {
const data = res.data;
// 执行一些操作
});
subNVue
子窗体监听 vue
页面的消息和向 vue
页面发送消息
// 获取当前 subNVue 子窗体
// 可以使用 getSubNVueById 查找的方式,但推荐使用下面的方式
const subNVue = uni.getCurrentSubNVue();
// subNVue 子窗体向 vue 页面发送消息
// postMessage(<Object>)
subNVue.postMessage({
type: 'message',
title: '我是来自 subNVue 子窗体的消息',
content: 'Hello, map_widget'
});
// subNVue 子窗体监听 vue 页面传递的消息
subNVue.onMessage((res) => {
const data = res.data;
// 执行一些操作
});
总结
基本的使用方式和场景已经介绍完了, 对于使用 subNVue
在更多的应用场景中去实现更多的功能,就需要大家去不断的尝试和创新了。
当然如果一些简单的需求,如果 cover-view 已经能搞定,那也没必要使用subNVue
,毕竟能跨端,内存占用也更低。
强大的东西往往也意味着消耗更多内存,为了保证更好的性能体验,一个vue页面不要加载太多 subNVue
子窗体,建议控制在三个以内。
注意事项:
在使用 subNVue 子窗体的页面中,同时满足下面两种情形时:
-
页面包含 map, video 之类的原生组件
-
页面使用了 type 为 navigationBar 的 subNVue 子窗体
原生组件可能会出现错位的问题,目前可以使用以下方法进行解决:
- 将此类元素放在页面的 onReady 中进行渲染。
- 采用延时的策略,保证元素在页面渲染后,再去定位位置。
小程序开发:用原生还是选框架(wepy/mpvue/taro/uni-app)-- 第1季
自 2017-1-9
微信小程序诞生以来,历经2年多的迭代升级,已有数百万小程序上线,成为继Web、iOS、Android之后,第四大主流开发技术。
与之相随,小程序的开发生态也在蓬勃发展,从最初的微信原生开发,到wepy
、mpvue
、taro
、uni-app
等框架依次出现,从刀耕火种演进为现代化开发,生态越来越丰富。
选择多了,问题也就来了,开发小程序,该用原生还是选择三方框架?
首先,微信原生开发的槽点大多集中如下:
- 原生开发对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程
- 微信定义了一个不伦不类的语法,不如正经学vue、react,学会了全端通用,而不是只为小程序。小程序的setData和类似template模式像是React和Vue的混合体,却丢了React的灵活和Vue的响应式。
- vue/react生态里有太多周边工具,可以提高开发效率,比如ide、校验器、三方库。。。
- 微信那个ide和专业编辑器相比实在不好用
- 没有正儿八经的状态管理
同时,开发者对三方框架,又总是有各种顾虑:
- 怕性能不如原生
- 怕有些功能框架实现不了,只能用原生
- 怕框架不稳定,跳到坑里
- 以及诸多三方框架,到底该用哪个
面对如此纠结的场景,不少热心开发者发布评测文章分享经验,但感觉众说纷纭,过期信息太多。缺少一份非常专业的、深度的,或者按如今流行的话来讲,“硬核的”评测报告。
做评测报告这件事,不同于泛泛经验分享,其实非常花费时间。它需要:
- 你必须成为每一个框架的专业使用人员,而不是浅浅的了解一下这些框架
- 真实的动手写多个平台的测试例,比较各个平台的功能、性能,了解他们的社区情况、技术服务情况
- 你要有长期跟踪和更新报告的能力,避免半年后沦为过期信息
换言之:评测要想真,功夫得做深!
uni-app
团队花费2个周时间完成本报告,并坚持每个季度更新一次本评测报告。目前更新时间为2019年5月。
本文从面向用户、面向开发者
两大维度七大细项,对微信原生及主流的wepy
、mpvue
、taro
、uni-app
开发框架进行横向对比,希望给开发者在小程序框架选型时提供一种参考思路。本文基于各框架官网可采集到的公开数据及真实测试数据,希望客观公正地评价各个框架的现状和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光来看待,如发现本文中有任何评测失真,欢迎在这里报 issuse。
面向用户、面向开发者
维度,具体包括:
- 用户:提供完整的业务实现,并保证高性能体验
- 开发者:平缓的学习曲线、现代开发体验(工程化)、高效的社区支持、活跃的开发迭代、多端复用
2. 用户
1.1 功能实现
软件开发,首要目标是向用户提供完整、闭环的业务功能。
在web开发中,如果vue、react等框架的使用,造成开发者无法操作浏览器提供的所有api,那这样的框架肯定是不成熟的。小程序开发也一样,任何开发框架,都不能限制底层的api调用。
而各种业务功能底层依赖微信暴漏的组件和接口(微信官网介绍的组件和 API 规范,也即微信原生API),三方框架是基于微信原生进行的二次封装,开发者此时常会有个疑问:小程序在不断的迭代升级,如果某项业务依赖于最新的小程序API,但三方框架尚未封装,该怎么办?
实际上就像web开发中使用vue、react一样,浏览器出了一个新API,并不会涉及vue、react的升级。本评测里的所有框架,都不会限制开发者调用底层能力。这里详细解释下原因:
- wepy:未对小程序API作二次封装,API依然使用微信原生的,框架与微信小程序是否新增API无关
- mpvue:支持微信的所有原生组件和api,无限制。同时框架封装了自己的跨端API,使用方式类似
mpvue.request()
- taro:支持微信的所有原生组件和api,无限制。同时框架封装了自己的跨端API,使用方式类似
Taro.request()
,支持Taro 代码与小程序代码混写,可通过混写的方式调用框架尚未封装的小程序新增API - uni-app:支持微信的所有原生组件和api,无限制。在跨端方面,即便仍然使用微信原生的组件和API,也可以直接跨端编译到App、H5、以及支付宝百度头条等小程序。但为了管理清晰,推荐使用uni封装的API,类似
uni.request()
。同时支持条件编译,可在条件编译代码块中,随意调用各个平台新增的API及组件
注:以上顺序,按各个框架的诞生顺序排序,下同。
故,三方框架均可调用所有小程序API,完成用户的业务需求,这个维度各框架是无差别的。
然而有差别的,是性能体验。
1.2 性能体验
三方框架,内部大多做了层层封装,这些封装是否会增加运行负载,导致性能下降?尤其是与原生微信小程序开发相比性能怎么样,这是大家普遍关心的问题。
为客观的进行对比,我们特意搭建了一个测试模型,详细如下:
-
开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。
-
界面如下:
-
开发版本:一共开发了5个版本,包括微信原生版、wepy版、mpvue版、taro版、uni-app版,按照官网指引通过
cli
方式默认安装。 -
测试代码开源(Github仓库地址:https://github.com/dcloudio/test-framework),
Tips:若有同学觉得测试代码写法欠妥,欢迎提交 PR 或 Issus -
测试机型:红米 Redmi 6 Pro、MIUI 10.2.2.0 稳定版(最新版)、微信版本 7.0.3(最新版)
-
测试环境:每个框架开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。
我们以上述仿微博小程序为例,测试2个容易出性能问题的点:长列表加载、大量点赞组件的响应。
1.2.1 长列表加载
仿微博的列表是一个包含很多组件的列表,这种复杂列表对性能的压力更大,很适合做性能测试。
从触发上拉加载到数据更新、页面渲染完成,需要准确计时。人眼视觉计时肯定不行,我们采用程序埋点的方式,制定了如下计时时机:
- 计时开始时机:交互事件触发,框架赋值之前,如:上拉加载(onReachBottom)函数开头
- 计时结束时机:页面渲染完毕(微信setData回调函数开头)
Tips:setData
回调函数开头可认为是页面渲染完成的时间,是因为微信setData
定义如下(微信规范):
测试方式:从页面空列表开始,通过程序自动触发上拉加载,每次新增20条列表,记录单次耗时;固定间隔连续触发 N 次上拉加载,使得页面达到 20*N 条列表,计算这 N 次触发上拉到渲染完成的平均耗时。
测试结果如下:
说明:以400条微博列表为例,从页面空列表开始,每隔1秒触发一次上拉加载(新增20条微博),记录单次耗时,触发20次后停止(页面达到400条微博),计算这20次的平均耗时,结果微信原生在这20次 触发上拉 -> 渲染完成
的平均耗时为876毫秒,最快的uni-app
是741毫秒,最慢的mpvue
是4493毫秒
大家初看这个数据,可能比较疑惑,别急,下方有详细说明
说明1:为何 mpvue/wepy 测试数据不完整?
mpvue
、wepy
诞生之初,微信小程序尚不支持自定义组件,无法进行组件化开发;mpvue
、wepy
为解决这个问题,将用户编写的Vue
组件,编译为WXML
中的模板(template),变相实现了组件化开发能力,提高代码复用性,这在当时的技术条件下是很棒的技术方案。
但如此方案,在页面复杂、组件较多的时,会大量增加页面 dom 节点数量,甚至超出微信的 dom 节点数限制。我们在 红米手机(Redmi 6 Pro)上实测,页面组件超过500个时,mpvue
、wepy
实现的仿微博App就会报出如下异常,并停止渲染,故这两个测试框架在组件较多时,测试数据不完整。这也就意味着,当页面组件太多时,无法使用这2个框架。
dom limit exceeded please check if there's any mistake you've made
Tips1:wepy
官网的CHANGELOG,提到测试版本添加了对小程序原生组件的支持,因为是测试版,官方在 issue 中也表示不推荐使用,暂未纳入评测
Tips2:wepy
在400条列表以内,为何性能高于微信原生框架,这个跟自定义组件管理开销及业务场景有关(wepy
编译为模板,不涉及组件创建及管理开销),后续对微博点赞,涉及组件数据传递时,微信原生框架的性能优势就提现出来了,详见下方测试数据。
说明2:为什么测试数据显示uni-app 会比微信原生框架的性能略好呢?
其实,在页面上有200条记录(200个组件)时,taro
性能数据也比微信原生框架更好。
微信原生框架耗时主要在setData
调用上,开发者若不单独优化,则每次都会传递大量数据;而 uni-app
、taro
都在调用setData
之前自动做diff
计算,每次仅传递变动的数据。
例如当前页面有20条数据,触发上拉加载时,会新加载20条数据,此时原生框架通过如下代码测试时,setData
会传输40条数据
data: {
listData: []
},
onReachBottom() { //上拉加载
let listData = this.data.listData;
listData.push(...Api.getNews());//新增数据
this.setData({
listData
}) //全量数据,发送数据到视图层
}
开发者使用微信原生框架,完全可以自己优化,精简传递数据,比如修改如下:
data: {
listData: []
},
onReachBottom() { //上拉加载
// 通过长度获取下一次渲染的索引
let index = this.data.listData.length;
let newData = {}; //新变更数据
Api.getNews().forEach((item) => {
newData['listData[' + (index++) + ']'] = item //赋值,索引递增
})
this.setData(newData) //增量数据,发送数据到视图层
}
经过如上优化修改后,再次测试,微信原生框架性能数据如下:
从测试结果可看出,经过开发者手动优化,微信原生框架可达到更好的性能,但 uni-app
、taro
相比微信原生,性能差距并不大。
这个结果,和web开发类似,web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。
也恰恰是因为Vue
、react
框架的优秀,性能好,开发体验好,所以原生js开发已经逐渐减少使用了。
复杂长列表加载下一页评测结论:微信原生开发手工优化
,uni-app
>微信原生开发未手工优化
,taro
> wepy
> mpvue
Tips:有人以为uni-app和mpvue是一样的,早期uni-app确实基于mpvue改造过,但后来因为性能和vue语法支持度问题,已经完全重新开发了。
1.2.2 点赞组件响应速度
长列表中的某个组件,比如点赞组件,点击时是否能及时的修改未赞和已赞状态?是这项测试的评测点。
测试方式:
- 选中某微博,点击“点赞”按钮,实现点赞状态状态切换(已赞高亮、未赞灰色),
- 点赞按钮
onclick
函数开头开始计时,setData
回调函数开头结束计时;
在红米手机(Redmi 6 Pro)上进行多次测试,求其平均值,结果如下:
说明:也就是在列表数量为400时,微信原生开发的应用,点赞按钮从点击到状态变化需要111毫秒。
测试结果数据说明:
- wepy/mpvue 测试数据不完整的原因同上,在组件较多时,页面已经不再渲染了
- 基于微信自定义组件实现组件开发的框架(uni-app/taro),组件数据通讯性能接近于微信原生框架,远高于基于
template
实现组件开发的框架(wepy/mpvue)性能
组件数据更新性能测评:微信原生开发
,uni-app
,taro
> wepy
> mpvue
综上,本性能测试做了2个测试,长列表加载和组件状态更新,综合2个实验,结论如下:
微信原生开发手工优化
,uni-app
>微信原生开发未手工优化
,taro
> wepy
> mpvue
2.开发者
在满足用户业务需求的前提下,我们谈谈开发者的需求,从如下几个维度比较:
- 平缓的学习曲线:简单易学,最好能复用现有技术栈,丰富的学习资料
- 高效的开发体验:现代前端开发流程、工程化支持
- 高效的社区支持:遇到问题,可很快的寻求到帮助
- 活跃的开发迭代:框架处于积极更新升级状态,无需担心停更
2.1 平缓的学习曲线
2.1.1 DSL语法支持
选择开发团队熟悉的、能快速上手的DSL,是团队框架选型的基本点。
首先微信原生的开发语法,既像React
,又像Vue
,有点不伦不类,对于开发者来说,等于又要学习一套新的语法,大幅提升了学习成本,这一直被大家所诟病。
其它开发框架基本都遵循React、Vue(类Vue)语法,其主要目的:复用工程师的现有技术栈,降低学习成本。此时,框架对于原框架(React/Vue)语法的支持度就是一个重要的衡量标准,如果支持度较低、和原框架语法差异较大,则开发者无异于要学习一门新的框架,成本太高。
实际开发中发现,各个开发框架,都没有完全实现Vue
、React
在web上的所有语法:
wepy
开发风格接近于 Vue.js
,属于类Vue
实现,相对微信原生开发算前进了一大步,但相比完整Vue
语法还有较大差距,开发时需要单独学习它的规则;
mpvue
、uni-app
框架基于 Vue.js
核心,通过修改 Vue.js
的 runtime
和 compiler
,实现了在小程序端的运行。mpvue
支持的Vue语法略少,uni-app
则基本支持绝大多数vue语法,如filter
、复杂 JavaScript
表达式等;
taro
对于 JSX
的语法支持度,也达到了绝大多数都支持的完善程度。
DSL语法支持评测:taro
,uni-app
> mpvue
> wepy
> 微信原生
2.1.2 学习资料完善度
官方文档、问题搜索、示例demo的完备度方面:
- 微信原生:文档丰富,API搜索准确,官方有示例demo,支持官网上调起微信开发者工具,预览运行效果 详见
- wepy:文档只有2页,没有搜索,组件API等文档都直接看微信的文档。没有提供示例demo,很多配置需要靠猜。详见
- mpvue:文档较少,但其概念不复杂,组件API等文档都直接看微信的文档,学习难度低。问题搜索效果一般。没有提供示例demo。详见
- taro:基础文档完整,具体使用问题资源较少,问题搜索效果一般,示例demo只包含基础功能,仅发布了微信一端。详见
- uni-app:基础文档和各种使用专题内容丰富,问题搜索效果较好,示例demo功能完备,并发布为7端上线。详见
教学课程方面:
学习资料完善度评测:微信原生 > uni-app
> mpvue
, taro
> wepy
2.2 现代前端开发体验
开发体验层面,处于明显劣势的是微信原生开发,主要差距在于:
- 框架开发提供了精简的代码组织(微信原生开发,一个Page由4个文件构成,写个代码要开的标签卡太多)
- 框架开发提供了更强大的组件化能力
- 框架开发提供了应用状态管理(类Vuex/Redux/Mobx等)
- 框架开发能灵活支持各种 Sass 等 预处理器
- 框架开发可提供完整的 ES Next 语法支持
- 框架开发方便自定义构建策略
其它小程序开发框架均支持cli
模式,可以在主流前端工具中开发,且基本都带有d.ts的语法提示库。
由于mpvue
、uni-app
、taro
直接支持vue
、react
语法,配套的ide工具链较丰富,着色、校验、格式化完善;wepy
要弱一些,有部分三方维护的vscode插件。
好的开发工具,绝对可以大幅提升开发体验,这个维度上,明显高出一截的框架是uni-app
,其出品公司同时也是HBuilder的出品公司,DCloud.io。HBuilder是四大主流前端开发工具(可对比百度指数),其为uni-app
做了很多优化,故uni-app
的开发效率、易用性非其他框架可及。
开发体验维度,对比结果:uni-app
> taro
,mpvue
> wepy
> 微信原生
这里可以输出一个结论:如果你需要工程化能力,那就直接忘了微信原生开发吧。
2.3 高效的社区支持
学习、开发难免遇到问题,官方技术支持和社区活跃度很重要。
本次评测demo开发期间,我们的同学(同时掌握vue和react),在学习研究各个多端框架时,切实感受到由于语法、学习资料、社区的差异带来的学习门槛,吐出了很多槽。
综合评估,本项评测结论:微信原生
, uni-app
> taro
> mpvue
> wepy
2.4 活跃的开发迭代
开发者必须关心一个问题:该项目是否有人长期维护?
这个问题可以通过github commits 频次、产品更新日志(changelog)、百度搜索指数等指标来衡量和对比。
github commits 频次
我们采集2019年4月份(时间为4.1 ~ 4.30),每个项目在github上的master分支有commit的天数,结果如下:
Tips:
- 微信原生是闭源的,看不到 commits 数量,但保持每月至少一次的更新节奏,详见
wepy
的master分支无commit,最新的2.0.x分支在4月份也仅1天有commit记录
从 commit 的记录来看,taro
、uni-app
处于更新比较活跃的状态,wepy
、mpvue
则相对疲软,呈现无人维护之态。
产品更新日志
通过浏览产品更新日志,可确认产品是否在积极迭代、增加新功能、修复用户bug。
我们分别查看各框架官方链接的更新日志(CHANGELOG),下方是链接地址:
通过产品更新日志对比,微信原生、taro
、uni-app
三者更新频繁,bug修复、新功能补充都处于比较紧凑的状态;而mpvue
、wepy
则已有长时间没有版本发布,开发者选型需谨慎。
另外提供下各框架的开发团队情况,这也是决定项目生命力的重要参考。
wepy
:腾讯一名员工的兼职作品
mpvue
:美团酒旅事业部前端团队出品
taro
:京东凹凸实验室
uni-app
:DCloud,B轮创业公司,投入几十人、数千万打造uni-app生态。
2.5 多端复用
除了微信小程序,支付宝、百度、字节跳动、QQ等小程序陆续上线,开发者迟早要面对多端开发。
但每个跨端框架能否真的像官网宣传的那样,实现开发一次,发布到所有小程序平台?甚至和H5平台复用代码?
我们用事实说话,依然使用上述仿微博App,依次发布到各平台,验证每个框架在各端的兼容性,结果如下:
测试结果说明:
- ⭕ 表示支持且功能正常,❌ 表示不支持,其它则表示支持但存在部分bug或兼容问题
通过这个简单的例子可以看出,跨端支持度测评结论: uni-app
,taro
> mpvue
> 原生微信小程序
、wepy
但是仅有上面的测试还不全面,实际业务要比这个测试例复杂很多。但我们没法开发很多复杂业务做评测,所以还需要再对照各家文档补充一些信息。
由于每个框架的文档中都描述了各种组件和API的跨端支持程度。我们过了几家的文档,发现各家基本是以微信小程序为基线,然后把各种组件和API在其他端实现了一遍:
taro
:H5端实现了大部分微信的APIuni-app
:组件、API、配置,大部分在各个端均已实现,个别API有说明在某些端不支持。可以看出uni-app是完整在H5端实现了一套微信模拟器
跨端框架,一方面要考虑框架提供的通用api跨端支持,同时还要考虑不同端的特色差异如何兼容。毕竟每个端都会有自己的特色,不可能完全一致。
taro
:提供了js环境变量判断和统一接口的多端文件,可以在组件、js、文件方面扩展多端,不支持其他环节的分平台处理。uni-app
:提供了条件编译模型,所有代码包括组件、js、css、配置json、文件、目录,均支持条件编译,可不受限的编写各端差异代码。
跨端框架,还涉及一个ui框架的跨端问题,评测结果如下:
最后补充跨端案例:
- mpvue:微信端案例丰富,未见其它端案例
- taro:微信端案例丰富,百度、支付宝、H5端亦有少量案例
- uni-app:多端案例丰富,官方示例已发布到7端(包括App端)
综合以上信息,本项的最终评测结论:uni-app
> taro
> mpvue
> 原生微信小程序
、wepy
这里可以输出一个结论,如果有多端发布需求,微信原生开发、wepy
这两种方式可以直接排除了。
结语
真实客观的永远是实验和数据,而不是结论。不同需求的开发者,可以根据上述实验数据,自行得出自己的选型结论。
但作为一篇完整的评测,我们也必须提供一份总结,虽然它可能加入了我们的主观感受:
如果你只开发微信小程序,不做多端,那么使用uni-app
、taro
是更优的选择,他们相当于web世界的vue和react,有了这些工具,不再需要使用原生wxml开发。
- 如果坚持微信原生开发,需要注意手动写优化代码来控制
setdata
,并且注意其工程化能力非常弱 - 如果你是
react
系,那就用taro
- 如果是
vue
系,那就用uni-app
,uni-app
在性能、周边生态和开发效率上更有优势
如果你开发多端,uni-app
和taro
都可以,可根据自己熟悉的技术栈选择,相对而言uni-app
的多端成熟度更高一些。
如有读者认为本文中任何评测失真,欢迎在这里报 issuse。
自 2017-1-9
微信小程序诞生以来,历经2年多的迭代升级,已有数百万小程序上线,成为继Web、iOS、Android之后,第四大主流开发技术。
与之相随,小程序的开发生态也在蓬勃发展,从最初的微信原生开发,到wepy
、mpvue
、taro
、uni-app
等框架依次出现,从刀耕火种演进为现代化开发,生态越来越丰富。
选择多了,问题也就来了,开发小程序,该用原生还是选择三方框架?
首先,微信原生开发的槽点大多集中如下:
- 原生开发对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程
- 微信定义了一个不伦不类的语法,不如正经学vue、react,学会了全端通用,而不是只为小程序。小程序的setData和类似template模式像是React和Vue的混合体,却丢了React的灵活和Vue的响应式。
- vue/react生态里有太多周边工具,可以提高开发效率,比如ide、校验器、三方库。。。
- 微信那个ide和专业编辑器相比实在不好用
- 没有正儿八经的状态管理
同时,开发者对三方框架,又总是有各种顾虑:
- 怕性能不如原生
- 怕有些功能框架实现不了,只能用原生
- 怕框架不稳定,跳到坑里
- 以及诸多三方框架,到底该用哪个
面对如此纠结的场景,不少热心开发者发布评测文章分享经验,但感觉众说纷纭,过期信息太多。缺少一份非常专业的、深度的,或者按如今流行的话来讲,“硬核的”评测报告。
做评测报告这件事,不同于泛泛经验分享,其实非常花费时间。它需要:
- 你必须成为每一个框架的专业使用人员,而不是浅浅的了解一下这些框架
- 真实的动手写多个平台的测试例,比较各个平台的功能、性能,了解他们的社区情况、技术服务情况
- 你要有长期跟踪和更新报告的能力,避免半年后沦为过期信息
换言之:评测要想真,功夫得做深!
uni-app
团队花费2个周时间完成本报告,并坚持每个季度更新一次本评测报告。目前更新时间为2019年5月。
本文从面向用户、面向开发者
两大维度七大细项,对微信原生及主流的wepy
、mpvue
、taro
、uni-app
开发框架进行横向对比,希望给开发者在小程序框架选型时提供一种参考思路。本文基于各框架官网可采集到的公开数据及真实测试数据,希望客观公正地评价各个框架的现状和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光来看待,如发现本文中有任何评测失真,欢迎在这里报 issuse。
面向用户、面向开发者
维度,具体包括:
- 用户:提供完整的业务实现,并保证高性能体验
- 开发者:平缓的学习曲线、现代开发体验(工程化)、高效的社区支持、活跃的开发迭代、多端复用
2. 用户
1.1 功能实现
软件开发,首要目标是向用户提供完整、闭环的业务功能。
在web开发中,如果vue、react等框架的使用,造成开发者无法操作浏览器提供的所有api,那这样的框架肯定是不成熟的。小程序开发也一样,任何开发框架,都不能限制底层的api调用。
而各种业务功能底层依赖微信暴漏的组件和接口(微信官网介绍的组件和 API 规范,也即微信原生API),三方框架是基于微信原生进行的二次封装,开发者此时常会有个疑问:小程序在不断的迭代升级,如果某项业务依赖于最新的小程序API,但三方框架尚未封装,该怎么办?
实际上就像web开发中使用vue、react一样,浏览器出了一个新API,并不会涉及vue、react的升级。本评测里的所有框架,都不会限制开发者调用底层能力。这里详细解释下原因:
- wepy:未对小程序API作二次封装,API依然使用微信原生的,框架与微信小程序是否新增API无关
- mpvue:支持微信的所有原生组件和api,无限制。同时框架封装了自己的跨端API,使用方式类似
mpvue.request()
- taro:支持微信的所有原生组件和api,无限制。同时框架封装了自己的跨端API,使用方式类似
Taro.request()
,支持Taro 代码与小程序代码混写,可通过混写的方式调用框架尚未封装的小程序新增API - uni-app:支持微信的所有原生组件和api,无限制。在跨端方面,即便仍然使用微信原生的组件和API,也可以直接跨端编译到App、H5、以及支付宝百度头条等小程序。但为了管理清晰,推荐使用uni封装的API,类似
uni.request()
。同时支持条件编译,可在条件编译代码块中,随意调用各个平台新增的API及组件
注:以上顺序,按各个框架的诞生顺序排序,下同。
故,三方框架均可调用所有小程序API,完成用户的业务需求,这个维度各框架是无差别的。
然而有差别的,是性能体验。
1.2 性能体验
三方框架,内部大多做了层层封装,这些封装是否会增加运行负载,导致性能下降?尤其是与原生微信小程序开发相比性能怎么样,这是大家普遍关心的问题。
为客观的进行对比,我们特意搭建了一个测试模型,详细如下:
-
开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。
-
界面如下:
-
开发版本:一共开发了5个版本,包括微信原生版、wepy版、mpvue版、taro版、uni-app版,按照官网指引通过
cli
方式默认安装。 -
测试代码开源(Github仓库地址:https://github.com/dcloudio/test-framework),
Tips:若有同学觉得测试代码写法欠妥,欢迎提交 PR 或 Issus -
测试机型:红米 Redmi 6 Pro、MIUI 10.2.2.0 稳定版(最新版)、微信版本 7.0.3(最新版)
-
测试环境:每个框架开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。
我们以上述仿微博小程序为例,测试2个容易出性能问题的点:长列表加载、大量点赞组件的响应。
1.2.1 长列表加载
仿微博的列表是一个包含很多组件的列表,这种复杂列表对性能的压力更大,很适合做性能测试。
从触发上拉加载到数据更新、页面渲染完成,需要准确计时。人眼视觉计时肯定不行,我们采用程序埋点的方式,制定了如下计时时机:
- 计时开始时机:交互事件触发,框架赋值之前,如:上拉加载(onReachBottom)函数开头
- 计时结束时机:页面渲染完毕(微信setData回调函数开头)
Tips:setData
回调函数开头可认为是页面渲染完成的时间,是因为微信setData
定义如下(微信规范):
测试方式:从页面空列表开始,通过程序自动触发上拉加载,每次新增20条列表,记录单次耗时;固定间隔连续触发 N 次上拉加载,使得页面达到 20*N 条列表,计算这 N 次触发上拉到渲染完成的平均耗时。
测试结果如下:
说明:以400条微博列表为例,从页面空列表开始,每隔1秒触发一次上拉加载(新增20条微博),记录单次耗时,触发20次后停止(页面达到400条微博),计算这20次的平均耗时,结果微信原生在这20次 触发上拉 -> 渲染完成
的平均耗时为876毫秒,最快的uni-app
是741毫秒,最慢的mpvue
是4493毫秒
大家初看这个数据,可能比较疑惑,别急,下方有详细说明
说明1:为何 mpvue/wepy 测试数据不完整?
mpvue
、wepy
诞生之初,微信小程序尚不支持自定义组件,无法进行组件化开发;mpvue
、wepy
为解决这个问题,将用户编写的Vue
组件,编译为WXML
中的模板(template),变相实现了组件化开发能力,提高代码复用性,这在当时的技术条件下是很棒的技术方案。
但如此方案,在页面复杂、组件较多的时,会大量增加页面 dom 节点数量,甚至超出微信的 dom 节点数限制。我们在 红米手机(Redmi 6 Pro)上实测,页面组件超过500个时,mpvue
、wepy
实现的仿微博App就会报出如下异常,并停止渲染,故这两个测试框架在组件较多时,测试数据不完整。这也就意味着,当页面组件太多时,无法使用这2个框架。
dom limit exceeded please check if there's any mistake you've made
Tips1:wepy
官网的CHANGELOG,提到测试版本添加了对小程序原生组件的支持,因为是测试版,官方在 issue 中也表示不推荐使用,暂未纳入评测
Tips2:wepy
在400条列表以内,为何性能高于微信原生框架,这个跟自定义组件管理开销及业务场景有关(wepy
编译为模板,不涉及组件创建及管理开销),后续对微博点赞,涉及组件数据传递时,微信原生框架的性能优势就提现出来了,详见下方测试数据。
说明2:为什么测试数据显示uni-app 会比微信原生框架的性能略好呢?
其实,在页面上有200条记录(200个组件)时,taro
性能数据也比微信原生框架更好。
微信原生框架耗时主要在setData
调用上,开发者若不单独优化,则每次都会传递大量数据;而 uni-app
、taro
都在调用setData
之前自动做diff
计算,每次仅传递变动的数据。
例如当前页面有20条数据,触发上拉加载时,会新加载20条数据,此时原生框架通过如下代码测试时,setData
会传输40条数据
data: {
listData: []
},
onReachBottom() { //上拉加载
let listData = this.data.listData;
listData.push(...Api.getNews());//新增数据
this.setData({
listData
}) //全量数据,发送数据到视图层
}
开发者使用微信原生框架,完全可以自己优化,精简传递数据,比如修改如下:
data: {
listData: []
},
onReachBottom() { //上拉加载
// 通过长度获取下一次渲染的索引
let index = this.data.listData.length;
let newData = {}; //新变更数据
Api.getNews().forEach((item) => {
newData['listData[' + (index++) + ']'] = item //赋值,索引递增
})
this.setData(newData) //增量数据,发送数据到视图层
}
经过如上优化修改后,再次测试,微信原生框架性能数据如下:
从测试结果可看出,经过开发者手动优化,微信原生框架可达到更好的性能,但 uni-app
、taro
相比微信原生,性能差距并不大。
这个结果,和web开发类似,web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。
也恰恰是因为Vue
、react
框架的优秀,性能好,开发体验好,所以原生js开发已经逐渐减少使用了。
复杂长列表加载下一页评测结论:微信原生开发手工优化
,uni-app
>微信原生开发未手工优化
,taro
> wepy
> mpvue
Tips:有人以为uni-app和mpvue是一样的,早期uni-app确实基于mpvue改造过,但后来因为性能和vue语法支持度问题,已经完全重新开发了。
1.2.2 点赞组件响应速度
长列表中的某个组件,比如点赞组件,点击时是否能及时的修改未赞和已赞状态?是这项测试的评测点。
测试方式:
- 选中某微博,点击“点赞”按钮,实现点赞状态状态切换(已赞高亮、未赞灰色),
- 点赞按钮
onclick
函数开头开始计时,setData
回调函数开头结束计时;
在红米手机(Redmi 6 Pro)上进行多次测试,求其平均值,结果如下:
说明:也就是在列表数量为400时,微信原生开发的应用,点赞按钮从点击到状态变化需要111毫秒。
测试结果数据说明:
- wepy/mpvue 测试数据不完整的原因同上,在组件较多时,页面已经不再渲染了
- 基于微信自定义组件实现组件开发的框架(uni-app/taro),组件数据通讯性能接近于微信原生框架,远高于基于
template
实现组件开发的框架(wepy/mpvue)性能
组件数据更新性能测评:微信原生开发
,uni-app
,taro
> wepy
> mpvue
综上,本性能测试做了2个测试,长列表加载和组件状态更新,综合2个实验,结论如下:
微信原生开发手工优化
,uni-app
>微信原生开发未手工优化
,taro
> wepy
> mpvue
2.开发者
在满足用户业务需求的前提下,我们谈谈开发者的需求,从如下几个维度比较:
- 平缓的学习曲线:简单易学,最好能复用现有技术栈,丰富的学习资料
- 高效的开发体验:现代前端开发流程、工程化支持
- 高效的社区支持:遇到问题,可很快的寻求到帮助
- 活跃的开发迭代:框架处于积极更新升级状态,无需担心停更
2.1 平缓的学习曲线
2.1.1 DSL语法支持
选择开发团队熟悉的、能快速上手的DSL,是团队框架选型的基本点。
首先微信原生的开发语法,既像React
,又像Vue
,有点不伦不类,对于开发者来说,等于又要学习一套新的语法,大幅提升了学习成本,这一直被大家所诟病。
其它开发框架基本都遵循React、Vue(类Vue)语法,其主要目的:复用工程师的现有技术栈,降低学习成本。此时,框架对于原框架(React/Vue)语法的支持度就是一个重要的衡量标准,如果支持度较低、和原框架语法差异较大,则开发者无异于要学习一门新的框架,成本太高。
实际开发中发现,各个开发框架,都没有完全实现Vue
、React
在web上的所有语法:
wepy
开发风格接近于 Vue.js
,属于类Vue
实现,相对微信原生开发算前进了一大步,但相比完整Vue
语法还有较大差距,开发时需要单独学习它的规则;
mpvue
、uni-app
框架基于 Vue.js
核心,通过修改 Vue.js
的 runtime
和 compiler
,实现了在小程序端的运行。mpvue
支持的Vue语法略少,uni-app
则基本支持绝大多数vue语法,如filter
、复杂 JavaScript
表达式等;
taro
对于 JSX
的语法支持度,也达到了绝大多数都支持的完善程度。
DSL语法支持评测:taro
,uni-app
> mpvue
> wepy
> 微信原生
2.1.2 学习资料完善度
官方文档、问题搜索、示例demo的完备度方面:
- 微信原生:文档丰富,API搜索准确,官方有示例demo,支持官网上调起微信开发者工具,预览运行效果 详见
- wepy:文档只有2页,没有搜索,组件API等文档都直接看微信的文档。没有提供示例demo,很多配置需要靠猜。详见
- mpvue:文档较少,但其概念不复杂,组件API等文档都直接看微信的文档,学习难度低。问题搜索效果一般。没有提供示例demo。详见
- taro:基础文档完整,具体使用问题资源较少,问题搜索效果一般,示例demo只包含基础功能,仅发布了微信一端。详见
- uni-app:基础文档和各种使用专题内容丰富,问题搜索效果较好,示例demo功能完备,并发布为7端上线。详见
教学课程方面:
学习资料完善度评测:微信原生 > uni-app
> mpvue
, taro
> wepy
2.2 现代前端开发体验
开发体验层面,处于明显劣势的是微信原生开发,主要差距在于:
- 框架开发提供了精简的代码组织(微信原生开发,一个Page由4个文件构成,写个代码要开的标签卡太多)
- 框架开发提供了更强大的组件化能力
- 框架开发提供了应用状态管理(类Vuex/Redux/Mobx等)
- 框架开发能灵活支持各种 Sass 等 预处理器
- 框架开发可提供完整的 ES Next 语法支持
- 框架开发方便自定义构建策略
其它小程序开发框架均支持cli
模式,可以在主流前端工具中开发,且基本都带有d.ts的语法提示库。
由于mpvue
、uni-app
、taro
直接支持vue
、react
语法,配套的ide工具链较丰富,着色、校验、格式化完善;wepy
要弱一些,有部分三方维护的vscode插件。
好的开发工具,绝对可以大幅提升开发体验,这个维度上,明显高出一截的框架是uni-app
,其出品公司同时也是HBuilder的出品公司,DCloud.io。HBuilder是四大主流前端开发工具(可对比百度指数),其为uni-app
做了很多优化,故uni-app
的开发效率、易用性非其他框架可及。
开发体验维度,对比结果:uni-app
> taro
,mpvue
> wepy
> 微信原生
这里可以输出一个结论:如果你需要工程化能力,那就直接忘了微信原生开发吧。
2.3 高效的社区支持
学习、开发难免遇到问题,官方技术支持和社区活跃度很重要。
本次评测demo开发期间,我们的同学(同时掌握vue和react),在学习研究各个多端框架时,切实感受到由于语法、学习资料、社区的差异带来的学习门槛,吐出了很多槽。
综合评估,本项评测结论:微信原生
, uni-app
> taro
> mpvue
> wepy
2.4 活跃的开发迭代
开发者必须关心一个问题:该项目是否有人长期维护?
这个问题可以通过github commits 频次、产品更新日志(changelog)、百度搜索指数等指标来衡量和对比。
github commits 频次
我们采集2019年4月份(时间为4.1 ~ 4.30),每个项目在github上的master分支有commit的天数,结果如下:
Tips:
- 微信原生是闭源的,看不到 commits 数量,但保持每月至少一次的更新节奏,详见
wepy
的master分支无commit,最新的2.0.x分支在4月份也仅1天有commit记录
从 commit 的记录来看,taro
、uni-app
处于更新比较活跃的状态,wepy
、mpvue
则相对疲软,呈现无人维护之态。
产品更新日志
通过浏览产品更新日志,可确认产品是否在积极迭代、增加新功能、修复用户bug。
我们分别查看各框架官方链接的更新日志(CHANGELOG),下方是链接地址:
通过产品更新日志对比,微信原生、taro
、uni-app
三者更新频繁,bug修复、新功能补充都处于比较紧凑的状态;而mpvue
、wepy
则已有长时间没有版本发布,开发者选型需谨慎。
另外提供下各框架的开发团队情况,这也是决定项目生命力的重要参考。
wepy
:腾讯一名员工的兼职作品
mpvue
:美团酒旅事业部前端团队出品
taro
:京东凹凸实验室
uni-app
:DCloud,B轮创业公司,投入几十人、数千万打造uni-app生态。
2.5 多端复用
除了微信小程序,支付宝、百度、字节跳动、QQ等小程序陆续上线,开发者迟早要面对多端开发。
但每个跨端框架能否真的像官网宣传的那样,实现开发一次,发布到所有小程序平台?甚至和H5平台复用代码?
我们用事实说话,依然使用上述仿微博App,依次发布到各平台,验证每个框架在各端的兼容性,结果如下:
测试结果说明:
- ⭕ 表示支持且功能正常,❌ 表示不支持,其它则表示支持但存在部分bug或兼容问题
通过这个简单的例子可以看出,跨端支持度测评结论: uni-app
,taro
> mpvue
> 原生微信小程序
、wepy
但是仅有上面的测试还不全面,实际业务要比这个测试例复杂很多。但我们没法开发很多复杂业务做评测,所以还需要再对照各家文档补充一些信息。
由于每个框架的文档中都描述了各种组件和API的跨端支持程度。我们过了几家的文档,发现各家基本是以微信小程序为基线,然后把各种组件和API在其他端实现了一遍:
taro
:H5端实现了大部分微信的APIuni-app
:组件、API、配置,大部分在各个端均已实现,个别API有说明在某些端不支持。可以看出uni-app是完整在H5端实现了一套微信模拟器
跨端框架,一方面要考虑框架提供的通用api跨端支持,同时还要考虑不同端的特色差异如何兼容。毕竟每个端都会有自己的特色,不可能完全一致。
taro
:提供了js环境变量判断和统一接口的多端文件,可以在组件、js、文件方面扩展多端,不支持其他环节的分平台处理。uni-app
:提供了条件编译模型,所有代码包括组件、js、css、配置json、文件、目录,均支持条件编译,可不受限的编写各端差异代码。
跨端框架,还涉及一个ui框架的跨端问题,评测结果如下:
最后补充跨端案例:
- mpvue:微信端案例丰富,未见其它端案例
- taro:微信端案例丰富,百度、支付宝、H5端亦有少量案例
- uni-app:多端案例丰富,官方示例已发布到7端(包括App端)
综合以上信息,本项的最终评测结论:uni-app
> taro
> mpvue
> 原生微信小程序
、wepy
这里可以输出一个结论,如果有多端发布需求,微信原生开发、wepy
这两种方式可以直接排除了。
结语
真实客观的永远是实验和数据,而不是结论。不同需求的开发者,可以根据上述实验数据,自行得出自己的选型结论。
但作为一篇完整的评测,我们也必须提供一份总结,虽然它可能加入了我们的主观感受:
如果你只开发微信小程序,不做多端,那么使用uni-app
、taro
是更优的选择,他们相当于web世界的vue和react,有了这些工具,不再需要使用原生wxml开发。
- 如果坚持微信原生开发,需要注意手动写优化代码来控制
setdata
,并且注意其工程化能力非常弱 - 如果你是
react
系,那就用taro
- 如果是
vue
系,那就用uni-app
,uni-app
在性能、周边生态和开发效率上更有优势
如果你开发多端,uni-app
和taro
都可以,可根据自己熟悉的技术栈选择,相对而言uni-app
的多端成熟度更高一些。
如有读者认为本文中任何评测失真,欢迎在这里报 issuse。
收起阅读 »uniapp 数组更新时页面不进行数据更新问题解决
问题描述:
今天在学习时发现在uni.request请求时,接收到数据被赋值给了data里面得数组myfriends里面,再由v-for进行数据渲染。
编写逻辑:
data(){
return{
myfriends:[]
}
}
方法:
initialfriendlist:function(id){
var data={
"userid":id
};
uni.request({
url: common.userPath + 'chat/getinit/',
data: data,
method: 'post',
header: {
'content-type': 'application/x-www-form-urlencoded',
},
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
this.myfriends.push(res.data.msg[i]);
console.log(this.myfriends);
}
console.log(this.myfriends);
}
}
})
}
数据渲染:
<view class="friendlist">
<div class="show-small-info">好友列表</div>
<div v-for="item in myfriends" :key="item.frienduserid" class="friend-item">
<image v-bind:src="userPath+'static/userheadpic/'+item.headpicurl"></image>
<span class="username">{{item.username}}</span>
</div>
</view>
结果:
在请求成功后打印出myfriends的类型是undefined,
在后面也能打印出this.myfriends也是添加数据成功后的数组。也就是数组内容更新了,但是页面没有同步更新。
处理方法:
在request请求前面增加变量代替this,可能是进入request请求体后this不再是我们所认识的this了,request内部的this指代了谁我不只是,但我应该敢肯定它没有对应着默认的那个vue实例。
修改后的方法:
initialfriendlist:function(id){
var that=this; //////**新增内容****
var data={
"userid":id
};
uni.request({
url: common.userPath + 'chat/getinit/',
data: data,
method: 'post',
header: {
'content-type': 'application/x-www-form-urlencoded',
},
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
that.myfriends.push(res.data.msg[i]); //////****新增内容***
}
console.log(this.myfriends);
}
}
})
}
果然,request请求体内的this并非我们所需的this,如果需要继续使用this来获取data内声明的变量,那么除了上面所说的that转存this之外,还可以通过bind方法将this绑定。
在success的回调函数后使用bind(this):
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
this.myfriends.push(res.data.msg[i]); //////****新增内容***
}
console.log(this.myfriends);
}
}.bind(this)
这也就可以使用this进行调用了。
问题描述:
今天在学习时发现在uni.request请求时,接收到数据被赋值给了data里面得数组myfriends里面,再由v-for进行数据渲染。
编写逻辑:
data(){
return{
myfriends:[]
}
}
方法:
initialfriendlist:function(id){
var data={
"userid":id
};
uni.request({
url: common.userPath + 'chat/getinit/',
data: data,
method: 'post',
header: {
'content-type': 'application/x-www-form-urlencoded',
},
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
this.myfriends.push(res.data.msg[i]);
console.log(this.myfriends);
}
console.log(this.myfriends);
}
}
})
}
数据渲染:
<view class="friendlist">
<div class="show-small-info">好友列表</div>
<div v-for="item in myfriends" :key="item.frienduserid" class="friend-item">
<image v-bind:src="userPath+'static/userheadpic/'+item.headpicurl"></image>
<span class="username">{{item.username}}</span>
</div>
</view>
结果:
在请求成功后打印出myfriends的类型是undefined,
在后面也能打印出this.myfriends也是添加数据成功后的数组。也就是数组内容更新了,但是页面没有同步更新。
处理方法:
在request请求前面增加变量代替this,可能是进入request请求体后this不再是我们所认识的this了,request内部的this指代了谁我不只是,但我应该敢肯定它没有对应着默认的那个vue实例。
修改后的方法:
initialfriendlist:function(id){
var that=this; //////**新增内容****
var data={
"userid":id
};
uni.request({
url: common.userPath + 'chat/getinit/',
data: data,
method: 'post',
header: {
'content-type': 'application/x-www-form-urlencoded',
},
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
that.myfriends.push(res.data.msg[i]); //////****新增内容***
}
console.log(this.myfriends);
}
}
})
}
果然,request请求体内的this并非我们所需的this,如果需要继续使用this来获取data内声明的变量,那么除了上面所说的that转存this之外,还可以通过bind方法将this绑定。
在success的回调函数后使用bind(this):
success:function(res){
if(res.data.code=="success"){
console.log(res.data);
var friendlist=uni.getStorageSync(".friendlist");
console.log(typeof(this.myfriends));
for(var i=0;i<res.data.msg.length;i++){
console.log(res.data.msg[i]);
this.myfriends.push(res.data.msg[i]); //////****新增内容***
}
console.log(this.myfriends);
}
}.bind(this)
这也就可以使用this进行调用了。
收起阅读 »深圳罗湖地区招聘前端工程师
熟练使用uni-app开发(小程序,h5,app等多端同步)
有想法的Q87364145
熟练使用uni-app开发(小程序,h5,app等多端同步)
有想法的Q87364145