HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

突然断电后,在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

昨天做一个将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 原生子窗体开发指南

subnvue uni_app uniapp

此功能需要 HBuilderX 版本 1.9.10+, 不支持非自定义组件模式。

需求背景

在我们的开发中,经常会遇到各种层级覆盖和原生界面自定义的问题:

  • 覆盖原生导航栏、tabbar 的弹出层组件。比如侧滑菜单盖不住地图、视频、原生导航栏,比如 popup盖不住tabbar
  • 弹出层内部元素可滚动,
  • 在地图、视频等组件上的添加复杂覆盖组件:比如直播视频上覆盖滚动的聊天记录。

在小程序中只能用 cover-view 来解决。App中,开发者希望有更强的解决方案。
当然在App端使用nvue是不存在前端元素无法覆盖原生元素的层级问题的,但app-vue页面仍然需要面对复杂的层级问题:

  1. app-vue的cover-view 不支持嵌套、只能在 videomap 上使用、样式和控件少;
  2. plus.nativeObj.view 虽然更灵活,但易用性比较差、没有动画、不支持内部内容滚动。

既然uni-app已经支持 nvue 的原生渲染,我们何不做一个subNVue,来替代 cover-view,实现更强的功能?

顾名思义,subNVuevue 页面的子窗体,它不是全屏页面,就是用于解决 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) 类型,且级别最高,一旦设置 typenavigationBarpopuppositiondock 的值都会被忽略。
  • position 为原生子窗体的定位方式。
  • dock 表示原生子窗体的停靠位置,只有当 position 值为 dock 时才生效,如 top, bottom,right, left 等。
  • 在配置中可以使用 upx 单位,方便你进行响应式布局。

subNVue 子窗体书写

subNVue 子窗体引用的是 nvue 页面。所以只需要书写 nvue 页面。

需要注意的是,nvuevue 页面的开发注意事项。两者开发起来还是有一些区别。

相关参考

怎么在页面中使用 subNVue 子窗体

pages.json 中增加完配置,也写好了 subNVue 子窗体,接下来就是在 vue/nvue 页面中使用了。 在 vuenvue 页面中使用方式是一样的,这里以 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 页面之间,可以互相发送和传递消息,进而实现彼此之间的互相更新和表现协调。 在 vuenvue 中进行通信的方式一致,这里仍然以 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页面仍然需要面对复杂的层级问题:

  1. app-vue的cover-view 不支持嵌套、只能在 videomap 上使用、样式和控件少;
  2. plus.nativeObj.view 虽然更灵活,但易用性比较差、没有动画、不支持内部内容滚动。

既然uni-app已经支持 nvue 的原生渲染,我们何不做一个subNVue,来替代 cover-view,实现更强的功能?

顾名思义,subNVuevue 页面的子窗体,它不是全屏页面,就是用于解决 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) 类型,且级别最高,一旦设置 typenavigationBarpopuppositiondock 的值都会被忽略。
  • position 为原生子窗体的定位方式。
  • dock 表示原生子窗体的停靠位置,只有当 position 值为 dock 时才生效,如 top, bottom,right, left 等。
  • 在配置中可以使用 upx 单位,方便你进行响应式布局。

subNVue 子窗体书写

subNVue 子窗体引用的是 nvue 页面。所以只需要书写 nvue 页面。

需要注意的是,nvuevue 页面的开发注意事项。两者开发起来还是有一些区别。

相关参考

怎么在页面中使用 subNVue 子窗体

pages.json 中增加完配置,也写好了 subNVue 子窗体,接下来就是在 vue/nvue 页面中使用了。 在 vuenvue 页面中使用方式是一样的,这里以 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 页面之间,可以互相发送和传递消息,进而实现彼此之间的互相更新和表现协调。 在 vuenvue 中进行通信的方式一致,这里仍然以 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季

评测 对比 原生 微信 框架 taro wepy mpvue uni_app 微信原生

2017-1-9微信小程序诞生以来,历经2年多的迭代升级,已有数百万小程序上线,成为继Web、iOS、Android之后,第四大主流开发技术。

