likes
comments
collection
share

uniapp 初体验踩坑记录

作者站长头像
站长
· 阅读数 11

最近在做一个 uniapp 项目,在此之前我只有原生微信小程序、taro 的开发经验,所以在使用 uniapp 的过程中踩了不少坑,这里做下记录,希望帮助到有缘人。另外,本篇文章不是教程!不是教程!不是教程!只是告诉你如何做选择,以及如何避坑及解决可能会遇到的坑。

编辑器选择

HbuildX,是 uniapp 官方编辑器,如果你爱折腾,那么你可以慢慢研究如何调教它,尽管它拥有奇怪的插件安装方式以及乱七八糟的问题。作为新人,使用 HbuildX 会浪费前期一定的时间,如果你懒得折腾,你也可以继续使用 vscode 或者 webstorm。因此你需要使用 cli 方式来创建项目,而不是在 HbuildX 中创建并使用其他编辑器打开文件,因为 HbuildX 创建的项目文件结构只适用于 HbuildX(不敢说这是100%正确的,只是我目前理解是如此的)。如果你使用其它编辑器且需要预览,那么有两种方式:

  • 在项目所在终端执行 npm (yarn, pnpm) run serve, 在浏览器中访问地址 http://localhost:8080/ 浏览 H5 页面
  • 下载例如微信开发者工具,,执行 npm (yarn, pnpm) run dev:mp-weixin 命令,将打包后的产物在微信开发者工具中打开,也可以实现预览

作为官方编辑器,我还是推荐你下载 HbuildX 来使用,即使你不将他作为主力开发工具,但是在打包或者其他一些场景下有它的优势和好处。

组件库选择

uniapp 可选择的组件库真的不多,市场上随便一搜就是 FirstUIuView,ColorUI(没有找到官网文档),ThorUIVant weapp等几款,可以说没有什么选择。FirstUI 部分免费,部分收费,除非有特别需求,否则不选。ColorUI 由于是 CSS 型组件库,也不在选择范围内。Vant 因为不能跨端,也没法选择。那么剩下的 ThorUI 以及 uView 就看你个人对于 UI 风格的喜好了。整体上,uView 的组件质量、数量以及风格会更强大些,也是大多数人的选择。所以如果你现在纠结于 UI 库的选择,可以直接采用 uView。

但是随之而来的坑,也会很多。

组件库不支持 Vue3

不论 uView 还是 ThorUI 都不支持 Vue3,我一开始创建的是 Vite + Vue3 的工程,由于缺乏经验加上看文档不仔细,无脑在项目中引入了 uView,但是在挂载组件时一直报错,导致我折腾了很久,后续经过仔细看了一遍文档,加上百度了下,才发现 uView 并不支持 Vue3。所以如果你想使用 Vue3 开发 uniapp,那么在组件库的选型上就得注意了。

组件库编译报错

我使用的预览工具是微信开发者工具,所以我的代码会被编译为微信小程序的代码。但是引入 uView 后会有一些报错令人费解,例如:

Uncaught TypeError: uni.requireNativePlugin is not a function

原因在于组件库作为 node_modules,在 webpack 进行 bundle 打包的过程中不会被 babel 编译,而是原封不动的打包进 bundle 中。因此组件库的代码编译成微信小程序的代码的过程中就会出现语法无法被微信开发者工具解析的情况,所以需要进行 webpack 配置。在 Vue2 的项目中,webpack 配置可以在 vue.config.js 中进行,添加以下代码即可:

module.exports = { 
  transpileDependencies: ['uview-ui'] // 这里可以添加其他需要被编译的 node_modules 里的库
}

相信编译为其它平台也是一样的,需要对组件库进行编译。

路由跳转没反应

在实际开发中,我在使用 uni.navigateTo 等路由跳转方法时,因为忘记在路径前添加 / 导致路由跳转失败:

// 失败
uni.navigateTo({ url: 'pages/home/Home' })

// 成功
uni.navigateTo({ url: '/pages/home/Home' })

所以初学者务必注意这些细节,可以少踩很多坑。

引入第三方包导致代码包体积增大N倍

在业务最初的页面设计中,需要根据后端返回的词汇数组进行分组,并使用 IndexList 组件进行词组分类。根据单词的首字拼音,从 A - Z 进行分组。所以我引入了 pinyin 库,一开始在模拟器中运行一切正常,当我想真机浏览的时候就报代码包体积过大的错误,代码体积超过了 10M。于是我进行代码压缩,关闭 sourceMap 以及打包成生产模式进行预览,但是包体积仍然过大。这也是我进行分包的原因,分包前我还没查到原因,所以就行了分包,但是分包后体积依然超过单包 2M 的极限。所以我从打包后的代码中去查。发现 pinyin 库被单独打包成了一个 vendor.js 作为公共 js 文件被引入,pinyin 中的所有字符的枚举以及方法都被打包进来,大小足有 6M,这是不能依靠分包解决的。

