入坑 UniApp 写小程序,一些感受

文章的 TOC 太多了,看起来不方便?PC 端可以看看我的 《两个 TamperMonkey 脚本,解决掘金阅读体验的三点不爽》,可以解决一些掘金阅读体验问题。

我写的「入坑」系列文章:

  1. 《入坑 Mac,看这一篇就够了》
  2. 《入坑 iTerm + OMZ,看这一篇就够了》
  3. 《入坑 Firefox Developer Edition 及 Mobile 版,看这一篇就够了》
  4. 《入坑 WebStorm,看这一篇就够了》
  5. 《入坑 VSCode,看这一篇就够了》
  6. 《入坑 Vim,看这一篇就够了》
  7. 《入坑 Git,看这一篇就够了》
  8. 《入坑 Docusaurus,看这一篇就够了》
  9. 《入坑 Nginx,看这一篇就够了》
  10. 《入坑 UniApp 写小程序,一些感受》← 本文

🎼 前言

好久没发表文章了,有很多想写的,但总是找各种借口推到明天的明天的明天。每天陆陆续续总会收到一些赞和收藏,非常感谢大家对我之前写的文章的认可。

距离上次发文章,也经历了漫长的时间,期间发生了一些让我很郁闷的事情。总之,2024 年 1 月初,我从上海回到了杭州。

回到杭州后,我加入了一家初创小公司,做 AI 的。我的说法是「趟一趟这趟历史的浑水」,老板的说法是「躬身入局」,瞬间觉得自己文化水平有些捉襟见肘了。

加入公司做的第一件事情便是写一个小程序。这篇文章其实在我接受这项任务的时候就已经想好要写了,也早就应该写了,但最终还是一拖再拖,闷馊闷臭了。

TL;DR

你或许会发现这篇文章和我其他的有些差别,是的,图很少,很干。

这不是一篇「如何用 uni-app 写小程序」或者「如何开发小程序」的主题的文章,因此不会特别多地介绍各种入坑知识(但也会涉及),更多的是我如何思考小程序开发中的技术选型、最佳实践以及遇到的各种问题的吐槽等等。

主要内容

适合读者

  • 想入坑写小程序,技术栈为 Vue 的同学
  • 正在或即将用 UniApp 开发小程序的同学
  • 单纯想了解了解小程序开发的同学

你将了解

  1. UniApp 开发小程序的入坑姿势(二选一)
  2. UniApp 和微信小程序的一些坑
  3. UniApp 的一些天坑和闭坑秘籍
  4. 一些算是最佳实践吧

术语

本文正文中用到的术语解释:

日期 版本说明
MP 小程序(真机或模拟器)
H5 运行成 H5 模式

编辑历史

日期 版本说明
2025/06/30 V1

🕹️ 技术选型

写这个小程序之前,我的背景是这样的:

  • 知道小程序这个东西,但从来没有写过或了解过其技术栈
  • 从来没有正式用过 Vue 写过应用,只通过 Weex 用过一点点

那为什么我上呢?原因有两个:没其他人了,这样的挑战让我兴奋大大大于担忧。

选择开发框架 uni-app

你可能知道小程序框架除了 UniApp 之外,比较有名的还有 Taro 啥的(我..不知道)。那为什么选 UniApp 呢?

其实就是被同事「忽悠」的。他是这么跟我说的:

  • 所有的组件它都给你弄好了,button 什么的,样式你都不需要操作,它就长那样...
  • 只要 div 改成 viewspan 改用 text 就好了
  • 很简单,超级简单...
  • 它甚至有自己的开发工具,叫 HBuilderX(简称 HX),你只需要...
  • ...

以上说了,小程序对我来说是一个全新的领域,我并没有急着进入业务开发,而是花了两个礼拜熟悉相关技术栈、并最终决定项目架构。说白了,就是看官文的「快速上手」本地瞎折腾。

不选 HX 做开发工具

官文的「快速上手」有两篇,HX 或 CLI,这是一个问题。

看过我文章的同学,应该能够了解到我的几个特点:

  1. 自称「软件研究员」,喜欢玩软件,尤其是开发工具,特别喜欢研究配置
  2. WebStorm 铁粉,自以为得心应手
  3. VSCode 略懂,自以为很多重度用户还不及我对其了解
  4. 快捷键高手
  5. 钟爱命令行
  6. 代码洁癖

