阅读 7589

uni-app的开发经历(持续更新)

技术选型

公司需求要做app.微信小程序,支付宝小程序三端的东西,查阅了一些资料,最终选择用uni-app,以下是一些其他前辈技术选型的文章,各位可以学习一下。

uni-app官方文档:

uniapp.dcloud.io/README

掘金上选型文章:

juejin.im/post/5c4ec3…

juejin.im/post/5ca173…

对比和参照之后,再结合我司情况,技术总监选择了uni-app

我认为的坑?

其实我觉得自己的水平不能够去评价一款框架的好坏,只是简单的说一下开发体验,我认为坑大概分三种。

1:该框架确实没有的功能,存在不能实现的东西。

2:明显的bug,导致程序出现问题,影响开发进度和结果

3:在开发过程中,自己凭借直觉和经验在开发,结果和预期不同,阅读文档过后才发现和之前经验积累的写法不一样,会不由自主的说一句真坑,或者说在不同端有的不同写法,但自己没有做好兼容,就出现了不同端效果不一致,当再次阅读文档找到问题后也会来一句真坑。

总的来讲,这次Uni-app的开发之旅还是比较通畅的。

踩坑之旅

前言:我只说我遇见过的问题,和一些注意点,以及怎么去解决,不会讲uni-app怎么用,怎么用大家还是去阅读文档吧,文档上写的更清楚

像素单位

使用upx 而不是 px

修改内容(评论区大佬的订正): 1px = 2upx是不准确的,upx和rpx是响应式单位,以750px为基准宽度,根据设备屏幕宽度自动调整

1px = 2upx

引入vuex

常规的引入方式在uniapp中是不行的,需要在mian.js中,vue原型上挂载一次

路由

uni-app的路由全部配置在page.json这个文件中,问题就在于多人开发的时候,路由无法拆分,如果处理的不好,经常发生冲突。至于其中的一些配置项,就请见官方文档。

在页面中没有专门的 $route$router对象 仅能在页面的生命周期里面接受路由传参,详情见文档。

uniapp.dcloud.io/frame?id=路由…

路由传参方式
let url = `/pages/shopManagement/sonPage/billDetails?StoreID=${StoreID}`
复制代码

路由接参方式:

onLoad(route){
	this.getData.StoreID=route.StoreID
	this.getCurryInfo()
},
onLoad接收到一个参数对象
复制代码

DOM操作

如果你的项目仅是h5,那可以放心大胆的使用dom操作,但如果要在小程序和app跑,就不要做dom操作了,不生效。

不过ref还是可以用的,一样可以获取到这个节点,该干啥干啥。

生命周期

说到ref我就要提一下生命周期

具体的生命周期在文档中可以看详情

uniapp.dcloud.io/frame?id=应用…

大致上和vue的差不多,分成页面生命周期和应用生命周期,页面生命周期就是针对单页面的,应用生命周期就是针对整个小程序/app的,不过我提出在开发时的一些情况

在组件中,没有生命周期,对,你没看错!比如页面a引用了组件b 在组件b中,onLoad,onShow,onReady全部失效,不过用created和mounted是生效的,但是我在开发的时候还是没有用created和mounted,毕竟文档明确写到

所以我在组件中规避使用原vue的生命周期,另外,在上面说了ref,如果要在初始化使用ref要注意生命周期,在onload和show的钩子中,内部如果是同步操作是用不了的,拿不到$refs,我不知道怎么解释这个问题,在vue中很好解释,在created拿不到ref是因为dom还没有渲染出来,只有在mounted时dom渲染出来了才能拿到ref,但是uniapp中不是没得dom嘛。。。。。我也没深究过,如果要用,只能异步,可以加setTimeout 或者 放在某个请求后用,这个时候是可以拿到ref的

201912.2更新

最近几个月又开了个新项目,赶得很紧,不过有了第一个app的基础,开发起来很顺手。

同时也在重构后台系统,有时候div和view傻傻分不清老是整错。

