第一次接触小程序大概是2017年初,那时候小程序刚刚上线,就被各种限制折磨的不行,单向绑定,不行,请求数限制,包大小限制,各种反人类,...总之感觉恶意满满。最近接了一个工程小程序项目,技术选型的时候又把旧东西捡起来看了一遍,重新熟悉了一下,感觉小程序还是很用心的,支持大部分es6语法,还发布了类似vue的mvvm框架wepy,还支持状态管理。粗略建了一个demo跑起来,虽然没有vue那么炫酷,但还是挺可以的,至少比原生的小程序语法好用很多。
然后就开始用Wepy搭建项目,写静态页面(因为公司的开发模式是先写静态页面,等后端同学出接口再绑定数据),虽然Wepy比原生容易上手,但是陷阱还是很多的,这里就不一一列举了……
当我们的静态页面写得差不多的时候,有一天晚上我在论坛上看到一条消息,说美团出了个小程序框架(不知道为什么,每次看到这个名字我只想到三个字:没朋友了,哈哈)。我随便看了下官方的介绍,发现有以下几个亮点:
与vue相同的开发体验,包括vuex
能够将H5代码转换为小程序目标代码
也就是说,我们不仅可以使用熟悉的 Vue 语法进行开发,还可以直接将你的 h5 页面编译成小程序。截止目前,项目开源不到 20 天,已获得近 7000 个 star,可见天下之患秦朝已久。
建了一个demo跑起来,感觉是开发界的良心之作。顺便把之前写的wepy的静态页面代码复制过来看一下,发现只需要稍微改一下,就能很平滑的从wepy切换到它(整个项目的切换时间大概半天)。说干就干,当天就切换到了它。到现在项目已经接近尾声了。整个开发过程真的很愉快。
Bug....我觉得我今天不是来打广告的,我是来找茬的。。
以下是我最近使用开发时需要注意的一些细节。(或者和 Vue 的区别)
1.个人感觉这个是最大的坑,除了缺少文件时报错,还有其他代码语法错误等等。控制台大部分时间都很安静(偶尔会报一个xxx is)。我经常遇到的是这个:this.xxx =5。有的情况下会报错,有的情况下就没反应了,具体情况我还没搞清楚。
有一次我
this.dataObject.map(() => { ...这里省略... })
结果,地图前面的“。”被意外地暴露了,实际的代码变成了
this.dataObjectmap(() => { ...这里省略... })
我搜索了很久,也没找到问题的原因
第二,这也是比较难的一个部分,在模板的数据绑定中,没有办法调用模板语法中的方法(或者说没有办法调用除模板语法之外的函数)。有人可能会说,我们可以使用属性,但如果我想给函数传递参数怎么办?看下面的代码:
<template> <view v-for="item in costList" > {{formatCost(item)}} view>template><script>export default { data(){ return{ costList:[] } }, methods: { formatCost(item){ return item.toFixed(2) }, getData(){ let arr = [3.255,4.1,5,15] this.costList = arr } }script>
此时,由于不支持函数,{{(item)}} 中的内容将被渲染为空字符串。此外,在这种情况下不能使用属性,除非你想写一个
对于这种情况,我的解决办法是在获取数据的时候进行修改,如上例所示,我们可以在方法中这样写
let arr = [3.255,4.1,5,15]// 遍历数组里面的元素,然后格式化一下,添加到 costList里去arr.map(item => { this.costList.push = this.formatCost(item) })
3.所有页面中的生命周期函数都会在小程序加载时执行一次,而不是每进入一个页面就执行一次。比如我有3个页面
...省略一些代码...creatted(){ console.log('pageA 的 created函数执行') }
...省略一些代码...creatted(){ console.log('pageB 的 created函数执行') }
...省略一些代码...creatted(){ console.log('pageC 的 created函数执行') }
然后,启动小程序,先不要进入这三个页面,假设我现在有一个页面,我们打开这个页面,会有下面的输出
pageA 的 created函数执行 pageB 的 created函数执行 pageC 的 created函数执行
这个其实很好解决,用 或者 或 代替就行。说到这几个函数,先提一下,这里的 和 是 vue() 的生命周期,而 和 是小程序的生命周期,官方的说明是:
除了 Vue 本身的生命周期,还兼容小程序的生命周期,这部分生命周期钩子来自于微信小程序的 Page,除非特殊情况,不建议使用小程序生命周期钩子。
不过官方的生命周期图也表明小程序的 和 执行时间早于 ,也就是说如果我们在 和 中请求数据,白屏时间会相对减少(这里的白屏指的是没有渲染数据的界面)。而且官方也没有说明为什么不建议使用小程序的生命周期,我们也尝试使用小程序的生命周期,并没有发现什么生命周期问题,所以还是倾向于优先使用小程序的生命周期,毕竟用户体验才是王道。
4.Vue.上挂载的属性必须经过计算后才能在模板语法中使用,在使用Vue时,我喜欢将图片的服务器路径存放在Vue的原型中:
import config from './config' Vue.prototype.$serverPath = config.serverPath
然后我们在页面中使用
:src="$serverPath + 'logo.png'" />
这样就可以避免每个页面都引入文件了,后面我们发布正式版的时候只需要修改这里的配置文件就可以了,写进去的话实际渲染的时候
<image src="undefinedlogo.png" >image>
如果要在每个页面中使用它,则只能在每个页面中导入它,或者在其中返回this.$。
5.在使用v-for循环的时候,如果要给当前项赋值一个索引,在vue中,为了省事,我一般喜欢这么做
v-for="item,index in list"
因为多打一对括号确实很烦人。但是下面不允许,必须老老实实的写,否则会报错。
v-for="(item,index) in list"
6. 单独设置每个页面的页眉信息。这个功能是提供的,但是文档不太详细。试了好几次才搞明白。
我们的入口文件main.js(延续Vue的名字,暂时就这么叫吧,不过我觉得应该叫配置文件)可以这样配置,官方文档大概是这么说的
这部分内容来自于app和页面文件,一般是main.js,你需要在你的入口文件里加上{:{}},这样我们才能识别它是一个配置,需要把它写成json文件。
import Vue from 'vue';import App from './app';const vueApp = new Vue(App); vueApp.$mount();// 这个是我们约定的额外的配置export default { // 这个字段下的数据会被填充到 app.json / page.json config: { pages: ['static/calendar/calendar', '^pages/list/list'], // Will be filled in webpack window: { // 顶部栏的统一配置 backgroundTextStyle: 'light', navigationBarBackgroundColor: '#455A73', navigationBarTitleText: '美团汽车票', navigationBarTextStyle: '#fff' } } };
同时此时我们会根据页面数据自动填充app.json中的字段,字段也可以自定义,约定以^符号开头的页面会放在数组最前面。
我们看到下面可以配置全局的顶部栏样式,但是如果我们想为每个页面指定一个样式怎么办?其实上面的方法只适合配置app.json中的内容,如果你想要为你的每个页面添加一个样式,你应该这样做:在页面所属的入口文件(main.js)中添加以下内容。例如,如果我想为/页面设置一个标题,我应该将它添加到/main.js中
export default { config: { navigationBarTitleText: '个人中心', } }
注意,这个和上面的全局配置的区别是,配置内容xt是的属性,而在全局配置中,它是的属性。
7.组件命名问题。有一次,我写了一个本地组件。为什么叫本地组件呢?因为我只在某个页面用到它。所以为了简化,我将这个组件命名为list.vue,然后在父组件中引用它:
<template> <list />template><script> import list from './components/list' export default { components: {list}, // 省略其他代码 }script>
组件能正常显示,样式没问题,看上去一切都很正常,但是组件内的逻辑却不会执行。另外,正如本文第一点提到的,没有报错,搞得我找了好久...排查之后,发现跟组件引入的名字有关,应该跟微信的关键字一样。
<template> <listA />template><script> import listA from './components/list' export default { components: {listA}, // 省略其他代码 }script>
这样就好了,踩过一些其他问题,命名的时候看到一些疑似关键词就有点心痛,这个应该是微信的问题,反正遇到就记录下来。
8.组件内部的内容在第一次加载时无法执行,隐藏后再显示才会显示,但每次进入都会显示页面,例如我们在一个组件中有如下代码
onLoad () { console.log('onLoad') },onShow () { console.log('onShow') },mounted () { console.log('mounted') },
当页面加载时,我们希望打印
onLoadonShow mounted
然后实际上只需打印出来
onLoadmounted
我已向官方反映此问题,但尚未收到回复。
到这里我们再来看一下小程序的页面跳转方法,小程序从一个页面(调用wx.)跳转到另一个页面时,并不会破坏原有的页面,而是转到后台执行原有页面里的代码。这也是为什么小程序的页面路径最多只能十级,因为你访问过的页面正常情况下都会保存在内存里,相当于vue里的keep-in,如果允许你跳转很多页面的话,很容易导致内存占用过大。
当然,我们也可以使用wx.wx.wx.来销毁页面。这三个方法都会调用页面的函数
9.放在-view中时,不会随着页面滚动。看似固定在某个位置,但在普通的view中是可以正常滚动的。这个问题其实是微信的问题,官方文档对这一点有说明。然而我遇到问题时没想到是微信官方的问题,百度、一搜,都没找到跟这个问题相关的内容。我甚至怀疑是自己的代码有问题,于是新建一个项目直接测试了官方的示例代码,效果是一样的。后来就准备放弃了,想其他的解决办法。没想到今天在官方文档最下方的小字--view组件的介绍里看到了。
提示:请勿在 -view 中使用、map、、组件
我进一步检查了组件文档,发现了类似的提示
提示:不要在 -view、-view、-view 中使用组件。
我之所以把这点包括进去,一是这个问题困扰了我好几天,一直在想其他的解决办法。二是这几天我在百度和谷歌上搜索过,发现好几个类似的问题,但是都没人回答,就记录在这里,希望以后遇到这个问题的朋友可以去搜索一下。
10.同一个子组件,在两个不同的地方引用时,会导致两个地方的样式加载失败,但如果只在一个地方引用就不会有问题。为什么把这个问题放在最后呢?因为这只是前几个版本的脚手架存在的问题,后面的版本应该不会再出现这个问题了。我也跟官方提过这个问题,官方的回答是使用新版本的脚手架重新生成项目,但是项目已经快完工了,此时再重新生成并复制代码感觉太累了,所以抱着不放弃的态度,直到最后找到了原因,因为早期版本的脚手架缺少这个插件,新版本的cli会自动添加这个插件。
还有一些问题官方已经明确指出,这里就不一一列举了,有兴趣的可以直接查看官方文档。