以上都会对我最后的选择有影响。

为了做出正确的选择,同时也是熟悉的一个过程,HX 和 CLI 两种方式,我都试了。

首先,作为「软件研究员」,我是非常喜欢玩各种软件的。但 HX 粗糙复古的图标先有让人眼前一「暗」的感觉,打开界面瞬间,更令我失去了热情。配色简直一言难尽,官方吹嘘说是「护眼」。

从界面上看,感觉它可能基于 Monaco,但又刻意不想做得跟 VSCode 很像。

为了了解技术栈,我跟文档操作了好几拨,同时也写了一些 Demo 代码,然感觉用着并不爽利,尤其是快捷键,虽然可以自定义。

更要命的是,它擅自修改了系统文件关联。

最令我不能接受的是它的 uni_modules 模式,一旦引入意味着两个后果:

  1. 你会有一堆不是你写的代码必须要提交,意味着你的代码提交量反应不了你的真实工作,并且需要专门扩展 Lint 等工具的忽略列表
  2. 你会被 HX 绑架,因为只有 HX 可以添加或更新 uni_modules

最最不能接受的是,HX 把很多细节都隐藏了,依赖了哪些东西,版本是什么,两眼一抹黑。想引个 npm 包?不行。想配 Husky?不行。想写个 npm script?不行。全因为没有 package.json

在对比了 HX 和 CLI 项目后,发现 HX 的项目其实就是 CLI 项目的 src 目录,这就意味着 CLI 的项目实际可以用 HX 开发,而 HX 的项目只能 HX 开发。包容性很重要。

总结一下 HX 的弊端:

  1. 要一个有经验的已经偏爱了某个 IDE 或者编辑器的开发者,单单为了一种开发框架换一个新开发工具,除非此开发工具真的有亮的不行的亮点,但 HX 在 IDE 或编辑器这一层面来讲几乎没亮点
  2. HX 项目不具包容性,基本上就是只能强迫人用 HX,以后想升级、迁移都是问题

另外让我很反感的是官方宣传 HX 的措辞,在他们 快速上手 - 通过 vue-cli 命令行 中,我引用几段(因为他们可能会改掉)。

为了提升易用性,降低门槛

很多开发者对 node 不熟悉、对命令行有心理抵触。不要想当然认为所有开发者都会 node,HBuilder 有几百万开发者,其中掌握 node 的开发者连一半都占不到。

「很多」?「心理抵触」?

我向来认为 IDE 除了便利开发之外,最重要的一个作用就是,它是开发者最好的「老师」之一。而命令行是开发者必会的技能之一,作为老师,应当「循循善诱」。

而且,使用命令行,没人说要「掌握」Node 呀,本人不才,命令行还行,但 Node 知识略懂。

如果你习惯其他 IDE,开发 uni-app 低效也无所谓,那也可以用其他 IDE。

很低情商的一句话..就像在说「老子天下第一,不用是你活该」,很茶的感觉。

为什么选 CLI 方式

不选 HX,就只能选 CLI 方式。好处:

  1. 拥有的是一个完整的可自运行的项目
  2. 能用命令行提效
  3. 能用 npm
  4. 能用 Husky 搭配各种 Lint
  5. 能用习惯的 IDE 或编辑器,也可以用 HX

CLI 需承担的「坏处」

需配置 easycom

首先是 easycom

只要组件安装在项目的 components 目录下或 uni_modules 目录下,并符合 components/组件名称/组件名称.(vue|uvue) 目录结构...

说实话,我对这种隐式依赖很反感,但它也的确提供了一些便利。CLI 开发模式要做的是在 src/pages.json 下添加以下配置:

bash 复制代码
  "easycom": {
    "autoscan": true,
    "custom": {
      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
    }
  }

但这只是告知了编译器,而并没有告知你的开发工具,你会看到 IDE 在抱怨:

Pasted image 20250716123751.png

要让 IDE 也知道这个东西存在,并且能够点过去,还需要装插件:

  • WebStorm 用 Uniapp Tool
  • VSCode 用「我没找到」

WebStorm 使用插件后的效果(这里我故意写错了一个属性,IDE 能检测到并给出警告):

Pasted image 20250716123825.png

不能使用 uni_modules

如上一章节总的摘录所说,uni_modules 也是 easycom 的一种,所以其实也不是不能用。

当然你也可以用 HX 打开项目后,用 HX 的能力进行安装和升级。

