H5迁移成Uni-app小程序项目,So Easy ?

好久没有更新项目中遇到的问题了,主要是最近太忙了,忙着家里的事也忙着加班,国庆回来之后才有喘息的机会。

国庆节之前一直在忙一个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很简单,但是就我个人体验来说,如果有时间,真不如重新开发,因为迁移会遇到各种奇奇怪怪的问题,这些问题会严重压缩功能开发的时间。所以时间够,还是重新开发吧,还能熟悉以前逻辑不是...

相关推荐
周亚鑫11 分钟前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.28 分钟前
CSS3新增边框属性(五)
前端·css·css3
neter.asia44 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫1 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
嚣张农民1 小时前
JavaScript中Promise分别有哪些函数?
前端·javascript·面试
光影少年1 小时前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_1 小时前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu10830189111 小时前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾1 小时前
前端基础-html-注册界面
前端·算法·html
Dragon Wu1 小时前
前端 Canvas 绘画 总结
前端