与之相随,小程序的开发生态也在蓬勃发展,从最初的微信原生开发,到wepympvuetarouni-app等框架依次出现,从刀耕火种演进为现代化开发,生态越来越丰富。

选择多了,问题也就来了,开发小程序,该用原生还是选择三方框架?

首先,微信原生开发的槽点大多集中如下:

  1. 原生开发对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程
  2. 微信定义了一个不伦不类的语法,不如正经学vue、react,学会了全端通用,而不是只为小程序。小程序的setData和类似template模式像是React和Vue的混合体,却丢了React的灵活和Vue的响应式。
  3. vue/react生态里有太多周边工具,可以提高开发效率,比如ide、校验器、三方库。。。
  4. 微信那个ide和专业编辑器相比实在不好用
  5. 没有正儿八经的状态管理

同时,开发者对三方框架,又总是有各种顾虑:

  1. 怕性能不如原生
  2. 怕有些功能框架实现不了,只能用原生
  3. 怕框架不稳定,跳到坑里
  4. 以及诸多三方框架,到底该用哪个

面对如此纠结的场景,不少热心开发者发布评测文章分享经验,但感觉众说纷纭,过期信息太多。缺少一份非常专业的、深度的,或者按如今流行的话来讲,“硬核的”评测报告。

做评测报告这件事,不同于泛泛经验分享,其实非常花费时间。它需要:

  • 你必须成为每一个框架的专业使用人员,而不是浅浅的了解一下这些框架
  • 真实的动手写多个平台的测试例,比较各个平台的功能、性能,了解他们的社区情况、技术服务情况
  • 你要有长期跟踪和更新报告的能力,避免半年后沦为过期信息

换言之:评测要想真,功夫得做深!

uni-app团队花费2个周时间完成本报告,并坚持每个季度更新一次本评测报告。目前更新时间为2019年5月。

本文从面向用户、面向开发者两大维度七大细项,对微信原生及主流的wepympvuetarouni-app开发框架进行横向对比,希望给开发者在小程序框架选型时提供一种参考思路。本文基于各框架官网可采集到的公开数据及真实测试数据,希望客观公正地评价各个框架的现状和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光来看待,如发现本文中有任何评测失真,欢迎在这里报 issuse

面向用户、面向开发者维度,具体包括:

  1. 用户:提供完整的业务实现,并保证高性能体验
  2. 开发者:平缓的学习曲线、现代开发体验(工程化)、高效的社区支持、活跃的开发迭代、多端复用

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 测试数据不完整?

mpvuewepy 诞生之初,微信小程序尚不支持自定义组件,无法进行组件化开发;mpvuewepy 为解决这个问题,将用户编写的Vue组件,编译为WXML中的模板(template),变相实现了组件化开发能力,提高代码复用性,这在当时的技术条件下是很棒的技术方案。

但如此方案,在页面复杂、组件较多的时,会大量增加页面 dom 节点数量,甚至超出微信的 dom 节点数限制。我们在 红米手机(Redmi 6 Pro)上实测,页面组件超过500个时,mpvuewepy 实现的仿微博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-apptaro 都在调用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-apptaro 相比微信原生,性能差距并不大。

这个结果,和web开发类似,web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。

也恰恰是因为Vuereact框架的优秀,性能好,开发体验好,所以原生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)语法的支持度就是一个重要的衡量标准,如果支持度较低、和原框架语法差异较大,则开发者无异于要学习一门新的框架,成本太高。

实际开发中发现,各个开发框架,都没有完全实现VueReact在web上的所有语法:

wepy开发风格接近于 Vue.js,属于类Vue实现,相对微信原生开发算前进了一大步,但相比完整Vue语法还有较大差距,开发时需要单独学习它的规则;

mpvueuni-app 框架基于 Vue.js 核心,通过修改 Vue.jsruntimecompiler,实现了在小程序端的运行。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的语法提示库。

由于mpvueuni-apptaro直接支持vuereact语法,配套的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 的记录来看,tarouni-app处于更新比较活跃的状态,wepympvue则相对疲软,呈现无人维护之态。

产品更新日志

通过浏览产品更新日志,可确认产品是否在积极迭代、增加新功能、修复用户bug。

我们分别查看各框架官方链接的更新日志(CHANGELOG),下方是链接地址:

通过产品更新日志对比,微信原生、tarouni-app 三者更新频繁,bug修复、新功能补充都处于比较紧凑的状态;而mpvuewepy则已有长时间没有版本发布,开发者选型需谨慎。

另外提供下各框架的开发团队情况,这也是决定项目生命力的重要参考。
wepy:腾讯一名员工的兼职作品
mpvue:美团酒旅事业部前端团队出品
taro:京东凹凸实验室
uni-app:DCloud,B轮创业公司,投入几十人、数千万打造uni-app生态。

2.5 多端复用

除了微信小程序,支付宝、百度、字节跳动、QQ等小程序陆续上线,开发者迟早要面对多端开发。

但每个跨端框架能否真的像官网宣传的那样,实现开发一次,发布到所有小程序平台?甚至和H5平台复用代码?

我们用事实说话,依然使用上述仿微博App,依次发布到各平台,验证每个框架在各端的兼容性,结果如下:

测试结果说明:

  • ⭕ 表示支持且功能正常,❌ 表示不支持,其它则表示支持但存在部分bug或兼容问题

通过这个简单的例子可以看出,跨端支持度测评结论: uni-app,taro > mpvue> 原生微信小程序wepy

但是仅有上面的测试还不全面,实际业务要比这个测试例复杂很多。但我们没法开发很多复杂业务做评测,所以还需要再对照各家文档补充一些信息。
由于每个框架的文档中都描述了各种组件和API的跨端支持程度。我们过了几家的文档,发现各家基本是以微信小程序为基线,然后把各种组件和API在其他端实现了一遍:

  • taro:H5端实现了大部分微信的API
  • uni-app:组件、API、配置,大部分在各个端均已实现,个别API有说明在某些端不支持。可以看出uni-app是完整在H5端实现了一套微信模拟器

跨端框架,一方面要考虑框架提供的通用api跨端支持,同时还要考虑不同端的特色差异如何兼容。毕竟每个端都会有自己的特色,不可能完全一致。

  • taro:提供了js环境变量判断和统一接口的多端文件,可以在组件、js、文件方面扩展多端,不支持其他环节的分平台处理。
  • uni-app:提供了条件编译模型,所有代码包括组件、js、css、配置json、文件、目录,均支持条件编译,可不受限的编写各端差异代码。

跨端框架,还涉及一个ui框架的跨端问题,评测结果如下:

  • taro:官方提供了taro ui,只支持微信小程序和H5两端,不支持App,详见
  • uni-app:官方提供了uni ui,可全端运行;uni-app还有一个插件市场,里面有很多三方ui组件,详见

最后补充跨端案例:

  • mpvue:微信端案例丰富,未见其它端案例
  • taro:微信端案例丰富,百度、支付宝、H5端亦有少量案例
  • uni-app:多端案例丰富,官方示例已发布到7端(包括App端)

综合以上信息,本项的最终评测结论:uni-app > taro > mpvue > 原生微信小程序wepy

这里可以输出一个结论,如果有多端发布需求,微信原生开发、wepy这两种方式可以直接排除了。

结语

真实客观的永远是实验和数据,而不是结论。不同需求的开发者,可以根据上述实验数据,自行得出自己的选型结论。

但作为一篇完整的评测,我们也必须提供一份总结,虽然它可能加入了我们的主观感受:

如果你只开发微信小程序,不做多端,那么使用uni-apptaro是更优的选择,他们相当于web世界的vue和react,有了这些工具,不再需要使用原生wxml开发。

  • 如果坚持微信原生开发,需要注意手动写优化代码来控制setdata,并且注意其工程化能力非常弱
  • 如果你是react系,那就用taro
  • 如果是vue系,那就用uni-appuni-app在性能、周边生态和开发效率上更有优势

如果你开发多端,uni-apptaro都可以,可根据自己熟悉的技术栈选择,相对而言uni-app的多端成熟度更高一些。

如有读者认为本文中任何评测失真,欢迎在这里报 issuse

继续阅读 »

2017-1-9微信小程序诞生以来,历经2年多的迭代升级,已有数百万小程序上线,成为继Web、iOS、Android之后,第四大主流开发技术。