然,我非常不建议这么搞。

以我的这个项目的经验来讲,我的确有从 uni_modules 的项目中汲取经验,但并没有使用任何一个。这种硬拷代码的三方依赖,实在叫人难以下咽。

TypeScript

无 TS 不 Coding,必须 TS,所以我用的是他们提供的 TS 模板。

Vue3

我并没有怎么接触 Vue,所以只想用最新的版本。UniApp 支持 Vue2 和 Vue3,但作为 Vue 生手加升级狂魔,我必须全面使用 Vue3 语法。模板以及 uni_modules 的代码,目前(截止 2025/06)大部分好像都还是 Vue2。

TSX

我想用。但小程序端还不支持,所以放弃。

🔋 开发折腾

折腾的成果

由于没法在 UniApp 项目下搞 Storybook,因此我专门搞了一个测试页面 pages/_xx,以便随时可以调戏 UniApp 和通用组件。

MP 封装

在 UniApp 中,你有一个全局的 uni 对象,它下边挂了很多方法,然而,我不建议你在业务代码中直接裸用 uni.xx,而是做一层封装。以下是几点考量:

  • 我们可以一眼就知道在项目中用到了多少相关的接口
  • uni 不够简洁,封装后使用更简洁
  • uni 类型定义不精确或者不正确,封装后对 TS 更友好
  • uni 存在多态,可能导致这些异化逻辑散落到业务层,封装以保护代码一致性
  • uni 存在平台差异问题,必须封装后方能安全使用
  • uni 不面向业务,通过封装可以使其具备业务特性,使代码更简洁
  • 万一哪天换框架了,我不需要到处改业务代码

问题:不够简洁

比如 uni.login,多数情况我们只需要返回的 code,然而原始返回长这样:

arduino 复制代码
{  
  errMsg: 'login:ok', // ← 微信
  success: true, // ← 支付宝
  code: '0e3ejr000AXOwR1g43400j7gwL2ejr05'
}

问题:类型定义不精确或者不正确

还是 uni.login 的例子,官方的数据返回类型如下:

typescript 复制代码
interface LoginRes {
  errMsg: string;
  code: string; // ← 唯一有用且需要关心的部分
  // 💥 以下都没有
  authResult: string;
  anonymousCode?: string;
  authCode?: string;
  authErrorScope?: any;
  authSucessScope?: string [];
  appleInfo?: AppleLoginAppleInfo;
}

问题:方法多态

比如对 storage 操作,uni 提供了同步(getStorageSync)和异步(getStorage)两种,这会导致业务代码的不一致性,通过封装可以限制成一种。

另外,多数 Promise 接口都提供了 successfail 回调,而传了回调就不再是 Promise 了,这让我这种 Promise 狂魔很不爽。

问题:平台差异性

uni 的接口并未处理平台间的兼容差异问题。

  • uni.getDeviceInfo,它的定义是「同步获取」,但在支付宝小程序(该接口不支持支付宝)下却返回了 Promise
  • uni.getUserInfo,H5 下直接报方法不存在(微信已宣布清退此接口)
  • 调用成功,微信的返回可能是 { errMsg: 'xx:ok', ...others },支付宝可能是 { success: true, ...others }
  • 调用失败,微信可能到 Promise.reject,而支付宝可能是 Promise.resolve

调用失败不一致性的例子:uni.getStoragekey 不存在的情况,微信会 reject({ errMsg: 'getStorage:fail data not found' }),支付宝则 resolve({ data: null })

storage 的同步接口还是对齐的,这也是为什么我们封装用的是同步接口的原因之一。

问题:需要面向业务

比如 storagekey 有保留前缀,我们可以统一设置前缀避免不必要的问题。

如何封装

我们需要:

  1. 封装错误 MpError,以保证所有的错误都是 Error 对象,而不是 uni 给出的普通 Object
  2. 工具方法 wrapUni,强制所有能 Promise 化的 API 都必须返回 Promise,标准化了错误对象 MpError,且对不存在的方法进行了捕获并抛出封装后的 MpError 错误
  3. 所有的方法名以 mp 打头,拼上真正的 API 名称(有的时候可以稍作改动)
  4. 按需封装,可以在写业务的时候,用到哪个封装哪个
  5. 封装的原则是:若方法有静态和 Promise,舍其一;明确真正的作用,简化输出