好了,言归正传,今日我在交流群里看见有朋友问起了组件内生命周期的问题,我也讲了自己的疑惑,我就直接贴图给大家看看。

也就是说其实在非页面组件里面使用vue自带的生命周期是可行的,之前我没有尝试过,所以一直规避了这种用法,把很多东西都放在页面的生命周期里面搞。

关于请求

我们最开始的时候是自己简单的封装了一下发送的请求

export const HttpRequest_ =  {
	config: function(name) {
		var info = null;
		if (name) {
			var name2 = name.split("."); //字符分割
			if (name2.length > 1) {
				info = configdata[name2[0]][name2[1]] || null;
			} else {
				info = configdata[name] || null;
			}
			if (info == null) {
				let web_config = cache.get("web_config");
				if (web_config) {
					if (name2.length > 1) {
						info = web_config[name2[0]][name2[1]] || null;
					} else {
						info = web_config[name] || null;
					}
				}
			}
		}
		return info;
	},
	post: function(url, data, header) {
		header = header || "application/x-www-form-urlencoded";
		//url = this.config("APIHOST")+url;
		return new Promise((succ, error) => {
			showLoading_()
			uni.request({
				url: url,
				data: data,
				method: "POST",
				header: {
					"content-type": header
				},
				success: function(result) {
					hidLoading_()
					succ.call(self, result.data)
				},
				fail: function(e) {
					hidLoading_()
					error.call(self, e)
				}
			})
		}).then(res=>{
				console.log(res)
				return res
			},err=>{
				console.log('err:',err)
		})
	},
	get: function(url, data, header) {
		header = header || "application/x-www-form-urlencoded";
		//url = this.config("APIHOST")+url;
		return new Promise((succ, error) => {
			showLoading_()// 加载中
			uni.request({
				url: url,
				data: data,
				method: "GET",
				header: {
					"content-type": header
				},
				success: function(result) {
					hidLoading_() //关闭加载中
					succ.call(self, result.data)
				},
				fail: function(e) {
					hidLoading_() //关闭加载中
					error.call(self, e)
				}
			})
		}).then(res=>{
			console.log(res)
			return res
		},err=>{
			uni.showToast({
				duration:2000,
				title:'数据异常,请稍后再试',
				icon:'none'
			})
			console.log('err:',err)
		})
	}
}
复制代码

之前我以为在Uniapp中发送请求只能用他们请求方法,后来同事说也可以用其他的。

我们便引入其他的库。

fly.io/

导航栏

导航栏注意的一个问题就是不同端的不同展示形式,所以需要处理兼容问题。

导航栏,可以用自定义的,可以用框架提供的,也有一些插件,都还是可以用的,就是注意显示的区别,如果有多端需求,一定要在不同端跑一下看看效果,不然到时候会很烦。

在支付宝小程序中,原生的导航栏是取消不了,所以不能用自定义的插件,就需要在page.json中配置原生的导航栏

随便找了一个页面,我们的头部导航是条件编译的:

动态的class style

具体的文档在这里:

uniapp.dcloud.io/use

我一开始是没有注意到这一点,按照我的一些常规习惯写并且一直在用h5调试,没有任何问题,后来上真机和小程序开发工具之后才发现全部失效。

打开第三方的网址或app

在app端想要打开第三方的网址或者程序,一定要区分ios和安卓端。

首先,ios和安卓唤起第三方app的地址是不一样的。

不管是在调试还是打包,要唤起第三方程序,在ios端要配置白名单

三方登录

以微信登录位例:

在app端,uni-app集成的几个方法,可以很顺利的拿到unionId,openid等一些列信息

    uni.getProvider({//获取uniapp支持的第三方数据
      service:'oauth'
    }).then(data=>{
      var [err,res] = data
      var providers=res.provider//类型(微信,新浪,小米,qq)
      var flagIndex=providers.indexOf(provider)
      if(flagIndex>-1){
        return providers[flagIndex] /
      }
    }).then(res=>{
      return uni.login({//登陆接口(可以获取用户信息)
        provider:res,
        scopes:'auth_base',
        timeout:20000,
      })
    }).then(data=>{//返回一系列登陆信息
        var [err,res] = data
        if(res.errMsg==="login:ok"){
          self.authResult=res.authResult
          return res.authResult
        }
      }
    }).then(res=>{//获取用户的信息 头像,地址,等等等
      return uni.getUserInfo({
        provider:provider,
        timeout:20000,
        withCredentials:true
      })
    }).then(data=>{//得到一些列用户信息
      var [err,res] = data 
      console.log(res)
      if(res.errMsg==="getUserInfo:ok"){
        return res
      }
    })