与之相随,小程序的开发生态也在蓬勃发展,从最初的微信原生开发,到wepympvuetarouni-app等框架依次出现,从刀耕火种演进为现代化开发,生态越来越丰富。

选择多了,问题也就来了,开发小程序,该用原生还是选择三方框架?

首先,微信原生开发的槽点大多集中如下:

  1. 原生开发对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程
  2. 微信定义了一个不伦不类的语法,不如正经学vue、react,学会了全端通用,而不是只为小程序。小程序的setData和类似template模式像是React和Vue的混合体,却丢了React的灵活和Vue的响应式。
  3. vue/react生态里有太多周边工具,可以提高开发效率,比如ide、校验器、三方库。。。
  4. 微信那个ide和专业编辑器相比实在不好用
  5. 没有正儿八经的状态管理

同时,开发者对三方框架,又总是有各种顾虑:

  1. 怕性能不如原生
  2. 怕有些功能框架实现不了,只能用原生
  3. 怕框架不稳定,跳到坑里
  4. 以及诸多三方框架,到底该用哪个

面对如此纠结的场景,不少热心开发者发布评测文章分享经验,但感觉众说纷纭,过期信息太多。缺少一份非常专业的、深度的,或者按如今流行的话来讲,“硬核的”评测报告。

做评测报告这件事,不同于泛泛经验分享,其实非常花费时间。它需要:

  • 你必须成为每一个框架的专业使用人员,而不是浅浅的了解一下这些框架
  • 真实的动手写多个平台的测试例,比较各个平台的功能、性能,了解他们的社区情况、技术服务情况
  • 你要有长期跟踪和更新报告的能力,避免半年后沦为过期信息

换言之:评测要想真,功夫得做深!

uni-app团队花费2个周时间完成本报告,并坚持每个季度更新一次本评测报告。目前更新时间为2019年5月。

本文从面向用户、面向开发者两大维度七大细项,对微信原生及主流的wepympvuetarouni-app开发框架进行横向对比,希望给开发者在小程序框架选型时提供一种参考思路。本文基于各框架官网可采集到的公开数据及真实测试数据,希望客观公正地评价各个框架的现状和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光来看待,如发现本文中有任何评测失真,欢迎在这里报 issuse

面向用户、面向开发者维度,具体包括:

  1. 用户:提供完整的业务实现,并保证高性能体验
  2. 开发者:平缓的学习曲线、现代开发体验(工程化)、高效的社区支持、活跃的开发迭代、多端复用

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 测试数据不完整?

mpvuewepy 诞生之初,微信小程序尚不支持自定义组件,无法进行组件化开发;mpvuewepy 为解决这个问题,将用户编写的Vue组件,编译为WXML中的模板(template),变相实现了组件化开发能力,提高代码复用性,这在当时的技术条件下是很棒的技术方案。

但如此方案,在页面复杂、组件较多的时,会大量增加页面 dom 节点数量,甚至超出微信的 dom 节点数限制。我们在 红米手机(Redmi 6 Pro)上实测,页面组件超过500个时,mpvuewepy 实现的仿微博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-apptaro 都在调用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-apptaro 相比微信原生,性能差距并不大。

这个结果,和web开发类似,web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。

也恰恰是因为Vuereact框架的优秀,性能好,开发体验好,所以原生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)语法的支持度就是一个重要的衡量标准,如果支持度较低、和原框架语法差异较大,则开发者无异于要学习一门新的框架,成本太高。

实际开发中发现,各个开发框架,都没有完全实现VueReact在web上的所有语法:

wepy开发风格接近于 Vue.js,属于类Vue实现,相对微信原生开发算前进了一大步,但相比完整Vue语法还有较大差距,开发时需要单独学习它的规则;

mpvueuni-app 框架基于 Vue.js 核心,通过修改 Vue.jsruntimecompiler,实现了在小程序端的运行。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的语法提示库。

由于mpvueuni-apptaro直接支持vuereact语法,配套的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 的记录来看,tarouni-app处于更新比较活跃的状态,wepympvue则相对疲软,呈现无人维护之态。

产品更新日志

通过浏览产品更新日志,可确认产品是否在积极迭代、增加新功能、修复用户bug。