比如以下是对 uni.login 的封装:

typescript 复制代码
/**  
 * 💥H5 → Promise reject 「login:fail method 'uni.login' not supported」  
 */  
export default function mpLoginCode(): Promise<string> {  
  return wrapUni<string>(() => uni.login().then(result => result.code));  
}

我们通过将其名称改为 mpLoginCode 明确了其真正的作用,返回改成 Promise<string> 简化了其结果。

以下是我一边写业务一边在 src/pkg/mp 下封装的接口概览:

Pasted image 20250716125756.png

注意循环依赖

由于我在封装 mp 的过程中对相关的错误进行了日志上报,这非常容易导致循环依赖。我在后面写 Taro 项目的时候吸取了教训,在 sls、stroage、fetcher 等模块中不再反向依赖 mp,而是直接将封装写在了相关模块的内部。

国际化

首先,请走出你的认知误区,国际化的目的并不一定是为了服务海外用户。为项目添加国际化的能力有如下好处:

  1. 保证文案的一致性
  2. 保证数据展示的一致性(数字、日期等的格式化展示也属于国际化)
  3. 为海外业务提前布局

所以,这也是我写小程序一开始就着手考虑的基础建设之一。

关于国际化的文案有两个

秉承 UniApp 的尿性,国际化也有很多坑:

  1. $t 在小程序下无法使用插值

    • values 类型报错...,但如果不传
    • H5 下会把文案中的插值干掉
    • MP 下传与不传都会保留文案中的原始值
  2. 由于真机中没有 Intl,模板不能用 $n,也不能 export const { global: { d } } = middlewareI18n

  3. 由于真机中没有 Intl,模板不能用 $d,也不能 export const { global: { d } } = middlewareI18n

以下是我封装了 pkg/middleware-i18n

Pasted image 20250716132700.png

main.ts 中安装此中间件:

javascript 复制代码
import {
  createSSRApp
} from 'vue';

+import middlewareI18n from '@/pkg/middleware-i18n';

import App from './App.vue';  

export function createApp() { // eslint-disable-line @typescript-eslint/explicit-function-return-type  
  const app = createSSRApp(App);
  
+  app.use(middlewareI18n);
  
  return {  
    app
  };
}

Pinia

要不要一个全局状态管理工具呢?

以我写 React 的经验,我可以不依赖于任何全局状态管理就能够写好一整个应用。但 Vue,我就不够有信心了,更不要说有小程序这个天坑的加持。

最后发现写页面其实可以不需要全局状态管理,但页面间共享的信息一定是有的,因此全局状态还是有必要的。

接下来的问题就是,选择哪个作为状态管理工具呢。

我最终在 Vuex 和 Pina 之间选择了 Pinia,没什么特别的理由。主要是好像 Vuex 自己也推荐用户选择 Pinia 了,更重要的是我实验性地用 Pinia 成功了。

Pinia 的使用方式比较简单,但非常不直观。我们需要:

  1. 定义一个 middleware
  2. maincreateAppapp.use(middlewarePinia),并必须在 return 中带上 Pinia 这个对象
  3. 使用 Pinia 的 defineStore 定义需要的 Store
  4. App.vue 中初始化定义好的 Store(有需要的话)

然后便可在应用各处快乐的使用定义好的 Store 进行全局状态的读写操作了。在我的应用了我定义了三个 Store:

  1. defineStore('login', ...) 用于维护用户登录信息
  2. defineStore('config', ...) 用于维护应用配置,比如三方链接、功能开关等
  3. defineStore('system-info', ...) 用于维护部分 uni.getSystemInfoSync 得到的信息,因为部分系统信息会变,比如 theme

接口请求

接口返回一般会把真正的数据做一层包裹,类似这样:

csharp 复制代码
interface ApiResponse<T = null> {
  code: string;
  message: string | null;
  data: T;
}

前端一般来说只关心两层:

  1. code 为「成功」时的 data
  2. code 为「失败」时的 message

我见过过分勤劳的人在每次调用的接口后加上一段条件判断的,但我是懒人,所以我需要一个全局请求的封装,在成功时直接得到数据,在失败时候抛出错误。

