uniapp 开发 h5 与微信小程序注意事项小结

本篇文章是使用 uniapp 开发 h5 与微信小程序的一些踩坑总结与记录(至于 app 的跨端开发,那还是使用 flutter 吧)。

开发

使用 vue3

新建项目

在新建项目时勾选 vue3:

在首次运行时,HBuilder 会自动安装相关插件:

props 接收路由传参

使用 vue3 创建的项目,路由传参可以通过 props 接收,这功能在 Vue Router 的官方文档也有提及。

样式

全局样式

在 uni.scss 文件定义或引入的 sass 变量,全局可以直接使用而无需 @import 引入。在 App.vue 页面的 <style> 标签内可以定义全局样式:

vue 复制代码
<style>
  @import url('@/common/styles/base.css');
</style>
<style lang="scss">
  /*每个页面公共css */
  @import 'uview-ui/index.scss';
  page {
    /* 各个页面的根元素样式 */
  }
</style>
  • 引入外部 css 文件可以使用 @import url@import, 它们的区别在于 @import url 可以引入网络文件,而 @import 只能引入本地文件;
  • 在 sass 中,只能使用 @import 引入外部 scss 文件,且后缀 .scss 可以省略。如果使用了 @import url 或者 @import 引入的是 css 文件,则不会导入 sass 文件而只是作为普通的 css 语句原封不动地照搬到编译后生成的 css 文件中;
  • 使用 sass 时,HBuilder 会自动安装相关插件,然后在 <style lang="scss"> 内直接编写 sass 代码即可;
  • 在 uniapp 中路径别名 @ 无需自己配置,可以直接使用;
  • 使用 page 定义样式相当于给每个页面的根元素定义了样式:
vue 复制代码
<style lang="scss">
  page {
    font-size: 28rpx;
    color: red;
  }
</style>

在小程序端每个页面的根元素就是 <page>

在 h5 端会生成自定义元素 <uni-page-body>

关于 scoped

在 h5 端,写在页面或组件的样式,默认就是只在当前页面或组件生效,不需要写 scoped;但是在微信小程序端则需要明确写上 scoped 才可以为 css 指定作用域。

适配刘海屏(异型屏)

微信小程序端,有些页面头部的内容在 iphone6 等非刘海屏显示正常,在 iphone13 等异型屏会有问题。uniapp 中通过 css 变量 --status-bar-height 来设置状态栏高度,在小程序里固定为 25px, 这是会有问题的。可以通过 uni.getSystemInfo 获取真实的 statusBarHeight,然后通过在每个页面文件的第一个节点设置 <page-meta :page-style="pageStyle"></page-meta>

pageStyle 中设置获取到的真实的状态栏高度等 css 变量:

javascript 复制代码
this.pageStyle =`--qy-status-bar-height: ${qyStatusBarHeight}px;`

在使用时,为了让 h5 端也可以找到 --qy-status-bar-height,别忘了在样式文件中进行设置:

css 复制代码
:root {
  --qy-status-bar-height: 0px;
  // ...
}

屏幕底部的安全区域也可以通过 uni.getSystemInfo 返回的结果中的 safeAreaInsets.bottom 得到:

内置组件 input 的 placeholder 样式

通过 placeholder-class 属性设置 placeholder 的样式类时,为了适配小程序端,需要将样式类定义在不带 scoped<style> 里。

自定义组件

在微信小程序端,自定义组件在渲染时会比 h5 端多一级节点,比如有个 <ComCascader> 组件:

在微信小程序中就会在外层多一层 <com-cascader>

如果遇到 h5 端和小程序端样式效果不一致的情况,可以看看是不是这个问题(可以通过 virtualHost 配置)。

动态绑定 style

微信小程序动态绑定 style,值得是数组,而不能直接是对象格式。

display

微信小程序中,自定义组件的默认 displayinline,所以在添加 margin 等属性时,要改为诸如 block 方可生效。

gap

在 iPhone 真机上,flex 布局中,使用 gap 属性可能无效。

::v-deep

微信小程序的 ::v-deep 要生效,需要在 methods 同级添加如下代码:

javascript 复制代码
options: {
  styleIsolation: 'shared'
},

路由

我使用的是 uni-simple-router 这个插件,其获取 router 对象是通过 this.$Router,获取 route 是通过 this.$Route(注意大小写)。

图片

微信小程序不支持本地的背景图片,而是支持 base64 格式的图片或网络图片。如果你使用了本地图片,当图片小于 40 kb 时,uniapp 在编译时会自动转成 base64:

本地背景图片的引用路径建议使用 ~@ 开头的绝对路径:background: url('~@/static/step.png') no-repeat;

图标

我通常在 iconfont 上找到合适的图标然后通过 symbol 引用的方式在项目中使用。这种用法使用的是 svg,但是微信小程序上只支持网络地址的 svg。所以我采取的方案是在选择图标时进行区分。

单色图标

对于如下所示的单色图标:

添加到一个项目,统一使用字体图标的 font-class 的方式引用,并对应封装了 <QyIcon>

vue 复制代码
<!-- 纯色图标 -->
<template>
    <view class="qy-icon-box">
        <i class="iconfont" :class="iconfontClass" :style="[iconStyle]"></i>
    </view>
</template>

<script>
import iconMixin from '@/mixins/iconMixin.js'
export default {
	name: 'QyIcon',
	mixins: [iconMixin]
}
</script>

<style lang="scss">
.qy-icon-box {
	display: inline-block;
}
</style>

iconMixin.js 文件如下,主要是对样式的处理:

javascript 复制代码
export default {
  props: {
    // icon 的名称
    flag: {
      type: String,
      required: true
    },
    classArr: {
      type: Array,
      required: false
    },
    iconStyle: {
      type: Object,
      required: false
    }
  },
  computed: {
    iconfontClass() {
      if (this.classArr) {
        return [...this.classArr, this.flag]
      } else {
        return [this.flag]
      }
    }
  }
}

使用时,如果传递的类名是定义在当前 vue 文件的,则需要添加 ::v-deep 方可生效:

vue 复制代码
<template>
  <QyIcon
    flag="icon-hongbao"
    :classArr="['icon-common']"
    :iconStyle="{ marginBottom: '4rpx' }"
  />
</template>

<style lang="scss">
  /* 类名要生效,需要添加 ::v-deep */
  ::v-deep .icon-common {
    color: #999;
    fontsize: 140rpx;
  }
</style>

在设置字体图标项目时,字体格式可以只勾选 WOFF 和 TTF:

微信小程序官方文档的建议也就是选择这两个:

多色图标

对于类似下图所示的多色图标:

则添加到另一个项目,在 h5 端使用 svg 的 symbol 方式引用,在小程序端则依然采用字体图标的方式,对应封装的是 <QySvgIcon>

vue 复制代码
<!-- 多色图标 -->
<template>
    <view class="qy-icon-box">
        <!-- H5 端使用 svg -->
        <!-- #ifdef H5 -->
        <svg class="icon" :class="classArr" :style="iconStyle" aria-hidden="true">
                <use :xlink:href="'#' + flag" />
        </svg>
        <!-- #endif -->

        <!-- 小程序端则改为使用字体图标 -->
        <!-- #ifdef MP-WEIXIN -->
        <i class="iconfont" :class="iconfontClass" :style="[iconStyle]"></i>
        <!-- #endif -->
    </view>
</template>

<script>
import iconMixin from '@/mixins/iconMixin.js'
export default {
	name: 'QySvgIcon',
	mixins: [iconMixin]
}
</script>

<style lang="scss">
.qy-icon-box {
	display: inline-block;
}
</style>

使用时,如果多色图标在变为单色后依然可以使用,则直接使用(注意,该图标也需要添加到单色图标那个项目中去):

html 复制代码
<QySvgIcon
  flag="icon-social-paypal"
  :iconStyle="{ fontSize: '28px', color: '#172C70' }"
/>

如果多色图标改为单色后不可用,则该图标仅仅需要存在于多色图标项目,在微信小程序端另外挑选其它近似的纯色图标:

html 复制代码
<!-- #ifdef H5 -->
<QySvgIcon
  flag="icon-jinbi"
  :classArr="['mr_20']"
  :iconStyle="{ fontSize: '38rpx' }"
/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<QySvgIcon
  flag="icon-jinbi2"
  :classArr="['c_yellow mr_20']"
  :iconStyle="{ fontSize: '38rpx' }"
/>
<!-- #endif -->

其它说明

  • 之所以要分成 2 个项目,是为了尽可能减小 main.js 引入的图标相关文件的大小,减少打包体积:
javascript 复制代码
// #ifdef H5
import './static/iconfont/iconfont.js' // 来自多色图标项目
// #endif
import './static/iconfont/iconfont.css' // 来自单色图标项目
  • 对于 iconfont.css 文件,记得要更改 @font-face 里的文件引用路径,改为以 ~@ 开头的绝对路径(一般在 css 中都是使用 ~@ 而在 js 中使用 @):
css 复制代码
@font-face {
  font-family: 'iconfont';
  src: url('~@/static/iconfont/iconfont.woff') format('woff'),
    url('~@/static/iconfont/iconfont.ttf') format('truetype');
}
  • 字体图标的文件组织如下:

当下载到本地的字体文件小于 40kb 时,在小程序端 uniapp 会自动将其转为 base64 格式以兼容小程序。

不支持动态组件

在微信小程序上是不支持使用 <component> 然后通过 is 属性来渲染动态组件的,这一点在 uniapp 的官方文档有说明:

不支持自定义指令

在微信小程序端是不支持 vue 的自定义指令的。

不能在子组件直接修改 props 接收的父组件的值

