这大半年精力从后端转移到前端开发,一下要同时弄ios + 安卓 + 小程序的研发,很自然选择了混合App的方案,这样可以在最小学习成本下快速实现功能,并且在遇到一些可能的风险的时候,不至于因为不懂OC, Swift或者Android相关的东西导致无法处理。
前端技术框架早期Angular和Vue都接触过,Angular从1,到2,6都试过,技术架构变得太大,听说现在都到9了,以前的6又不兼容了,上生产的系统,还是不要依赖这种变化太大的技术了,不然后期重构和维护成本很大。反观vue,从早期1到现在的2, 和后来马上要出的3 语法变化都不大,况且,我还要兼顾小程序研发,而小程序的结构和vue很像,因此web部分我选用了Vue作为技术选型。
顺带推广一下自家小程序哈~~~
最近微信官方推Kbone了,打出的口号就是vue和小程序同时支持,本想多研究研究的,但是由于目前的项目的一些限制,我们并没有完全依赖Vue的所有特性和组件,中间有一些换为我们自己的实现方式,因此Kbone并不一定能很好支持这种方式的项目,就不去研究了。况且手动移植会更加可靠些,毕竟是两个独立项目,调试起来会更方便一些,而且小程序和App本身从界面设计和应用需求上就会有些不同。
以下举个简单例子:
App的界面:
小程序的界面:
这里的主要区别的原因在于Tab的支持, Vue里的Tab在任何地方都可以把Tab内容作为独立页面来看待,有完整的生命周期,但是小程序里,除非是App的根页面可以配置正儿八经的Tab外,其它子模块都只能靠自己绘制和Component实现,而小程序的Component的生命周期(lifetimes)仅支持attach和detach两种, 以及(pageLifetimes)仅支持show和hide两种,因此,若采用直接移植的话,对于页面生命周期的相关逻辑,改动较大。因此在小程序内若需要保持两个页面(首页、订单页)的逻辑独立性,我们在小程序内把订单入口作为按钮引入。
注意:这里这么改变的原因,仅仅在于我们是App先入为主,小程序是从App的逻辑迁移过来才这么设计的。如果一开始就是小程序先研发,则完全可以通过Component方式实现Tab的切换逻辑,并且Vue同样支持这样的结构。
由于是从后端Java研发转到的前端,不管是Vue还是小程序都是半路出家,因此里边可能有很多错误,欢迎指出。
由于我之前没有真正参与过完整的小程序开发,这也是我第一次自己上手迁移了一个模块后进行的总结,粗浅的了解了一些小程序的特性。因此可能小程序还有一些更好的特性我没有涉及到的,各位就根据自己的实际情况看看吧。
这个在Vue和小程序里都有同样的需求,App本身包括了很多功能模块,每个模块都是独立的Vue项目,可能都是不同的人负责研发的,为了维持App的UI设计和交互逻辑一致,我们把UI元素(如:按钮,对话框,Toast,表单元素等)独立封装为一个项目(类似一个Vant等独立的UI库,但是比它们简单,轻量地多)。组件封装我的看法如下:
比如一个按钮的封装,在逻辑设计上App和小程序保持一致:按钮的类型定义,是否可用定义,loading状态定义,点击事件定义等等……,保持一致的好处在于,具体应用在各个页面中时,按钮相关的所有逻辑输入、输出都是一样的。
1. Vue的封装
可参考的例子很多,这里主要参考这个Vue组件封装(以封装一个button组件为例)。
在使用的时候就是如
import XXXButton from "shared-components/XXXButton"
// 注册(可全局可局部)
Vue.use(XXXButton);
xxxxx
// ..........
在Vue里 @代表事件输出, :代表参数输入,对应到组件内是 $emit() 输出事件, props 传入参数即可。
2.小程序的封装
小程序的组件封装简单的多,在小程序的开发工具内,右键单击"新建Component"就行了~~~ 自动吧wxss, js, json, wxml四个文件都创建好了,按照正常的研发逻辑开发即可。
这里js文件承载逻辑,properties和Vue的props对应。事件输出则使用 this.triggerEvent("eventName", eventDetail) 。使用的时候,在页面的.json文件中的usingComponents注册,然后正常使用即可。和Vue的例子类比则伪代码如下
// XXXPage.json
{
"usingComponents": {
"XXXButton": "../../../components/XXXButton/XXXButton"
}
}
// XXXPage.wxml
xxxxx
// XXXPage.js
//....
data: {
loadingState: false
},
methods: {
onButtonClick(e){
// e.detail是输出参数
console.log("button clicked",e.detail);
}
}
我所涉及的页面较简单,几乎是无脑操作,步骤大致如下:
App的 , 输入输出替换: App直接CSS拷贝到wxss都行,尺寸换算如下: 这里仅对于我自己的实际项目前提: App统一采用rem单位,rootFont = 37.5像素, 设计人员使用2倍图。 假设一个元素在2倍图预览时候的长为 75px,宽度为 37.5px。 则对应app的长为 1rem, 宽为 0.5rem。对应小程序的长为75rpx,宽为 37.5rpx。 同理换算即可。 A.数据方面 B.生命周期 小程序页面和组件有不同的生命周期 主要把Vue中常用的onCreate, onMount, beforeDestroy 和 小程序的 onLoad, onShow, onHide, onUnload 等对应起来即可。 对于小程序的组件由于生命周期选项有限: onAttach 和 onDetach, 因此,需要若不能频繁创建,但是要控制组件显示和隐藏行为的时候,就得靠外部父组件的调用了。 C. 类的动态绑定 根据数据变化样式一般是通过class的动态变化做到的,这里语法稍有不同 D. 关于Vue中Computed的移植 这里Vue的Computed在 html模板中可以直接当做变量使用,在小程序里不行,但是可以借助 WXS 实现,官方文档见: WXS文档。 wxs的语法像Javascript但是和Javascript不太一样(不太能理解为啥要这样设计),这里举一个例子,把当前时间戳显示出来: 在小程序内 new Date()会被认为是语法错误,得用getTime()函数替代 在wxml中使用时候,如下: 同理正则表达式用法也不一样,这个各位用的时候再留意就行。 E. 关于Vue中watch的移植 虽然Computed在很多场景下已经可以代替watch做计算了,但是某些时候我仍然需要监听变量变化做一些事的时候,我还是得用watch来实现,这个在小程序内我就简单变为set方法就好了。。。貌似目前也没有遇到什么问题。 向下一个页面传递参数,这个在项目初期我们就定死了用url传值的方式了 参数传回上一个页面,这里我们Vue采用了自己封装的页面方法,并监听回调,小程序用了页面路由 以上就是我目前迁移业务所涉及的几个关键点了,以后遇到更多需求再更新咯。
2) 样式
3)逻辑
// 无需刷新页面的数据变化
// -> Vue
this.xxx = yyy;
// -> 小程序
this.data.xxx = yyy;
// 需要页面刷新的数据变化
// -> Vue
this.xxx = yyy;
// -> 小程序
this.setData({
xxx: yyy
});
// **** 注意,如果数据操作之间有先后要求的,setData应该在回调函数进行下一步操作,因为setData是异步的
this.setData({
xxx: yyy
}, () => {
nextOperate();
})
this.selectComponent('someSelector').show();
this.selectComponent('someSelector').hide();
// wsx
var now = function() {
// 错误
// return new Date();
// 正确
return getTime();
}
module.exports = {
now: now
}
// Vue
watch: {
someVar(val){
console.log("var change to", val);
}
}
// 小程序
setVal(val) {
this.data.val = val;
console.log("var change to", val);
}
4)页面参数传递// Vue的参数获取
let xxx = this.$route.query.xxx;
// 小程序的参数获取
onLoad(options) {
let xxx = options.xxx;
}
// Vue
let that = this;
MyCustomAppUtil.push({
url: 'xxxxx',
onResume: data => {
that.yyy = data.yyy
}
});
// 小程序
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.setData({
yyy:this.data.yyy
});