同时,我见过太多后端的接口问题,从命名到类型的诡异,再有就是各种不一致。所以,我封装了接口请求的基础逻辑 fetcher,然后在 fetcher 的基础上以对象为核心分批封装了所有的数据接口,把 API 的 method、URL、以及恶臭到可能极致的参数和结果统统埋进了数据层。这样,暴露给前端业务层的请求,就是类型明确,干净卫生的纯 Promise 方法。

这是我一贯坚持的编程方式,且我不愿意把这个交给 AI 来做,因为在梳理数据接口,把后端接口转化为前端 API 方法的过程,其实就是理解业务的过程,且这种跟 UI 无关的联调模式,能在联调初期发现后端设计上的问题。

后来,我在写 Taro 小程序的时候,实在受不了到处复制代码,又为了能够尽可能复用 PC 端的数据封装逻辑,于是把写 PC 端时期封装的 @kcuf/fetcher 进行了提纯,写了 @kcuf/fetcher-core,使其可以脱离环境使用,使各端的接口请求方式可以保持在同一基准线。

SSE

写过 AI 流式交互的同学,应该都知道 SSE

小程序请求接口有个 enableChunked 类似这样(我已经不记得为什么要用 wx.request 而不是 uni 了):

php 复制代码
// #ifndef H5
const requestTask = wx.request({
  url: apiUrl,
  enableChunked: true,
  header: headers,
  success: resolvePromise,
  fail: (err): void => reject(createFetcherErrorNetwork(request, err.errMsg))
});

requestTask.onHeadersReceived(() => onOpen?.());
requestTask.onChunkReceived((e: IChunkReceivedArg) => {
  processArrayBuffer(e.data);
});
// #endif

但我需要兼容 H5,而浏览器自带的 EventSource 并不支持 header,虽然有 EventSourcePolyfill,但我试了有循环启动的 BUG(不明就里),且 Polyfill 写得比较臃肿。于是,我自己写了一个 @kcuf/fetch-sse

解析 SSE Chunk 需要 TextDecoder,小程序下没有这个全局对象,可以参考 FastestSmallestTextEncoderDecoder

条件编译

UniApp 提供了 条件编译 机制,上次听说「条件编译」已经不知道是什么时候了,大概是上古时期了吧。

条件编译的好处是,特定环境不会有永远用不到的代码片段。

但我其实不太喜欢条件编译,尤其在带 return 语句的时候,会看起来有语法错误。

日志

虽然微信平台会默认添加 PV、UV,甚至新用户等日志,但更细节的日志还是应该我们自己弄。我采用了阿里云 SLS Web Tracking 的方式,对以下场景进行了全局统一的日志支持:

  1. 小程序及页面生命周期
  2. API 请求成功与失败

另外就是关心一下关键的用户点击等操作。

.env 文件,有无必要

你可以在 .env 中写接口的 Base URL,或者别的跟环境有关的配置信息。但这意味着你需要承担一个后果:不同环境的构建结果无法统一。

或者这并不是什么大问题。但我更倾向于写一个 env 包(这也是我后面写 Taro 时用的方案),把跟环境有关的都封装在这个包里面,也不会造成 import.meta.env.VITE_XX 漫天飞舞的惨烈现象。

🪁 上线折腾

你以为开发已经很折磨人了?你想少了,上线还得再折磨你一遍。

包大小限制

由于本人没有经验,所有的页面都放在了 src/pages 下,然后也不知道什么叫「主包」「分包」,在微信开发者工具上进行上传的时候就被告知「你的包超过了 2MB,不能上传」。

我看了相差不了多少,就勾选了项目本地设置中的「上传时压缩脚本」,将将及格,但开发工具死活不给你记住那个选项,每次还给你提醒。等业务多了,代码超不少了,就要考虑分包了。

可恶地是,无论 UniApp 还是 Taro,亦或是微信自己,都没有把分包作为很重要的文档,给开发者看到。如果你正在着急上线,然后也不直到什么叫分包(就像当初我一样)那么就会很烦躁。

资质材料

如果你的小程序是个全新的东西,那么你在上线前,起码需要准备一周左右,因为微信平台(其他我不知道)会要求你上传一些资质材料,具体流程我没有记,总之挺烦的。

上线前,还可能要求上传截图,他们有 AI 会对截图里的内容和你小程序的元信息进行匹配,若有问题,还会被打回(这个过程在每次发布正式版都要来一次)。

🤯 问题记录小全

我专门为开发过程中遇到的问题及相应的解决方案做了记录,或许你也会遇到。

SSR