复制代码

但是如果在小程序端,很多方法就失效了,因为小程序有一套自己的三方登录交互策略。

developers.weixin.qq.com/miniprogram…

还记得当时刚在app上测成功微信三方登陆后,领导过来看进度,问小程序怎么样,我给他放了个体验版,让他看看,他问我这个微信登录也可以吗?我拍拍胸脯说没得问题,随便登,结果。。。。。。。。。。。。。。。脸疼

    uni.login({//登陆接口
      provider:'weixin',
      scopes:'auth_base',
      timeout:20000,
    }).then(data=>{//返回一系列登陆信息
        let [err,res] = data
        if(res.code){
          let data ={//这个code很重要,需要拿到code向后台去换unionid等
            js_code: res.code
          }
      return this.$Request.get(this.$store.state.getopenidUrl,data)
    }else{
      setTimeout(()=>{
        this.$api.msg('数据异常')
      },500)
      uni.switchTab({
        url:'/pages/index/index'
      })
    }
    }).then(res=>{
        let res_ = JSON.parse(res.Data)
        if('unionid' in res_){
          this.getIsBindData.openid=res_.unionid
          this.getDataWX.openid = res_.unionid
          this.getDataWX.unionid = res_.openid
        }else{
          this.getIsBindData.openid=res_.openid
          this.getDataWX.openid = res_.openid
          this.getDataWX.unionid = res_.openid
        }
        return this.WXuserInfo
    })
复制代码

其他的三方登录我没有试过,但是一定要注意各端之间的差异性

另外,支付宝三方登录uni-app没有集成,要是自己想做,就用原生来写,理论上是可以做的。由于我们团队没有会原生的,我们试过用webview做支付宝的三方登录,最后还是卡在了授权这一块,不得而终,遂阉了该需求。

2019 8.20 更新:

在插件市场已经有了安卓端和ios端授权登录的插件(是付费插件)详情:

ext.dcloud.net.cn/plugin?id=6…

canvas

写成组件在小程序上失效,仅能在index页面上使用。在h5生效,封装成组件也可以,渲染效果也不是太好,app端没有试过,因为后来看效果不好就暂时搁置这个需求了。

评论区朋友的订正: canvas在组件中使用时,记得传递第二个参数组件实例对象:uni.createCanvasContext(canvasId, this)

我试过了,封装成组件的情况下,传入this,在小程序是生效的.之前是没有传入的

注意sdk的配置

我之前从未写过app和小程序,对一些东西不够敏感

在我调试地图,导航等功能的时候用的很顺畅,后来打包就失效了,就是因为没配sdk,原来在我们调试的时候用的sdk,key等是Uniapp的。

另外需要用到一些权限的时候记得也要去manifest.json中去配置,总的来讲,图形化界面配置还是比较友好,源码配置也没有那么复杂,基本上都能查到

原生子窗体(nvue)

原生子窗体:不支持upx ,不支持百分比,不支持background-image 如果放image的话必须要宽高,这都是weex的标准,所以在用东西之前还是要仔细读一下文档,不要就是什么都不读,凭着感觉在写。

2019 8.20 更新:

评论区朋友补充: nvue要支持upx,需要使用uni-app编译模式,而不是weex编译模式

开屏引导

uni-app没得开屏这个配置项,只能用一些策略来做。

ext.dcloud.net.cn/plugin?id=1…