网上尽管有引入第三方 js 库的方法,但是由于 pinyin 体积过大,且用处在我的项目中很少,所以就更换了设计图,删除了 pinyin 库。但是这里的坑是影响巨大的,如果你想在项目里使用第三方库,例如图表库或者其他的大型 js 依赖,就得小心了。

静态资源打包后失效

项目中用到了几张图片,在本地模拟器中一切正常,但是打包后总是显示不出来。网上有人讨论是资源路径不对,如果分包了需要在资源路径前添加分包名。尝试了一番并不能解决问题,我连续更换了图片所在位置,从一开始的全局 static 文件夹到使用位置的文件夹,都不起效果。最终我还是怀疑问题出现在 webpack 身上,可能是打包时 loader 对静态资源的处理出现了问题。由于项目中只使用了几张图片,所以我想到的办法是直接转换成 base64 来参与模块化,这样 100% 能解决问题。最终的测试效果也在意料之中,图片被正常加载出来了。做到这里其实我没有深究下去,但是既然知道问题出现在 webpack 打包上,那也许重写下 webpack 配置也许就行了。这里留着后续拓展了,暂时没有去做,后续有结果了再补充进来。

文本输入组件手机端无法回车换行

手机端的输入是使用输入法键盘,区别于 PC 端。模拟器或者浏览器始终是 web,测试时使用键盘 Enter 键轻易就能换行。但是在手机端却不行,手机端的 Enter 肩负很多功能,例如搜索、进入、下一个等。在 uniapp 中,键盘的 Enter 默认值是 done,这样键入 Enter 时键盘就会自动收起,并不能实现输入换行。查阅了一番,textarea 等文本表单输入时,可以控制键盘 Enter 键的能力,使用 conform-type 属性控制,具体有以下值:

说明
send右下角按钮为“发送”
search右下角按钮为“搜索”
next右下角按钮为“下一个”
go右下角按钮为“前往”
done右下角按钮为“完成”

但是换行的能力不是以上某个值的能力,所以不能给 conform-type 传以上某个值,而是不传值,设置 conform-type = null 即可,就是反其道而行之,不给 Enter 键赋予特殊能力,保留初始的换行能力就行。

pages.json 的配置解读

如果你要进行 uniapp 的开发,那么 pages.json 将是一个非常重要的全局配置文件,如果使用不当,会有很多坑在等着你。uniapp 官网有完整的配置示例,所以这里拿我的举例,这里更贴合实际业务,也更能表达我遭遇的问题。

先来看下我的 pages.json 文件有哪些配置:

{
    "easycom": {
	"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
    },
    "pages": [
	{
            "path": "pages/home/Home",
            "style": {
		"navigationBarTitleText": "招聘信息",
		"enablePullDownRefresh": true
            }
	},
        {
            "path": "pages/home/recruitment-detail/RecruitmentDetail",
            "style": {
                "navigationBarTitleText": "招聘详情"
            }
        },
        {
            "path": "pages/join-in/JoinIn",
            "style": {
                "navigationBarTitleText": "加盟"
            }
        },
        {
            "path": "pages/personal-center/PersonalCenter",
            "style": {
                "navigationBarTitleText": "个人中心"
            }
        },
        {
            "path": "pages/monitor/Monitor",
            "style": {
                "navigationBarTitleText": "管理员"
            }
        }
    ],
    "subPackages": [
        {
            "root": "pagesA",
            "pages": [
                {
                    "path": "sub-monitor/HomeManage",
                    "style": {
                        "navigationBarTitleText": "首页管理"
                    }
                },
                {
                    "path": "sub-monitor/JoinInManage",
                    "style": {
                        "navigationBarTitleText": "加盟管理"
                    }
                },
                {
                    "path": "sub-monitor/TagManage",
                    "style": {
                        "navigationBarTitleText": "标签管理"
                    }
                },
                {
                    "path": "login/Login",
                    "style": {
                        "navigationBarTitleText": "登录注册"
                    }
                },
                {
                    "path": "login/UserAllow",
                    "style": {
                        "navigationBarTitleText": "用户须知与隐私政策"
                    }
                }
            ]
        },
        {
            "root": "pagesB",
            "pages": [
                {
                    "path": "sub-monitor/PublishManage",
                    "style": {
                        "navigationBarTitleText": "发布招聘信息"
                    }
                },
                {
                    "path": "sub-monitor/RecruitmentInfoManage",
                    "style": {
                        "navigationBarTitleText": "招聘信息管理"
                    }
                },
                {
                    "path": "sub-monitor/EmployeeInfoInputManage",
                    "style": {
                        "navigationBarTitleText": "人员信息录入"
                    }
                },
                {
                    "path": "sub-monitor/EmployeeManage",
                    "style": {
                        "navigationBarTitleText": "人员管理"
                    }
                }
            ]
        }
    ],
    "condition": {
        "current": 5,
        "list": [
            {
                "name": "招聘详情",
                "path": "pages/home/recruitment-detail/RecruitmentDetail"
            },
            {
                "name": "加盟",
                "path": "pages/join-in/JoinIn"
            },
            {
                "name": "个人中心",
                "path": "pages/personal-center/PersonalCenter"
            },
            {
                "name": "管理员",
                "path": "pages/monitor/Monitor"
            },
            {
                "name": "招聘信息管理",
                "path": "pagesB/sub-monitor/RecruitmentInfoManage"
            },
            {
                "name": "标签管理",
                "path": "pagesA/sub-monitor/TagManage"
            },
            {
                "path": "pagesA/login/Login",
                "name": "登录注册"
            }
         ]
    },
    "tabBar": {
        "custom": true,
        "list": [
            {
                "pagePath": "pages/home/Home",
                "text": "首页"
            },
            {
                "pagePath": "pages/join-in/JoinIn",
                "text": "加盟"
            },
            {
                "pagePath": "pages/personal-center/PersonalCenter",
                "text": "个人中心"
            },
            {
                "pagePath": "pages/monitor/Monitor",
                "text": "管理员"
            }
        ]
    },
    "globalStyle": {
        "navigationBarTextStyle": "black",
        "navigationBarTitleText": "XX咨询",
        "navigationBarBackgroundColor": "#F8F8F8",
        "backgroundColor": "#F8F8F8"
    }
}