想在 H5 使用 SSR?最好想清楚了。因为很多的 API 并没有做相应的保护,比如 uni.getLocale 就不行(官方没有做降级,也没有在文档中标注)。

这就是为什么 start 命令用的是 uni 而不是 uni --ssr 的原因。

国际化 - manifest.json

不可用,在微信小程序开发工具中报错「project.config.json: libVersion field needs to be string, string」。

国际化 legacy 问题

legacy: false legacy: false
H5 $t
H5 useI18n 💥1
小程序 $t 💥2 😱3
小程序 useI18n
  • 💥1 - 报错(白屏)「Uncaught (in promise) SyntaxError: Not available in legacy mode」
  • 💥2 - 报错(部分组件不展示)「TypeError: _ctx.$t is not a function」
  • 😱3 - 警告(子组件不展示)「Property "$t" was accessed during render but is not defined on instance」

其中 2 和 3 容易导致「问题代码上线」,风险更高,故此:

  1. 不用 legacy: false
  2. 不用 useI8n
  3. 仅用 $t

没有 @keydown.meta.enter

如题

TS 问题

组件 来源 定义 实际
chooseImage @dcloudio/types chooseImage(...): void chooseImage(...): Promise
uni-search-bar @uni-helper/uni-ui-types `onInput({ value: string number; }): void`
uni-list-item @uni-helper/uni-ui-types `link: false Xx`

@dcloudio/types 提供了全局的 namespace UniApp,在 TS 文件下使用完全没有问题,但不能在 Vue 下使用(即使 script 为 ts),会 Eslint 报错。

H5 VS MP