这句话乍看上去好像是句废话------这不就是 vue 官方文档的规范吗?但我在做一个页面时,使用到了 uView 框架的 Checkbox 复选框,该组件需要通过 v-model 绑定一个类型为布尔值的变量:

而我赋值的 item.checkeditem 是从父组件传入的数组 unpaidList 的成员:

这么写在 H5 端一切正常,但是在微信小程序端会发现点击复选框没有效果。其原因就是点击复选框时会在子组件改变 item.checked 的值,相当于改变了 props 接收的父组件的 unpaidList,所以会导致点击失效。解决办法就是在 change 事件发生时,向父组件发射 checkboxChange 事件,然后由父组件更改对应 itemchecked 的值:

javascript 复制代码
checkboxChange(e) {
  this.$emit('checkboxChange', e)
}

值为 null 或 undefined

当接口传递的值为 nullundefined 时,微信小程序会直接显示 null 或 undefined,所以可以使用 v-if 做下判断,或是 vue2 中的 filter 做下处理。

表单的验证

使用 uView 的表单组件时,如果某一添加了必填验证的表单项有默认值,该值若是数字类型的:submitForm: { quantity: 1 },要改为字符串类型:submitForm: { quantity: '1' },否者提交表单时会报错:

<u-input> 组件的 @input

使用 uView 的 <u-input> 组件,添加的 @input 事件,在微信小程序端,当删除输入框内容时,删除到最后清空的那一下操作不会触发 @input 事件,解决办法是可以配合 watch 来监听输入框值的变化。

<text>组件中的空格

<text> 组件中,空格最好使用 &nbsp; 表示,如果直接使用空格键敲出的空格:

在编译后可能导致换行:

小程序分享按钮

如果要让小程序页面点击后弹出的菜单中分享按钮可用:

需要在页面添加如下代码:

javascript 复制代码
wx.showShareMenu({
  withShareTicket: true,
  menus: ['shareAppMessage', 'shareTimeline']
})

运行

运行时如果没有自动打开浏览器或微信开发者工具

可以在 HBuilder X 中打开"运行"菜单:

在运行设置里设置浏览器或微信开发者工具的路径:

另外,运行小程序还需要在微信开发者工具开启服务端窗口:"设置 - 安全设置" 以让 HBuilder 可以启动微信开发者工具:

手动运行小程序

如果自动打开微信开发者工具失败,也可以直接通过微信开发者工具打开打包后的位于项目根目录下的 unpackage\dist\dev\mp-weixin 来手动运行小程序(dev 目录下的是开发版,build 目录下的是生产版)。

公众号网页调试

在微信开发者工具中,通过"项目 - 更换开发模式"切换:

然后在新打开的窗口上方地址栏输入网页的地址即可:

打包

减小主包的方法

在打包上传微信小程序时,会要求主包大小不能大于 1.5M,下面介绍几点减小主包的方法。

分包

在 manifest.json 开启分包:

json 复制代码
"mp-weixin": {
  "optimization": {
    "subPackages": true
  }
},

pages 目录下的页面会被放入主包,分包可以在 pages.json 中设置 subPackages,注意 root 的值不能是 pages

json 复制代码
// #ifdef MP-WEIXIN
"subPackages": [{
  "root": "subPages",
  "pages": [{
    "path": "contact-information/contact-information",
    "style": {
      "navigationBarTitleText": "联系方式",
      "enablePullDownRefresh": false
    }
  }]
}],
// #endif
"pages": [
  // #ifdef H5
  {
    "path": "subPages/contact-information/contact-information",
    "style": {
      "navigationBarTitleText": "联系方式",
      "enablePullDownRefresh": false
    }
  },
  // #endif
]

如此,subPages 目录下的页面就会被放入分包。

按需引入 lodash

为了减小打包后的体积,要使用 lodash 中的哪个方法就按需引入哪个方法,比如 import cloneDeep from 'lodash/cloneDeep'

时间格式化

使用 day.js 而不是 moment.js 来实现时间格式化,因为前者的包体积按其官方说法只有 2 kb。

查看主包大小

在微信开发者工具点击"详情 - 基本信息 - 代码依赖分析"查看:

得到的结果如下所示:

代码质量分析

通过微信开发者工具的"详情 - 性能质量" tab,点击扫描代码质量即可:

报错汇总

[ app.json 文件内容错误] app.json: pages 不能为空

查看 pages.json 文件,删除自动生成的如下代码即可:

json 复制代码
{
  "path": "App",
  "style": {}
}

watcher 报错

使用 uView 的表单组件时,遇到如下报错:

原因是某个 <u-input>type 属性使用了 select,在某些情况下,比如清空表单后,该表单项的值为 null,uView 内部处理去掉多余空格时就会报错:

解决办法是让重置时 select 表单项的值为 ''

页面路径找不到

如果报错什么页面路径找不到,可以检查下路径的最前面是否缺少 /

相关推荐
hackeroink2 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-4 小时前
验证码机制
前端·后端
燃先生._.5 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235246 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css