与文件结构对比着看更能理解其中字段配置的含义

uniapp 初体验踩坑记录

  • easycom 字段是用来匹配组件的,用于组件的快速识别与注册,如果你不配置此选项,你只是引入组件库的话,是不可能使用的。这里的匹配识别规则需要根据你的组件库命名来进行编写,其中 $1 可以理解占位符或者通配符;uView 的组件命名风格是 u-buttonu-icon 型的,所以用 $1 通配组件名称;
  • pages 用于注册页面,所有页面都需要在这里进行配置,如果你不分包,那么所有的页面都放在此处。需要注意的是,这里的页面路径前面是不需要加 / 的;
  • subPackages 是分包字段。在小程序中,各家小程序对代码包的体积都是有限制的,少则 8M,多则 24M,但是对于主包 pages 下的代码基本限制不超过 2M,所以代码量比较大的情况下必须使用分包,pagesA 与 pagesB 就是与 pages 同级的文件夹,这两个包会在小程序运行时被延迟加载;
  • condition 是开发模式下的配置,当我们的页面层级嵌套得比较深时,可能我们写某个页面时需要在页面上进行连续的点击,或者需要在 pages 配置中将该子级页面放在索引第一位,但是来回切换特别麻烦,所以 condition 是告诉编辑器,当前程序一进入就给我渲染哪个页面,这样我不再一层一层点击进去,节约开发时间;
  • tabbar 有两个坑点。我的 app 需求是根据权限控制不同的 Tab 栏的显示,所以必须是自定义 tabbar,这里 custom 字段设置为 true。坑一,list 字段中注册的 tabbar 页面路由必须是在 pages 中的,不然会报错,也就是说,我们分了主包、分包,分包中是不能存放我们 tabbar 对应的页面的。这当然很好理解,但是我第一次接触还是犯了错误;坑二,自定义 tabbar 虽然你把 custom 设置为 true 了,但是原生的 tabbar 还是会在你打包的 APP 或者小程序中出现,这是因为原生的 tabbar 并不会因为你配置了自定义 tabbar 就会被隐藏,所以你还需要在每个主页面的 onLoad 生命周期钩子里的调用方法 uni.hidetabbar() 才能够隐藏原生 tabbar,否则你就会发现,模拟器里正正常常的 tabbar 在真机上就会出现上下两个堆叠的 tabbar。另外,如果你在网上看到有人 App.vue 里调用 uni.hidetabbar(),那么请不要这么做,因为我使用 Vue2 的实测情况来看,在 App.vue 里调用 uni.hidetabbar() 并不会起作用,当然,Vue3 项目我并不清楚。

总结

我之前有使用 taro 的经历,但是由于经验匮乏,业务本身不复杂,且由于没有研究清楚,使用了单页面入口的形式开发了 taro 项目,现在想来实在拙劣了些,这也是这次选型 uniapp 的原因。之前的 taro 开发体验不算太差,虽然项目在启动的过程中也出现过问题,但是相对 uniapp 来说问题要少一些。但是 taro 的组件库并不多且不够优秀,加上很久没有写 Vue 了,出于技术广度的考虑使用了 uniapp,踩了一些坑。所以记录一下,一是防止自己忘记,二是给其他同学一点参考。但是 Vue2 确实已经有些落后了,后续的新项目可能还会使用 Vue3 开发,预计还会有一波新坑,到时候再做补充。