- H5 MP 说明
$t 国际化插值参数 MP 不会进行插值替换,且 IDE 报错 $t 不接受两个参数
slot name 带中划线 尽可能用单个词,若必须用词组,则需下划线或骆驼命名
slot v-if 🪲 MP 无论如何都会触发 slot 的 onMounted,不论是否最终判定为需要渲染 github.com/dcloudio/un...,这种时候,不能依赖 onMounted 进行数据获取等初始化操作,可以用 watch,需设置 immediate: true
v-if 🪲 MP 下必须是 boolean,不能是 funcion 对象
v-show 🪲 MP 下 v-show 不能定义在组件定义(会一直展示着),只能用在
类似大写的 Input 的组件名 🪲 MP 下的渲染节点会变成 input 从而导致奇奇怪怪的问题(比如样式)
onLoad 同步 异步
uni.showModal 传入 undefinedconfirmTextcancelText 🪲 H5 会显示英文,MP 抛错
uni.chooseImage 取消 H5 下取消,会导致 Promise 一直挂起
uni.request(//xxx) MP invalid URL
uni.createInnerAudioContext() 复用一个 context MP 播放第二次的时候,报错「TypeError: Cannot read property 'then' of undefined」,解决方案:用完即焚
封装组件传 class MP 下会多一层,class 在这多出来的一层上,导致样式可能失效,uniapp.dcloud.net.cn/tutorial/vu...
<uni-row> / <uni-col>class 用 CSS developer.mozilla.org/en-US/docs/...
<uni-swipe-action> 🪲 MP 下右侧多一个 view 包裹,导致 flex 无效,必须自己多裹一层 flex
<text> 裹组件 🪲 MP 下内裹组件的 <text> 高度为 0,但只有 text 支持 selectable...
Url 中的参数 🪲 H5 下,script 中从 props 得到的是正确(解析后)的参数,但在 template 中却还是解析前的
Audio onCanplay 🪲 MP 在结束后,会重新触发一次...
Audio duration 🪲 MP 第一次 onCanplay 拿不到时长,只有在调用 .stop() 后自动触发的第二次 onCanplay 可以...
在 TS/JS 中 import 样式文件 🪲 MP 下会被 tree-shake 掉

Map 组件需要申请腾讯 key,lbs.qq.com/dev/console... Map 中定位需要到小程序开发工具测试,web 端模拟器拿不到地理坐标

🌰 onLoad

javascript 复制代码
import {
  onLoad
} from '@dcloudio/uni-app';

console.info(111);
onLoad(() => console.info(222));
console.info(333);

MP 输出:

复制代码
111
333
222

H5 输出:

复制代码
111
222
333

开发工具 VS 真机

- 微信开发工具 真机 说明
Intl 真机不支持 vue-i18n 的 d$d(警告)
TextDecoder
RecorderManager duration 开发工具下 duration 无效,需加 hack

三方库

三方库 H5 微信开发工具 真机 说明
marked @12.0.1 报错 /^((?![*_])[\s\p{P}\p{S}])/: Invalid property name in character class
micromark @4.0.0 🚫 构建报错 [vite]: Rollup failed to resolve import "micromark-util-symbol"(或其他包)
markdown-it @14.0.0 🚫 构建报错 [vite]: Rollup failed to resolve import "linkify-it"

其它代码设计问题

关于页面 padding

有些页面需要 padding,有些不需要,看起来可以用下面这样的代码解决。但!

xml 复制代码
<style lang="scss" scoped>
page {
  padding: 20px;
}
</style>

H5 没有问题,MP 不行;如果要想 MP 可以,则必须去掉 scoped,于是 H5 下所有的页面受到影响...

uni-list-item

link 点击出错没有任何反应,事件也是 click 而不是 error,这导致问题很难被发现:

javascript 复制代码
pageApi(api) {
  let callback = {
    url: this.to,
    success: res => {
      this.$emit('click', {
        data: res
      });
    },
    fail: err => {
      this.$emit('click', { // <-- ...
        data: err
      });
    }
  };
  
  // ...
}

🙋 FAQ

❓为什么不用 ncu 更新 uni-app 的包?

他们的包都是死版本,试过改成 ^x.y.z 会导致 uni_modulescomponents 自动注入失效等一系列奇怪的问题,而且安装的包会多版本重复。

官方的更新的方式,使用 npx @dcloudio/uvm@lates,参考 更新依赖到指定版本

另外,UniApp 的整体依赖貌似很不健康,我曾经仅仅只是升级了 @dcloudio/* 的包,然后,跑不起来了就... 一个多年的 陈年老 BUG 无人管。

📌️ 链接

🪭 写在最后

如果你的技术栈只有 Vue,那么用 UniApp 写小程序无疑是不二之选,如果你的技术栈是 React,那末,你可以试试 Taro(我试了,可用)。

如果你顾及到框架的可持续发展,你会发现 Taro 的更新频率不如 UniApp,我曾经也以为 Taro 是死掉的状态,因为最末一个版本已经很久不动了,直到有一天(2026/07 的某一天),它居然升了个小版本,然后我选择的 UI 库 @taroify 也升了几次小版本。装死?Very confusing.. 🤔

若你选择 Taro,那么你就要容忍它的工具链陈旧(Vite 4,最新 8,而 Webpack 虽然是 5,但必须写死版本)和仅支持到 React 18 的问题(意味着你无法随心所欲地升级基础库)。

遗憾地是,无论你选择 UniApp 还是 Taro,你都不得不容忍它们 API 的设计问题(有的可能需要回溯到微信本身的 API 设计问题)、微信小程序开发工具的慢、Buggy、与真机不一致,还有就是 H5 和小程序兼容性问题等等。

总之,小程序开发是我见过,开发体验最差的工种 😘。

相关推荐
不如摸鱼去1 天前
Wot UI 2.3.0 发布:二维码组件来了,Open Wot 与 wot-starter 同步更新
前端·ui·微信小程序·前端框架·uni-app
万亿少女的梦1681 天前
基于微信小程序、Express与MongoDB的校园失物招领系统设计
mongodb·微信小程序·node.js·express·系统设计
小徐_23332 天前
Wot UI 2.3.0 发布:二维码组件来了,Open Wot 与 wot-starter 同步更新
前端·微信小程序·uni-app
2601_953720822 天前
【计算机毕业设计】基于微信小程序的拼车服务系统设计与实现
微信小程序·小程序·课程设计
FungLeo2 天前
Taro 4 微信小程序:RootPortal CSS 变量继承问题与自建 PagePortal 解决方案
微信小程序·taro·css 变量
海纳百川·纳海川4 天前
租房行业数字化:换个思路解决“老问题”
大数据·微信小程序·小程序
JasonMa1536 天前
从零搭建一个微信小程序活动管理平台(一):架构设计与技术选型
javascript·微信小程序·node.js
鲁Q同志7 天前
微信小程序【uni-file-picker文件预览】
微信小程序·小程序
2601_963869957 天前
【计算机毕业设计】基于微信小程序的拼车服务系统设计与实现
微信小程序·小程序·课程设计