我们分别查看各框架官方链接的更新日志(CHANGELOG),下方是链接地址:

通过产品更新日志对比,微信原生、tarouni-app 三者更新频繁,bug修复、新功能补充都处于比较紧凑的状态;而mpvuewepy则已有长时间没有版本发布,开发者选型需谨慎。

另外提供下各框架的开发团队情况,这也是决定项目生命力的重要参考。
wepy:腾讯一名员工的兼职作品
mpvue:美团酒旅事业部前端团队出品
taro:京东凹凸实验室
uni-app:DCloud,B轮创业公司,投入几十人、数千万打造uni-app生态。

2.5 多端复用

除了微信小程序,支付宝、百度、字节跳动、QQ等小程序陆续上线,开发者迟早要面对多端开发。

但每个跨端框架能否真的像官网宣传的那样,实现开发一次,发布到所有小程序平台?甚至和H5平台复用代码?

我们用事实说话,依然使用上述仿微博App,依次发布到各平台,验证每个框架在各端的兼容性,结果如下:

测试结果说明:

  • ⭕ 表示支持且功能正常,❌ 表示不支持,其它则表示支持但存在部分bug或兼容问题

通过这个简单的例子可以看出,跨端支持度测评结论: uni-app,taro > mpvue> 原生微信小程序wepy

但是仅有上面的测试还不全面,实际业务要比这个测试例复杂很多。但我们没法开发很多复杂业务做评测,所以还需要再对照各家文档补充一些信息。
由于每个框架的文档中都描述了各种组件和API的跨端支持程度。我们过了几家的文档,发现各家基本是以微信小程序为基线,然后把各种组件和API在其他端实现了一遍:

  • taro:H5端实现了大部分微信的API
  • uni-app:组件、API、配置,大部分在各个端均已实现,个别API有说明在某些端不支持。可以看出uni-app是完整在H5端实现了一套微信模拟器

跨端框架,一方面要考虑框架提供的通用api跨端支持,同时还要考虑不同端的特色差异如何兼容。毕竟每个端都会有自己的特色,不可能完全一致。

  • taro:提供了js环境变量判断和统一接口的多端文件,可以在组件、js、文件方面扩展多端,不支持其他环节的分平台处理。
  • uni-app:提供了条件编译模型,所有代码包括组件、js、css、配置json、文件、目录,均支持条件编译,可不受限的编写各端差异代码。

跨端框架,还涉及一个ui框架的跨端问题,评测结果如下:

  • taro:官方提供了taro ui,只支持微信小程序和H5两端,不支持App,详见
  • uni-app:官方提供了uni ui,可全端运行;uni-app还有一个插件市场,里面有很多三方ui组件,详见

最后补充跨端案例:

  • mpvue:微信端案例丰富,未见其它端案例
  • taro:微信端案例丰富,百度、支付宝、H5端亦有少量案例
  • uni-app:多端案例丰富,官方示例已发布到7端(包括App端)

综合以上信息,本项的最终评测结论:uni-app > taro > mpvue > 原生微信小程序wepy

这里可以输出一个结论,如果有多端发布需求,微信原生开发、wepy这两种方式可以直接排除了。

结语

真实客观的永远是实验和数据,而不是结论。不同需求的开发者,可以根据上述实验数据,自行得出自己的选型结论。

但作为一篇完整的评测,我们也必须提供一份总结,虽然它可能加入了我们的主观感受:

如果你只开发微信小程序,不做多端,那么使用uni-apptaro是更优的选择,他们相当于web世界的vue和react,有了这些工具,不再需要使用原生wxml开发。

  • 如果坚持微信原生开发,需要注意手动写优化代码来控制setdata,并且注意其工程化能力非常弱
  • 如果你是react系,那就用taro
  • 如果是vue系,那就用uni-appuni-app在性能、周边生态和开发效率上更有优势

如果你开发多端,uni-apptaro都可以,可根据自己熟悉的技术栈选择,相对而言uni-app的多端成熟度更高一些。

如有读者认为本文中任何评测失真,欢迎在这里报 issuse

收起阅读 »

uniapp 数组更新时页面不进行数据更新问题解决

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