好久没有更新项目中遇到的问题了,主要是最近太忙了,忙着家里的事也忙着加班,国庆回来之后才有喘息的机会。
国庆节之前一直在忙一个H5项目迁移成小程序的一个项目,因为之前也没有什么小程序开发经验,导致天天加班到深夜,现在项目忙完,觉得有必要总结一下项目中遇到的问题,将来朋友们有这种迁移项目的时候可以参考借鉴。
tips: 小程序项目框架为uni-app(uniapp.dcloud.net.cn/tutorial/)
一、 Typescript 配置问题
从H5迁移到uni-app
项目后,由于脚手架(我们是用的公司内部脚手架)不同,ts配置信息可能和原项目差距比较大,所以一定要对应修改ts配置,否则等待你的将是海量ts报错。下面我列举几个印象比较深的因为配置原因出现的ts报错:
1. noImplicitAny
default: true, strict模式下
在严格模式下默认为true
,当我们h5项目代码拷贝进去之后,启动会发现一堆Parameter 'xxx' implicitly has an 'any' type
这种报错,找到详细的报错代码查看,都是类似下面这种情况,
ts
function fn(s) {
// Parameter 's' implicitly has an 'any' type.
console.log(s);
}
ts中,函数参数没有给固定类型,会推断成any
类型,但是noImplicitAny:true
则当遇到这种类型推断为any
类型的情况就会有上面的报错提示信息。
所以,针对上述问题的解决方案为noImplicitAny:false
,瞬间,屏幕绿的大半,没有那么多红红的报错了。
2. strictNullChecks
default: true, strict模式下
在严格模式下默认为true
,当我们发现有类似'xxx' is possibly 'undefined'
,找到详细的报错代码查看,都是类似这种情况,
ts
const users = [
{ name: "Oby", age: 12 },
{ name: "Heera", age: 32 },
];
const loggedInUser = users.find((u) => u.name === loggedInUsername);
console.log(loggedInUser.age);
由于是老代码迁移,不想对老代码做大量更改,那么简单粗暴的方式就是直接 strictNullChecks:false
。瞬间,世界安静了。
3. ts项目中需要引入js
模块:allowJs: true,checkJs: false
这两个属性在ts/js
混合存在的项目中,并且js文件中存在export,需要在其他文件中引入js文件抛出的模块的,都需要配置上这两个属性。
ts
// @filename: card.js
export const defaultCardDeck = "Heart";
// @filename: index.ts
import { defaultCardDeck } from "./card";
console.log(defaultCardDeck);
4. 项目中自定义的类型或者写在global.d.ts中的类型一定也要一起复制过来
做完上述4点,基本上ts报错已经消灭了90%,剩下的就看是缺什么配置或者逐个进行类型修改就好。
对ts类型有什么疑惑,大家也可以看我以前总结的effective-typescript。
二、样式问题
上面ts配置改好后,项目起来了,样式又出现了大问题,很多样式缺失。详细看了下代码,主要两方面问题,一方面h5中用的rem单位,另一方面是h5中使用的unocss配置。
针对上述问题,采用了以下解决方案。
1. rem单位转换
uni-app官方文档中说,vue页面中是支持rem,但是跟字体大小需要通过page-meta配置。在代码迁移的时候有同事直接将rem单位全局替换成了px,考虑到后面需要根据不同屏幕自适应,决定采用postcss-px-to-viewport
这个插件将px
转换成vw
; 但是由于该项目是分包项目
中的一个子项目,那么为了只对当前项目进行css单位转换,所以在postcss-px-to-viewport
的配置参数中使用了include:/\/xxx\/xxx\//
,设置完之后,发现include属性不生效,查issue大家说这个属性最新代码支持,但是作者大大还没有发包,最终自己在内网发了个包,解决了这个问题。
2. unocss配置
这个打包配置和 h5配置形式一样,但是小程序里没有生效的原因是没有增加unocss-preset-weapp
这个预设,只需要在uno.config.js配置中增加这个预设就好。
js
import presetWeapp from 'unocss-preset-weapp';
export default defineConfig({
mode: 'global',
presets: [presetWeapp()],
separators: '__',
rules: []
});
至此,我们解决了大部分在迁移中样式遇到的问题,下面还有一些比较零散的小问题,也一并记录下:
3. 图片(img)宽度或者高度丢失
在h5中,图片的宽高有些人可能习惯只给一个,因为给定一个,就会根据图片的大小自适应出另外一个的大小;但是在小程序中不是,小程序中的图片宽高必须都给定,不然就会出现图片比例失调现象。
4. vue文件中组件样式的padding、margin会失效
html
<template>
<template-demo class="template-demo" />
</template>
<style>
.template-demo {
margin-top: 16px; // 不生效
}
</style>
上述写法不生效,想要margin生效,需要在组件外包裹一层
html
<template>
<div class="template-demo">
<template-demo />
</div>
</template>
<style>
.template-demo {
margin-top: 16px; // 生效了
}
</style>
5. 尽量不要全局写标签样式
这个问题的出现还是由于咱们的项目是从h5项目迁移过来的,咱们都知道h5标签和小程序标签是不一致的,但是uni-app帮咱们解决了这个对应关系,比如h5中的img
标签就会被转换成小程序中的view
。在转换标签的同时,也相当贴心的会给view
标签加上标签样式。比如img标签转换成view标签之后,<view class="_img"></view>
会多加一个class,当我们在全局中写了img { height: 100%, width: 100% }
之后,就会影响所有的img标签,但是不是所有img标签都有父级包裹,或者都存在宽高,这时候就会出现图片样式问题。所以尽量不要写标签样式,需要改的加自己的class,统一修改就好。
至此,样式问题基本解决完毕。
三、js问题
uni-app的js代码,h5端运行于浏览器中。非h5端(包含小程序和App),Android平台运行在v8引擎中,iOS平台运行在iOS自带的jscore引擎中,都没有运行在浏览器或webview里。
所以,非h5端不支持
window、document、navigator等浏览器的js API。
所以,但凡用到这些API的组件,方法都需要替换重写。基本上用uni-app官网提供的钩子和API能解决上述问题,就是替换比较耗费人力。
这里虽然官网上说对vue支持很好,但是还是遇到了一个问题,就是props传参时,数据中包含function类型属性时,该属性丢失。
html
<Demo :data="{ fn: () => { console.log('fn') } }">
data.fn // undefined
在社区里也有朋友遇到了类似的问题,并且解释了一下原因,说是小程序中这个props传递是通过JSON.parse(JSON.stringify(props))
这种形式传递的,所以function
被丢弃了。
针对这种情况,我的解决方案就是在组件内部重写函数逻辑。
四、小程序功能开发遇到的问题
一路升级打怪,总算是项目起来,样式正常,老功能看上去没有问题之后,终于能开发本次小程序新增加的功能「订阅」了。
1. 「订阅」的问题
大家看文档一定要看仔细,本来以为订阅就是调个API的事,但是万万没想到啊,坑可不少。
首先,大家一定要看那行「注意」:用户发生点击行为或者发起支付回调后,才可以调起订阅消息界面。
这是啥意思呢,意思就是想订阅一定得是用户发起点击行为或者进行支付回调之后,有朋友可能会说哎,你这不就把人家官网上的话又重复一遍么。确实,重复一遍的意思是让大家重视,按我自己的理解就是页面上要有按钮,用户触发之后调我的API才能成功。
html
<button @click="onClick">点我订阅</button>
function onClick () {
wx.requestSubscribeMessage({
xxx
})
}
这样,才能调起订阅面板(当然我们没有支付相关的操作,就没有试支付回调)。其余其他情况,休想调起订阅面板。
到这以为就功能实现,喜大普奔了么?nonono,现在高兴还为时尚早。
我们的订阅逻辑没有上述那么简单,是在用户提交一些资料之后,再弹订阅面板。比较合理的逻辑是用户都提交完,后端服务端返回成功之后再调起订阅面板,这时候问题来了,在「异步函数」之后,订阅面板也是死活出不来。
html
<button @click="onClick">点我订阅</button>
async function onClick () {
await axios.post({ url: 'xxx' });
wx.requestSubscribeMessage({
xxx
}) // 不能调起订阅
}
没办法,一顿找解决方案,也没能解决,最终还是把订阅放到了异步函数之前,不管接口的返回结果,都弹出让用户订阅的面板。
至此,订阅才算完事,一度让我感觉开发这么多年,怎么连API都调不明白了?差点抑郁...
2. 页面白屏
页面第一次打开正常,第二次白屏,控制台有Framework inner error (expect FLOW_APPLY_PROPERTY but get another)
报错,查了社区,看了看网友的回复,有说基础库问题的,有说其他的,想了下,如果是基础库的问题,那第一次页面打开的时候就会报问题。但现在稳定复现的场景是第一次能打开,第二次失败,所以基本可以确定不是基础库的问题,那就是其他情况。
按照网友提供的案例一一检查,没有发现类似的问题。
万般无奈,只能祭出万能二分法查找。页面引用了很多组件,一一注释,先把问题聚焦到组件,果然,发现只要注释了某个组件,该页面就能正常打开,多试了几次也都能正常打开。基本定位到问题就出在这个组件上了。然后发现组件里很简单,就是用for循环展示了一个组件,类似如下:
html
<template>
<demo v-for="(item, index) in list" :key="item.id" :data="item" ></demo>
</template>
看到上述代码,第一反应难道是key重了?然后对key进行了改造:
html
<template>
<demo v-for="(item, index) in list" :key="`${item.id}-${new Date().getTime()}`" :data="item" ></demo>
</template>
改造完,发现果然好了,页面多次都能正常打开,就奇怪,怎么key能重复呢?难道是有缓存?但是其他页面也有类似的循环遍历组件的情况怎么就没有问题呢?
看了看数据,确实数据有重复,在往上查找,天老爷,果然发现了问题(简单代码):
html
<template>
<demo v-for="(item, index) in list" :key="`${item.id}-${new Date().getTime()}`" :data="item" ></demo>
</template>
import {list} from './a';
// a.ts
// export const list = [xxx, xxx]
data() {
return {
list // 全局数据
}
},
mounted() {
list.push(xxx);
}
猜测,小程序中新开页面不会初始化环境;跟app中新开webview打开h5页面不一样,每次新开webview都是一套新环境,数据都会初始化。但是小程序中都是一套环境,所以list在mounted时,一直在加重复的值。所以导致页面有了上述报错并白屏。
解决方案:
html
<template>
<demo v-for="(item, index) in list" :key="`${item.id}-${new Date().getTime()}`" :data="item" ></demo>
</template>
import {list} from './a';
// a.ts
// export const list = [xxx, xxx]
data() {
return {
list: [...list] // 解决方案
}
},
mounted() {
list.push(xxx);
}
解决方案很简单,就每次list让它是个新数组就好了,不要成全局引用。
注意:真心不建议初始化的数据直接返回,这样很容易就变成全局数据,即便是正常浏览器js中也可能会出问题,所以大家在返回初始化数据时可以用函数返回,就避免这种直接返回数据当成全局数据导致报错这种情况。
3. 上传
上传没有什么可说的,但是上传的域名需要在小程序「开发管理」=>「开发设置」=>「服务器域名」中配置。不然本地可能上传成功,但是发版之后就不调上传接口了。除了uploadFile域名,还有其他域名也都需要提前配置。
以上为本次在小程序开发中,遇到的坑。大家如果也遇到类似问题,可以参考上述解决方案。
其实,都以为h5迁移uni-app很简单,但是就我个人体验来说,如果有时间,真不如重新开发,因为迁移会遇到各种奇奇怪怪的问题,这些问题会严重压缩功能开发的时间。所以时间够,还是重新开发吧,还能熟悉以前逻辑不是...