实质上是是用swiper组件+本地缓存做的模拟开屏引导,注意的是,如果产品一开始就定位了要做引导页,那就考虑好index的怎么写,我们是后期才打算加的,如果要把index改掉成本有点大,所以用了另一种策略,但如果在性能不好的手机上会出现尴尬的事情,就不细说怎么尴尬了,如果用上面的策略,再尴尬也不过是白屏,在上面的dome中,index仅是一个中转页面,什么都没有写。所以个人建议还是用以上的策略。

路径别名

至今我没有我没有找到如何配置路径别名的文件或者方法。

在uniapp中 @ 是表示根路径,不过整个文件结构还是很清晰的,没有那么多的配置文件,整个根文件很像常规的src目录,用@基本上也很舒服。

插件市场和生态

总的来讲,Uniapp的插件市场还是不错的,大多数能用到的插件都可以找到,找不到的也可以从个别相似的插件中找到灵感,自己再魔改一下,论坛也还行,基本上自己遇到的问题都能找到答案,但是也有找不到的或者有人提出却无人回答的,官方qq群也还是比较热闹,有些东西自己没遇见的看看别人的问题和解决方案也算是一种成长,自己遇见过的给别人解答,换来一声谢谢也是很开心的。

压缩图片

2019 8.16更新

文档:

uniapp.dcloud.io/api/media/i…

我不知道为什么 uni.compressImage(OBJECT) 这个接口在app端失效微信小程序生效,报错信息也很简单,就是说压缩失败,没有其他的提示,我在官方群问了一圈没人给我回答,看了下社区也没有太有价值的回答。

后来自己慢慢琢磨了一下,也参考了这位老哥的文章(文章中也包含了转换base64的方法),简单的封装了个函数。

参考文章:

github.com/SilurianYan…

/*
src:图片的本地路径
quality:压缩范围 0-100
*/

 function zipImage(src,quality=30){
	// #ifdef APP-PLUS app
	let index = src.lastIndexOf(".")
	let imgDirname = src.substring(0,index)//图片的原始地址
	let imgName = new Date().getTime();//压缩后的文件
	let imgType  = src.substring(index+1,src.length);//图片的类型
	return new Promise((resolve,rej)=>{
		plus.zip.compressImage({
			src,
			dst: imgDirname+imgName+'.'+imgType,
			quality,
		}, res => {
			resolve(res.target)
		}, err => {
			console.log(err);
			rej(err)
		});
	})
	// #endif
	// #ifdef MP-WEIXIN   微信小程序
	return new Promise((resolve,rej)=>{
		uni.compressImage({
		  src,
		  success:(res)=>{
		  	console.log(res,1)
			let path = res.tempFilePath
			resolve(path)
			// this.imgList.push(path)
		  	// this.imgList.push
		  },
		  fail:(e)=>{
			uni.showToast({
				title:'上传失败,请重新上传',
				duration:2000
			})
		  },
		  complete:(val)=>{
		  	console.log(val,3)
		  }
		})
	})
	// #endif
	
}
复制代码

调用的时候,直接会吐出来压缩后的图片路径

uni.chooseImage({
	success:(res)=>{
	    res.tempFiles.forEach(async (it)=>{
			let localPath =it.path	
			localPath = await this.$api.zipImage(localPath) //await一下就可以接住了
			this.imageList.push({
				value:localPath
			})	
							
		})
	}
});					
复制代码

最后还是一句话

注意各端的差异性,很多东西,h5对着的,上真机就错了,真机好着的,换小程序就错了,不同小程序之间还有差异,总之就是考虑好不同的情况,重点是仔细阅读文档。

虽然可能一些原生可以实现的功能uniapp实现不了,不过整体开发下来还是比较愉快,很多的坑还是因为多端不兼容,除了写起来麻烦一点,基本上都还是有可以解决的策略。

新的项目也即将开始,这一次是h5+小程序,还是打算用uniapp。

最后也希望uniapp越做越好,各方面越来越完善,为我这种搬砖码农增加一份生存的筹码。

这是开发出来的微信小程序,有兴趣的朋友可以扫码从注册到登录试一下

关注下面的标签,发现更多相似文章
评论