164 KiB
UTS Android 兼容性开发规范指南
以下为 uni-app x (UTS) Android 端开发常见注意事项与踩坑点,建议所有开发成员遵循:
================================================================================ 一、基础语法规范
-
变量声明
- 只能使用 let 和 const,不能使用 var
- 变量声明必须有显式类型或初始化值
- 不支持 undefined 类型,变量未赋值就是 null
- 不支持 undefined 关键字,判断是否存在要用 != null
-
类型定义
- 只适合用 type,不适合使用 interface(interface 在 kotlin/swift 中另有不同)
- 不支持 Intersection Type(交叉类型)
- 不支持 Index Signature(索引签名)
- 类型推断严格,必要时用 as Type 明确类型
- 不支持内联对象类型(Object Literal Type),需要单独定义 type
-
函数定义
- 函数必须在使用前定义(不支持函数提升)
- 在 setup 模式下,调用的函数必须在调用之前定义
- 依赖关系需要明确:被调用的函数必须先定义
- 这与 JavaScript 的函数提升行为不同,UTS 更接近 C/Java 的编译方式
-
循环
- for 循环的 i 必须写明类型:let i: number = 0
- 不要用 forEach、map,数组遍历用 for 循环
- 嵌套的数组方法调用可能导致类型推断失败,应改用 for 循环
================================================================================ 二、类型与对象访问
-
any 类型访问
- 不能直接访问 any 类型对象的属性
- 需要将对象转换为 UTSJSONObject 类型后使用 getString()、getNumber() 等方法访问属性
- any 类型属性访问需转换为 Record<string, any> 后用索引访问
- 使用索引访问属性时,推荐使用方括号语法 obj['property'] 而非点语法 obj.property
- any 类型不支持索引访问 obj['key'],必须先转换为 UTSJSONObject
-
UTSJSONObject 使用
- 用 utils/utis 下的 UTSJSONObject 做类型转换
- 不要用 safeget,只要 UTSJSONObject 就好
- 需要创建动态对象时,应使用 new UTSJSONObject() 然后调用 .set() 方法
- 对于 type 定义的对象类型,同样需要使用 UTSJSONObject
- 使用 getString()、getNumber() 方法获取属性值
-
数组类型
- 数组类型建议写成 Array,不要用 Type[] 简写
- 空数组需要明确指定类型,如 [] as string[]
- 数组元素需要明确的类型定义才能在模板中正确访问属性
- 对于 any[] 或 reactive 数组,访问元素属性时需要先转换为 Record<string, any> 或 any[]
-
对象操作
- 不支持 Object.keys()、Object.values()、Object.entries()
- 不支持 Record<K, V> 对象字面量语法
- 对象字面量 {...} 只能用于构造类型(class),不能用于接口(interface)
- reactive 对象在 UTS 中不支持索引器赋值操作
================================================================================ 三、条件判断与逻辑运算
-
if 条件
- if 判断只能接 boolean 类型,不能是其他类型的值
- 判断空要用 !== null,不能用 !变量(uts android 不支持 !在变量前面的判断空方式)
- 模板中的 || 运算符左边必须是 boolean 类型
- 可空类型使用可选链 ?. 和空值合并 ??
- 字符串判断空要用:variable != null && variable !== ''
-
逻辑运算符
- || 表示逻辑或
- && 表示逻辑与
- ! 表示逻辑非(但 !变量 不支持用于判断空)
- ?? 表示空值合并运算符(当左侧为 null 时返回右侧值)
- ts 的为空则使用默认值的语法在 uts 中不能用 ||,要用 ?? 来代替
================================================================================ 四、组件与模板
-
表单与输入
- 表单优先用 form 组件
- 不支持 uni-easyinput,用 input 代替
- 时间选择用 uni_modules/lime-date-time-picker
-
选择器
- uts android 不支持 picker,用 picker-view 或 uni.showActionSheet
- 一维的优先用 uni.showActionSheet
- picker-view 的事件用 UniPickerViewChangeEvent
-
导航与布局
- 不支持 uni-nav-bar,先删除
- 不支持 uni-data-select,用 picker-view 代替
- 不支持 uni-datetime-picker,用 components/picker-date 或 components/picker-time 代替
- 不支持 uni-icons
-
模板注意事项
- 跟 template 交互的变量尽量用一维变量(不要嵌套对象)
- 模板中可空类型必须使用 ?. 安全访问
- 模板中访问可空类型属性前必须先判空 v-if="order != null"
================================================================================ 五、CSS 样式限制
-
布局方式
- 只支持 display: flex
- 不支持 display: grid、display: block
- 不支持 gap
- 不支持 table、grid、grid-template-columns
-
单位与计算
- 不支持 calc()
- 不支持的单位: vh、vw
- property value
100%is not supported for min-height (supported values are: number|pixel) - property value
60%is not supported for max-height (supported values are: number|pixel) - property value
calc(33.33% - 10px)is not supported for min-width - height 不支持 vh 单位,需要使用具体的像素值或百分比
-
选择器
- [APP-ANDROID] 不支持伪类选择器
- [APP-IOS] 不支持伪类选择器
- ERROR: Selector
.login-button[disabled]is not supported. uvue only support classname selector
-
font-weight 限制
- font-weight 只支持: normal | bold | 400 | 700
- 不支持: 100, 300, 500, 600, 900 等其他数值
-
border-radius 限制
- border-radius 不支持百分比单位 (如 50%)
- 需要使用具体的像素值 (如 border-radius: 9px 实现圆形)
-
line-height 限制
- line-height 不支持 normal 值
- 需要使用具体的数值或像素值 (如 line-height: 36px 或 line-height: 1.5)
-
position 限制
- position 不支持 sticky 值
- 只支持: relative | absolute | fixed
-
不支持的 CSS 属性
- outline 属性不支持
- aspect-ratio 属性不支持,需要用具体的 width 和 height 值
- text-decoration 属性不支持(如 line-through)
- align-items 不支持 baseline 值,只支持 center | flex-start | flex-end | stretch
- 不支持后代选择器(如 .parent text),只支持类名选择器
-
其他样式
- WARNING:
backdrop-filteris not a standard property name - style property
white-spaceis only supported on<text>|<button> - ERROR: property value
allis not supported fortransition-property
- WARNING:
================================================================================ 六、UTS 模板表达式限制
-
条件语句必须使用布尔类型
- 错误码: UTS110111120
- 不支持 || 运算符的隐式类型转换
- 错误写法: {{ value || '默认值' }}
- 正确写法: {{ value != null && value != '' ? value : '默认值' }}
-
模板中的类型判断
- 所有条件表达式必须返回布尔值
- 不能使用 truthy/falsy 值作为条件
================================================================================ 七、scroll-view 使用
- scroll-view 在 uni-app-x 中不是用 scroll-y=true
- 而是要用 direction="vertical"
================================================================================ 八、异步与回调
- uni.showModal 的 success 回调不能是 async 函数
- 解决方案:创建独立的 async 函数,在回调中调用
- UTS 中箭头函数 () => {} 有时会导致 "Parenthesized expression cannot be empty" 错误
- 解决方案:使用普通函数 function name(): Type {} 代替箭头函数
================================================================================ 九、响应式数据
- 对于需要整体替换的数组,推荐使用 ref 而非 reactive
- 使用 ref 时,通过 .value 进行整体替换可以正确触发响应式更新
- ref 数组元素不能直接整体替换,需要修改元素属性
- 对于可能为 null 的参数,需要显式检查后再传递给函数
================================================================================ 十、类型导入
- 类型导入需要使用 type 关键字
- 一般情况下,尽可能用强类型模式
- uni_modules 的情况下,尽量把 type 定义到 interface 里面
- 数据获取争夺都用强类型方式,查询或 rpc 查询用 supa.from.executeAs() 方式
- 返回的是 result,resultdata 一般可以 as Array
================================================================================ 十一、常见错误速查
- "Unresolved reference" - 函数未定义或顺序错误
- "Cannot create an instance of an abstract class" - Record<K,V> 不能实例化
- "Assignment type mismatch" - 类型不匹配,需要显式类型转换
- "if condition must be boolean" - if 条件必须是 boolean 类型
- "Index Signature is not supported" - 不支持索引签名
- "Intersection Type is not supported" - 不支持交叉类型
- "Parenthesized expression cannot be empty" - 箭头函数问题,改用普通函数
- "找不到名称" - 属性访问问题,需转换为 UTSJSONObject
- "参数类型不匹配" - 参数类型错误,需要显式类型转换
================================================================================ 十二、简明速记(100+条)
- 表单优先用 form 组件
- 跟 template 交互的变量尽量用一维变量
- 不要用 forEach、map、safeget,数组遍历用 for 循环,类型转换用 UTSJSONObject
- 数组类型建议写成 Array,不要用 Type[] 简写
- 不支持 undefined,变量未赋值就是 null,判断用 != null
- 变量声明只能用 let 或 const,不能用 var
- 判断空要用 !== null,不能用 !变量
- 只支持 type,不建议用 interface
- for 循环的 i 必须写明类型:let i: number = 0
- 逻辑或用 ||,空值合并用 ??,不能混用
- if 判断只能是 boolean 类型
- 不支持索引签名(Index Signature)
- 类型推断严格,必要时用 as Type 明确类型
- 不支持 Intersection Type
- picker 用 picker-view 或 uni.showActionSheet 替代
- 样式只支持 display: flex,不支持 gap、grid、calc()、伪类选择器、display: block
- scroll-view 用 direction="vertical"
- 不支持 table、grid、vh/vw 单位、min-height/max-height 百分比等
- font-weight 只支持 normal/bold/400/700,不支持 300/500/600 等数值
- border-radius 不支持百分比(50%),用具体像素值(9px)实现圆形
- line-height 不支持 normal,用具体数值(36px)或倍数(1.5)
- position 不支持 sticky,只支持 relative/absolute/fixed
- 不支持 outline、aspect-ratio、text-decoration 等 CSS 属性
- align-items 不支持 baseline,只支持 center/flex-start/flex-end/stretch
- 组件事件如 picker-view 用 UniPickerViewChangeEvent
- 时间选择用 uni_modules/lime-date-time-picker
- 类型转换建议用 utils/utis 下的 UTSJSONObject
- 在 uts setup 的 android 模式下,调用的函数必须在调用之前定义
- 箭头函数不支持默认参数值,改用显式传参或普通函数定义
- 不支持 Number()、String() 构造函数,用 as 类型转换
- 不支持 Object.keys(),用 JSON.stringify() 或 for 循环
- 不支持 typeof xxx === 'function',用 try-catch 替代
- 不支持 as unknown as 语法
- parseInt() 参数必须是 string 类型
- decodeURIComponent() 返回可空类型,需要处理 null
- charCodeAt() 返回可空类型,需要处理 null
- 不支持内联对象类型,需要在 types 文件中单独定义
- UTSJSONObject.get() 返回可空类型 Any?,需要处理 null 并转换为具体类型
- Array 元素不能直接访问属性,需转换为 UTSJSONObject 或定义明确类型
- 类型定义中属性可能为 null 时,必须声明为可空类型(如 any | null)
- switch 语句在某些版本可能有问题,建议用 if-else 替代
- 模板中可选链 ?.length 需要改为显式判断:v-if="arr != null && arr.length > 0"
- 解构赋值 const { data, error } 在 UTS 中可能有问题,建议用 response.data 方式访问
- response.data 返回 Any?,赋值前需要判断 null 并类型转换
- 空数组 [] 无法推断类型,需要显式声明:let arr: Array = [] 或先判断 null 再转换
- ref 对象字面量需要定义类型:const obj = ref({...} as MyType),否则属性访问会报错
- if 条件必须是 boolean,可空类型要用 != null 判断,if (obj != null) 而非 if (obj)
- throw 语句不能抛出 Any 类型,需要处理错误而非抛出
- 模板中可选链 ?.property 需要改为三元表达式:obj != null ? obj.property : ''
- ref 在模板中无法访问属性,必须定义明确类型,ref<MyType | null>(null)
- 展开运算符 [...arr] 不支持,需要手动复制数组
- Array.from(new Set()) 不支持,需要手动去重
- Promise.all 可能有问题,建议改为顺序执行
- .sort(() => Math.random() - 0.5) 随机排序不支持,需要手动实现
- 数组索引访问 arr[index] 可能越界,建议用 if-else 替代数组查找
- 事件对象 e.detail.value 需要转换为 UTSJSONObject 后访问
- String() 构造函数不支持,用 as string 类型转换
- 数组类型简写 string[] 需要改为 Array
- any 类型参数不能直接访问属性,需要转换为 UTSJSONObject 后使用 get/getString/getNumber 方法
- 模板中 !变量 取反不支持,改为显式判断:v-if="str == ''" 或 v-if="bool == false"
- 模板中 :class="{ 'class': condition }" 对象语法可能有问题,改为三元表达式::class="condition ? 'class' : ''"
- supabase .update() 参数需要 UTSJSONObject 类型,用 new UTSJSONObject() 创建并用 .set() 设置属性
- ref<Array> 在模板中无法访问元素属性,必须定义明确的类型后才能访问
- 函数参数可以用联合类型:func(item: TypeA | TypeB)
- JSON.stringify(UTSJSONObject) 可能有问题,需要手动拼接字符串
- UTSJSONObject.keys() 方法不存在,无法获取键列表
- 联合类型参数不能直接访问属性,需要先类型转换:const id = (item as TypeA).id
- 某些 uni API 可能不存在(如 navigateToMiniProgram),需要检查或替换
- JSON.parse(JSON.stringify(obj)) 复杂转换可能有问题,简化处理
- showModal success 回调不能是 async 函数,需要改为同步或使用 Promise
- supa.auth.signOut() 等 supabase auth 方法可能不支持,需要简化处理
- 模板中可空字符串判断 userInfo.phone ? 改为 userInfo.phone != null && userInfo.phone != ''
- .then() 回调可能有问题,建议用 async/await 或直接调用
- let res: any = null 不支持,改为 let res: any = {} 或其他默认值
- 类型定义中没有的字段不能赋值,检查类型定义后移除多余字段
- ref<Array> 在模板中无法访问元素属性,必须定义明确的类型
- 模板中复杂表达式如 parseFloat(String(x)) 不支持,简化为直接比较
- forEach 不支持,改用 for 循环
- any 类型数组元素不能直接访问属性,需转换为 UTSJSONObject
- showModal success 回调不能是 async 函数,需要改为同步调用独立 async 函数
- 被生命周期钩子调用的函数必须在钩子之前定义,包括 onMounted、watch、onUnmounted 等
- 箭头函数不支持默认参数值,改用显式传参或普通函数定义
- 对象字面量赋值给 ref 需要显式类型声明:const obj: Type = {...} as Type
- 模板中访问对象属性时,类型定义必须包含该属性,否则报 "找不到名称" 错误
- !variable 取反操作不支持,改为 variable == '' 或 variable == false
- supa.auth 方法不支持,需要简化处理或移除
- setInterval 回调中使用外部变量,需要先声明:let timer: number = 0,然后在回调中赋值
- $t() 国际化函数在模板中可能有问题,建议使用硬编码文本或自定义翻译函数
- profile.username ?? $t('xxx') 混合表达式不支持,改为条件判断:profile != null && profile.username != null ? profile.username : '默认值'
- 可选链操作符 ?. 在某些场景不支持,如 currentPage?.options,需要改为 if 判断
- as any[] 类型转换后无法访问属性,需要使用正确的类型如 UTSJSONObject
- 可空类型 string | null 传给需要 string 的函数,需要显式类型转换:redirect as string
- setInterval 回调中修改外部变量,需要用 ref 而不是 let 声明变量,避免 smart cast 问题
- 非空断言操作符 ! 在某些场景仍无法解决类型问题,建议简化逻辑避免复杂类型转换
- decodeURIComponent 函数参数类型严格,可空类型即便使用 ! 也可能报错,建议简化或避免使用
- getCurrentPages() 获取页面 options 复杂且容易出错,建议简化跳转逻辑
- 模板中内联箭头函数不支持类型注解,如 @input="(e: any) => ..." 会报错,改用 v-model
- :class="{ disabled: codeDisabled }" 对象语法可能有问题,改为三元表达式 :class="codeDisabled ? 'disabled' : ''"
- 使用外部类型定义时,确保所有属性都有默认值,避免 null 导致类型不匹配
- Supabase insert/update 在 .uvue 文件中直接调用可能报类型错误,建议封装到 .uts 服务文件中调用
- 可空类型属性在模板中使用时需要处理 null:profile.gender ?? 'other'
- 可空数字类型比较前需要先检查 null:profile.height_cm != null && profile.height_cm > 0
- CSS 伪类选择器 :last-child、:first-child、:nth-child() 不支持,需要移除
- Supabase 批量 insert 数组参数类型不匹配,需要改为循环逐条 insert
- CSS 中独立的属性(没有选择器)会导致编译错误:Return type mismatch: expected 'Map<String, Map<String, Map<String, Any>>>'
- Supabase 查询整数字段用字符串形式:.eq('status', '1') 而非 .eq('status', 1)
- Supabase insert 整数字段也用字符串:target_type: '1' 而非 target_type: 1
- 数据库返回的字段类型可能不符,需要用 typeof 检查后安全转换
- UUID 字段可能返回 Integer,需要安全转换:typeof val === 'number' ? val.toString() : val
- 使用辅助函数 safeGetString、safeGetNumber 处理数据库字段
================================================================================ 十三、构造函数限制
- 不支持 Number() 构造函数,使用 as number 类型转换
- 不支持 String() 构造函数,使用 as string 类型转换
- 示例:Number(x) → x as number,String(i) → i as string
================================================================================ 十四、取反操作符限制
- 不支持 !变量 的取反操作符用于判断空
- 字符串判断空:variable == null || variable === ''
- 示例:!this.selectedSkuId → (this.selectedSkuId == null || this.selectedSkuId === '')
================================================================================ 十五、parseInt/parseFloat 限制
- parseInt() 参数必须是 string 类型
- 如果变量是 number 类型,直接使用,不要调用 parseInt
- 示例:parseInt(this.quantity) 错误,quantity 是 number,直接用 this.quantity
================================================================================ 十六、Object.keys() 不支持
- 不支持 Object.keys() 方法
- 替代方案:使用 JSON.stringify() 或 for 循环遍历
- UTSJSONObject 可用 .keys() 方法(但某些版本可能不支持)
================================================================================ 十七、typeof 函数检查不支持
- 不支持 typeof xxx === 'function' 语法
- 替代方案:使用 try-catch 包裹方法调用
================================================================================ 十八、as unknown as 语法不支持
- 不支持 as unknown as 双重类型转换
- 直接使用 as 目标类型:obj as UTSJSONObject
================================================================================ 十九、可空类型方法返回值
- decodeURIComponent() 返回 String?,需要处理 null
- charCodeAt() 返回 Number?,需要处理 null
- 示例:const code = str.charCodeAt(i); if (code != null) { ... }
================================================================================ 二十、内联对象类型不支持
- 不支持 Array<{id: string, name: string}> 这种内联类型定义
- 需要在 types 文件中单独定义 type
- 示例:定义 type ItemType = { id: string, name: string },然后使用 Array
================================================================================ 二十一、eventChannel 不支持
- uni.navigateTo 的 success 回调中 res.eventChannel 不支持
- 替代方案:使用 Storage 或全局变量传递数据
================================================================================ 二十二、链式调用问题
- .map().join() 链式调用可能导致类型推断失败
- 替代方案:使用 for 循环或分步处理
================================================================================ 二十三、v-model 类型限制
- input 的 v-model 期望 string 类型
- 如果变量是 number,使用 :value="variable.toString()" 替代 v-model
================================================================================ 二十四、setup 模式函数定义顺序(重要)
在 <script setup lang="uts"> 中,函数定义顺序至关重要:
-
基本原则
- 函数必须在调用之前定义(不支持 JavaScript 的函数提升)
- 这与 JavaScript 的行为不同,UTS 更接近 C/Java 的编译方式
-
watch 和 onMounted 中的函数调用
- watch() 和 onMounted() 是立即执行的
- 它们内部调用的函数必须在 watch/onMounted 之前定义
- 错误示例:
// 错误:resetData 和 loadRefunds 还没定义 watch(activeTab, () => { resetData() loadRefunds() }) const resetData = () => { ... } const loadRefunds = async () => { ... } - 正确示例:
// 正确!先定义函数 const resetData = () => { ... } const loadRefunds = async () => { ... } // 再调用 watch/onMounted watch(activeTab, () => { resetData() loadRefunds() }) onMounted(() => { loadRefunds() })
-
推荐的代码组织顺序:
<script setup lang="uts"> // 1. 导入语句 import { ref, watch, onMounted } from 'vue' import { supabaseService } from '@/utils/supabaseService' // 2. 类型定义 type MyType = { id: string, name: string } // 3. 响应式变量声明 const data = ref<Array<MyType>>([]) const loading = ref<boolean>(false) // 4. 工具函数(不依赖其他函数的) const getUserId = (): string => { ... } // 5. 业务函数(可能依赖工具函数) const loadData = async () => { ... } const resetData = () => { ... } // 6. 事件处理函数 const handleClick = () => { ... } // 7. 生命周期钩子和 watch(放在最后) watch(data, () => { ... }) onMounted(() => { ... }) </script> -
编译错误提示
- 错误信息:"找不到名称 xxx"
- 原因:函数在调用之后定义
- 解决:调整函数定义顺序,确保被调用的函数先定义
================================================================================ 二十五、箭头函数限制(重要)
-
箭头函数不支持默认参数值
- 错误示例:
const loadData = async (page: number = 1) => { ... } // 错误! - 正确示例:
// 方案1:不使用默认参数,调用时显式传参 const loadData = async (page: number): Promise<void> => { ... } loadData(1) // 调用时传参 // 方案2:使用普通函数定义 function loadData(page: number = 1): Promise<void> { ... }
- 错误示例:
-
箭头函数需要明确返回类型
- 建议显式声明返回类型:
: Promise<void>或: string等 - 示例:
const getData = async (): Promise<string> => { ... }
- 建议显式声明返回类型:
-
编译错误提示
- 错误信息:"Anonymous functions cannot specify default values for their parameters"
- 原因:箭头函数使用了默认参数
- 解决:移除默认参数,改用显式传参或普通函数定义
================================================================================ 二十六、数组元素属性访问(重要)
-
Array 元素属性访问问题
- 数组元素类型为 any 时,不能直接访问属性
- 错误示例:
const steps: Array<any> = [...] steps[1].time = 'xxx' // 错误!找不到名称 "time" - 正确示例:
const steps: Array<any> = [...] const step = steps[1] as UTSJSONObject step.set('time', 'xxx')
-
推荐方案:定义明确的类型
type StepType = { status: number, title: string, time: string, desc: string } const steps: Array<StepType> = [...] steps[1].time = 'xxx' // 正确! -
编译错误提示
- 错误信息:"找不到名称 xxx"
- 原因:any 类型数组元素无法直接访问属性
- 解决:转换为 UTSJSONObject 或定义明确类型
================================================================================ 二十七、类型定义与可空类型(重要)
-
类型定义中可空类型的处理
- 如果属性可能为 null,必须显式声明为可空类型
- 错误示例:
type ItemType = { sku_specifications: any // 错误!如果值可能为 null } const spec = getSpec() // 返回 Any? item.sku_specifications = spec // 类型不匹配 - 正确示例:
type ItemType = { sku_specifications: any | null // 正确! } const specRaw = getSpec() const spec = (specRaw != null) ? (specRaw as any) : null item.sku_specifications = spec
-
UTSJSONObject.get() 返回值处理
- get() 方法返回 Any? 类型
- 必须先判断 null,再进行类型转换
- 示例:
const raw = obj.get('field') const value = (raw != null) ? (raw as string) : null
-
编译错误提示
- 错误信息:"参数类型不匹配:实际类型为 'Any?',预期类型为 'Any'"
- 原因:可空类型不能直接赋值给非空类型
- 解决:修改类型定义为可空类型,或处理 null 情况
================================================================================ 二十八、模板中的可空类型处理(重要)
-
模板中可选链限制
- 模板中 ?.length 等可选链操作可能报错
- 错误示例:
<view v-if="refund.status_history?.length > 0"> - 正确示例:
<view v-if="refund.status_history != null && refund.status_history.length > 0">
-
编译错误提示
- 错误信息:"Operator call is prohibited on a nullable receiver of type 'Number?'"
- 原因:可空类型不能直接调用操作符
- 解决:显式判断 null 后再访问属性
================================================================================ 二十九、解构赋值限制(重要)
-
UTS 中解构赋值可能有问题
- 错误示例:
const { data, error } = await supa.from('table').select('*') - 正确示例:
const response = await supa.from('table').select('*').execute() const data = response.data const error = response.error
- 错误示例:
-
推荐使用 .execute() 获取完整响应
- 使用 response.data 和 response.error 访问结果
- 避免使用解构赋值
================================================================================ 三十、API 响应数据处理(重要)
-
response.data 返回 Any? 类型
- 错误示例:
order.value = orderRes.data // 错误,Any? 不能赋值给 Any const itemsArray = itemsRes.data ?? [] // 错误!无法推断空数组类型 - 正确示例:
if (orderRes.data != null) { order.value = orderRes.data as UTSJSONObject } const rawData = itemsRes.data let itemsArray: Array<any> = [] if (rawData != null) { itemsArray = rawData as Array<any> }
- 错误示例:
-
空数组类型推断问题
[] ?? []或data ?? []无法推断类型- 必须显式声明数组类型
- 示例:
// 错误 const arr = data ?? [] // 正确 let arr: Array<any> = [] if (data != null) { arr = data as Array<any> }
-
编译错误提示
-
错误信息:"Assignment type mismatch: actual type is 'Any?', but 'Any' was expected"
-
原因:可空类型 Any? 不能直接赋值给非空类型
-
解决:先判断 null,再进行类型转换
-
错误信息:"Cannot infer type for this parameter" 或 "Not enough information to infer type"
-
原因:空数组无法推断类型
-
解决:显式声明数组类型
-
================================================================================ 三十一、ref 对象字面量类型(重要)
-
ref 对象字面量必须定义类型
- 错误示例:
const merchantRating = ref({ description: 5, logistics: 5, service: 5 }) merchantRating.value.description = rating // 错误!找不到名称 "description" - 正确示例:
type MerchantRatingType = { description: number logistics: number service: number } const merchantRating = ref<MerchantRatingType>({ description: 5, logistics: 5, service: 5 } as MerchantRatingType) merchantRating.value.description = rating // 正确!
- 错误示例:
-
编译错误提示
- 错误信息:"找不到名称 xxx"
- 原因:ref 对象字面量没有显式类型定义
- 解决:定义 type 并在 ref 中指定泛型类型
================================================================================ 三十二、if 条件与可空类型(重要)
-
if 条件必须是 boolean 类型
- 错误示例:
if (merchant.value) { ... } // 错误,MerchantType? 不是 boolean - 正确示例:
if (merchant.value != null) { ... } // 正确!
- 错误示例:
-
可空类型不能直接用于 if 条件
- 必须使用 != null 或 !== null 判断
- 示例:
// 错误 if (obj) { ... } // 正确 if (obj != null) { ... }
-
编译错误提示
- 错误信息:"Condition type mismatch: inferred type is 'XXX?' but 'Boolean' was expected"
- 原因:可空类型不能直接用于 if 条件
- 解决:使用 != null 判断
================================================================================ 三十三、throw 语句限制(重要)
-
throw 语句不能抛出 Any 类型
- 错误示例:
const { error } = await supa.from('table').insert(data) if (error !== null) { throw error // 错误,Any 类型不能抛出 } - 正确示例:
const res = await supa.from('table').insert(data).execute() if (res.error != null) { console.error('操作失败:', res.error) uni.showToast({ title: '操作失败', icon: 'none' }) return // 处理错误而非抛出 }
- 错误示例:
-
推荐错误处理方式
- 记录错误日志
- 显示用户提示
- 返回或终止操作
-
编译错误提示
- 错误信息:"类型不匹配 推断类型是 'Any',但预期的是'Throwable'"
- 原因:UTS 中 throw 只能抛出 Throwable 类型
- 解决:处理错误而非抛出,或创建 Error 对象
================================================================================ 三十四、模板中的可选链与属性访问(重要)
-
模板中可选链限制
- 错误示例:
<text>{{ order?.order_no }}</text> <text>{{ formatTime(order?.created_at) }}</text> - 正确示例:
<text>{{ order != null ? order.order_no : '' }}</text> <text>{{ formatTime(order != null ? order.created_at : '') }}</text>
- 错误示例:
-
ref 在模板中无法访问属性
- 错误示例:
const order = ref<any>({})<text>{{ order?.order_no }}</text> <!-- 错误!找不到名称 --> - 正确示例:
type OrderType = { id: string order_no: string created_at: string } const order = ref<OrderType | null>(null)<text>{{ order != null ? order.order_no : '' }}</text>
- 错误示例:
-
编译错误提示
- 错误信息:"找不到名称 xxx"
- 原因:any 类型或可选链在模板中无法正确推断属性
- 解决:定义明确类型,使用三元表达式代替可选链
================================================================================ 三十五、数组操作限制(重要)
-
展开运算符 [...arr] 不支持
- 错误示例:
const shuffled = [...allGuessItems.value] searchResults.value.push(...newItems) - 正确示例:
// 复制数组 const arr: Array<any> = [] for (let i: number = 0; i < allGuessItems.value.length; i++) { arr.push(allGuessItems.value[i]) } // 追加元素 for (let i: number = 0; i < newItems.length; i++) { searchResults.value.push(newItems[i]) }
- 错误示例:
-
Array.from(new Set()) 不支持
- 错误示例:
const uniqueNames = Array.from(new Set(names)) - 正确示例:
// 手动去重 const uniqueNames: Array<string> = [] for (let i: number = 0; i < names.length; i++) { let found = false for (let j: number = 0; j < uniqueNames.length; j++) { if (uniqueNames[j] === names[i]) { found = true break } } if (found === false) { uniqueNames.push(names[i]) } }
- 错误示例:
-
Map.entries() 和 Map.keys() 不支持,需使用forEach遍历
- 错误示例:
const keywordCount = new Map<string, number>() // ... 填充数据 ... // 错误:entries() 不能作为函数调用 const sortedKeywords = Array.from(keywordCount.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, limit) .map(entry => entry[0]) // 错误:keys() 也不能作为函数调用 const keySet = keywordCount.keys() for (let i = 0; i < keySet.length; i++) { ... } - 正确示例:
const keywordCount = new Map<string, number>() // ... 填充数据 ... // 定义类型用于存储键值对 type KeywordEntry = { keyword: string count: number } // 使用forEach遍历Map(UTS支持) const entryArray: KeywordEntry[] = [] keywordCount.forEach((value: number, key: string) => { entryArray.push({ keyword: key, count: value }) }) // 排序时需要明确类型注解 entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => { return b.count - a.count }) // 提取结果 const sortedKeywords: string[] = [] const maxCount = Math.min(entryArray.length, limit) for (let i: number = 0; i < maxCount; i++) { sortedKeywords.push(entryArray[i].keyword) }
- 错误示例:
-
Supabase upsert 方法不支持,需使用 insert
- 错误示例:
await supa .from('ml_browse_history') .upsert(browseRecord) .execute() - 正确示例:
// 使用 insert 替代 upsert await supa .from('ml_browse_history') .insert(browseRecord) .execute()
- 错误示例:
-
.sort() 带回调函数需要明确类型注解
- 错误示例:
const shuffled = arr.sort(() => Math.random() - 0.5) - 正确示例(自定义排序需要类型注解):
// Fisher-Yates 洗牌算法(推荐用于随机排序) for (let i: number = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) const temp = arr[i] arr[i] = arr[j] arr[j] = temp } - 或者使用带类型注解的sort回调:
type SortableItem = { id: string count: number } const items: SortableItem[] = [...] // 必须明确参数类型和返回类型 items.sort((a: SortableItem, b: SortableItem): number => { return b.count - a.count })
- 错误示例:
================================================================================ 三十五、异步操作限制(重要)
-
Promise.all 可能有问题
- 错误示例:
const [prodResp, shopResp] = await Promise.all([ supabaseService.searchProducts(keyword, page, limit), supabaseService.searchShops(keyword) ]) - 正确示例:
const prodResp = await supabaseService.searchProducts(keyword, page, limit) const shopResp = await supabaseService.searchShops(keyword)
- 错误示例:
-
解构赋值可能有问题
- 错误示例:
const [prodResp, shopResp] = await Promise.all([...]) - 正确示例:
const prodResp = await func1() const shopResp = await func2()
- 错误示例:
================================================================================ 三十六、事件对象处理(重要)
-
事件对象属性访问
- 错误示例:
const onInput = (e: any) => { const val = e.detail.value // 错误!找不到名称 "detail" } - 正确示例:
const onInput = (e: any) => { const eObj = e as UTSJSONObject const detailRaw = eObj.get('detail') const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject()) const val = detail.getString('value') ?? '' }
- 错误示例:
-
编译错误提示
- 错误信息:"找不到名称 xxx"
- 原因:any 类型事件对象无法直接访问属性
- 解决:转换为 UTSJSONObject 后使用 .get() 方法
================================================================================ 三十七、数组索引访问限制(重要)
-
数组索引访问可能越界
- 错误示例:
const texts = ['非常差', '差', '一般', '好', '非常好'] return texts[rating - 1] ?? '未评价' // 可能越界或类型推断失败 - 正确示例:
if (rating === 1) return '非常差' if (rating === 2) return '差' if (rating === 3) return '一般' if (rating === 4) return '好' if (rating === 5) return '非常好' return '未评价'
- 错误示例:
-
推荐使用 if-else 或 Map 替代数组索引查找
================================================================================ 三十八、函数可选参数限制(重要)
-
可选参数不能跳过传递
- UTS Android 不支持跳过可选参数传递
- 如果函数有多个可选参数,必须按顺序传递所有参数
- 错误示例:
// 函数定义 async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise<boolean> // 错误调用 - 跳过了 merchantId 参数 await supabaseService.addToCart(productId, 1, '') // 编译错误:No value passed for parameter 'merchantId' - 正确示例:
// 方案1:给可选参数添加默认值 async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise<boolean> // 方案2:调用时传递所有参数 await supabaseService.addToCart(productId, 1, '', '')
-
可选参数定义规范
- 推荐使用
param: Type = defaultValue而非param?: Type param?: Type在 Android 端调用时仍需传递参数param: Type = defaultValue可以在不传参时使用默认值- 示例:
// 不推荐 - 调用时仍需传递参数 function foo(a: string, b?: string, c?: string): void // 推荐 - 可以跳过参数使用默认值 function foo(a: string, b: string = '', c: string = ''): void
- 推荐使用
-
编译错误提示
- 错误信息:"No value passed for parameter 'xxx'"
- 原因:可选参数在 Android 端不能跳过
- 解决:
- 修改函数签名,使用默认值
param: Type = defaultValue - 调用时传递所有参数
- 修改函数签名,使用默认值
-
最佳实践
- 对于有多个可选参数的函数,统一使用默认值语法
- 调用时显式传递所有参数,避免依赖可选参数跳过
- 在服务层函数定义中,优先使用
= ''或= 0等默认值
================================================================================ 三十九、模板中的非空断言限制(重要)
-
模板中不支持非空断言操作符
!- UTS Android 模板中不能使用
variable!非空断言 - 错误示例:
<text v-if="product.original_price != null && product.original_price! > product.price"> - 正确示例:
<text v-if="product.original_price != null && product.original_price > product.price">
- UTS Android 模板中不能使用
-
编译错误提示
- 错误信息:"参数类型不匹配:实际类型为 'Number?',预期类型为 'Number'"
- 原因:模板中使用非空断言
!不被支持 - 解决:移除非空断言
!,直接使用变量进行比较
-
最佳实践
- 在模板中,先用
!= null判断可空类型,然后直接使用变量 - UTS 编译器会在
!= null判断后自动识别变量为非空类型
- 在模板中,先用
================================================================================ 四十、未导入类型的处理(重要)
-
未导入的类型不能直接使用
- 在页面中使用的类型必须先导入或使用 UTSJSONObject 替代
- 错误示例:
// Shop 类型未导入 const s = shopRespData[i] as Shop const id = s.id // 找不到名称 "id" - 正确示例:
// 使用 UTSJSONObject const s = shopRespData[i] as UTSJSONObject const id = s.getString('id') ?? '' const name = s.getString('shop_name') ?? ''
-
编译错误提示
- 错误信息:"找不到名称 'XXX'"
- 原因:类型未导入或类型定义不存在
- 解决:
- 导入需要的类型:
import { Shop } from '@/utils/supabaseService.uts' - 使用 UTSJSONObject 替代:
as UTSJSONObject然后用.getString()、.getNumber()访问属性
- 导入需要的类型:
-
最佳实践
- 对于简单的数据转换,推荐使用 UTSJSONObject
- 避免在多个文件中重复定义相同的类型
- 如果需要类型安全,从服务层导入类型定义
================================================================================ 四十一、服务层数据字段完整性(重要)
-
服务层返回数据必须包含所有必要字段
- 从数据库获取数据时,必须正确映射所有需要的字段
- 错误示例:
const product: Product = { id: prodObj.getString('id') ?? '', name: prodObj.getString('name') ?? '', // 错误:merchant_id 硬编码为空字符串 merchant_id: '' } as Product - 正确示例:
const product: Product = { id: prodObj.getString('id') ?? '', name: prodObj.getString('name') ?? '', // 正确:从数据库获取 merchant_id merchant_id: prodObj.getString('merchant_id') ?? '' } as Product
-
调用服务层方法时必须传递完整参数
- 页面调用服务层方法时,需要传递所有必要参数
- 错误示例:
// 错误:merchant_id 传空字符串 await supabaseService.addToCart(productId, 1, '', '') - 正确示例:
// 正确:从商品对象获取 merchant_id const merchantId = product.merchant_id ?? '' await supabaseService.addToCart(productId, 1, '', merchantId)
-
编译错误提示
- 问题表现:数据添加到数据库失败,或添加的数据不完整
- 原因:服务层或页面层缺少必要字段的传递
- 解决:
- 检查服务层数据映射是否完整
- 检查页面调用时是否传递了所有必要参数
-
最佳实践
- 服务层方法返回的对象应包含数据库视图的所有字段
- 页面调用服务层方法时,应从数据对象中获取并传递所有参数
- 对于关联数据(如 merchant_id),确保在数据加载时一并获取
================================================================================ 四十二、模板中的非运算符限制(重要)
-
模板中不支持
!非运算符- UTS Android 模板中不能使用
!variable非运算符 - 错误示例:
<view v-if="!brand.logo_url"> - 正确示例:
<view v-if="brand.logo_url == null || brand.logo_url == ''">
- UTS Android 模板中不能使用
-
编译错误提示
- 错误信息:"找不到名称 not'"
- 原因:模板中不支持非运算符
! - 解决:使用显式的比较表达式替代
-
最佳实践
- 使用
== null或== ''检查空值 - 使用
!= null && != ''检查非空值
- 使用
================================================================================ 四十三、索引访问限制(重要)
-
不支持
(obj as any)['key']索引访问方式- UTS Android 不支持对 any 类型使用索引访问
- 错误示例:
const detail = (e as any)['detail'] val = detail['value'] ?? '' - 正确示例:
// 方案1:使用 UTSJSONObject const eObj = JSON.parse(JSON.stringify(e)) as UTSJSONObject const detail = eObj.get('detail') as UTSJSONObject val = detail.getString('value') ?? '' // 方案2:先判断类型再转换 if (e instanceof UTSJSONObject) { const eObj = e as UTSJSONObject const detail = eObj.get('detail') as UTSJSONObject val = detail.getString('value') ?? '' }
-
编译错误提示
- 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch"
- 原因:any 类型不支持索引访问
- 解决:转换为 UTSJSONObject 后使用
.get()方法
-
最佳实践
- 统一使用 UTSJSONObject 处理动态对象
- 使用
.get()、.getString()、.getNumber()方法访问属性 - 对于复杂对象,先用
JSON.parse(JSON.stringify(obj))转换
================================================================================ 四十四、字符串不能直接作为布尔条件(重要)
-
字符串不能直接作 if 条件
- UTS Android 不支持将字符串直接作为布尔条件判断
- 错误示例:
const paramId = '123' if (paramId) { // 错误:字符串不能直接作为布尔条件 // ... } - 正确示例:
const paramId = '123' if (paramId != null && paramId != '') { // 正确:显式判断 // ... }
-
编译错误提示
- 错误信息:"Condition type mismatch: inferred type is 'String' but 'Boolean' was expected"
- 原因:字符串类型不能直接作为布尔条件
- 解决:使用显式的比较表达式
-
最佳实践
- 使用
!= null && != ''检查字符串非空 - 使用
== null || == ''检查字符串为空
- 使用
================================================================================ 四十五、联合类型属性访问(重要)
-
联合类型不能直接访问属性
- 当参数类型为联合类型(如
A | B)时,不能直接访问属性 - 错误示例:
type A = { id: string, name: string } type B = { id: string, title: string } const foo = (item: A | B) => { const id = item.id // 错误:联合类型不能直接访问属性 } - 正确示例:
const foo = (item: A | B) => { // 方案1:转换为 UTSJSONObject const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const id = obj.getString('id') ?? '' // 方案2:使用类型守卫 if ('name' in item) { const id = item.id // 此时类型已收窄为 A } }
- 当参数类型为联合类型(如
-
编译错误提示
- 错误信息:"找不到名称 xxx'"
- 原因:联合类型的属性访问受限
- 解决:转换为 UTSJSONObject 或使用类型守卫
-
最佳实践
- 对于联合类型参数,统一转换为 UTSJSONObject 处理
- 使用
.getString()、.getNumber()等方法安全访问属性
================================================================================ 四十六、any 类型变量不能赋值为 null(重要)
-
any 类型变量不能赋值为 null
- UTS Android 中
any类型不能赋值为null - 错误示例:
let res: any = null // 错误:Null cannot be a value of a non-null type 'Any' - 正确示例:
let res: any = {} // 正确:使用空对象 // 或者 let res: any | null = null // 使用联合类型
- UTS Android 中
-
编译错误提示
- 错误信息:"Null cannot be a value of a non-null type 'Any'"
- 原因:any 类型不允许 null 值
- 解决:使用空对象
{}或联合类型any | null
================================================================================ 四十七、对象字面量类型推断问题(重要)
-
对象字面量直接赋值给 ref 可能类型不匹配
- 当对象字面量直接赋值给特定类型的 ref 时,可能报类型不匹配错误
- 错误示例:
merchant.value = { id: shop.id, user_id: shop.merchant_id, // ... } // 错误:Assignment type mismatch - 正确示例:
// 方案1:显式声明类型 const merchantData: MerchantType = { id: shop.id, user_id: shop.merchant_id, // ... } merchant.value = merchantData // 方案2:使用 as 类型断言 merchant.value = { id: shop.id, user_id: shop.merchant_id, // ... } as MerchantType
-
编译错误提示
- 错误信息:"Assignment type mismatch: actual type is '', but 'XXX' was expected"
- 原因:对象字面量被推断为匿名类型
- 解决:显式声明类型或使用类型断言
================================================================================ 四十八、any 类型不能直接访问属性(重要)
-
any 类型参数不能直接访问属性
- 在 map、forEach 等回调中,any 类型的参数不能直接访问属性
- 错误示例:
const list = rawList.map((item): ProductType => { const id = item.id // 错误:找不到名称 "id" const name = item.name // 错误:找不到名称 "name" }) - 正确示例:
const list = rawList.map((item: any): ProductType => { // 方案1:转换为 UTSJSONObject const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const id = itemObj.getString('id') ?? '' const name = itemObj.getString('name') ?? '' // 方案2:显式标注参数类型并使用索引 // 注意:这种方式在 UTS Android 中也可能有问题 })
-
编译错误提示
- 错误信息:"找不到名称 xxx'"
- 原因:any 类型的属性访问受限
- 解决:转换为 UTSJSONObject 后使用
.getString()等方法
================================================================================ 四十九、类型断言不会添加方法(重要)
-
as UTSJSONObject不会给对象添加方法- 使用
as UTSJSONObject只是类型断言,不会让普通对象获得getString等方法 - 错误示例:
const profileObj = profile as UTSJSONObject const id = profileObj.getString('user_id') // 运行时错误:getString is not a function - 正确示例:
// 必须使用 JSON.parse(JSON.stringify()) 进行真正的转换 const profileObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject const id = profileObj.getString('user_id') ?? ''
- 使用
-
运行时错误提示
- 错误信息:"XXX is not a function"
- 原因:类型断言只是编译时行为,不会改变运行时对象的方法
- 解决:使用
JSON.parse(JSON.stringify())进行真正的对象转换
-
最佳实践
- 对于从 API 返回的数据,统一使用
JSON.parse(JSON.stringify())转换 - 使用
instanceof UTSJSONObject检查对象类型 - 不要依赖
as类型断言来添加方法
- 对于从 API 返回的数据,统一使用
================================================================================ 五十、类型必须包含所有必填字段(重要)
-
创建类型实例时必须包含所有必填字段
- UTS 类型定义中的非可选字段(不带
?)都是必填的 - 错误示例:
export type ProductType = { id: string merchant_id: string // 必填 category_id: string // 必填 name: string // ... } // 错误:缺少 merchant_id、category_id 等必填字段 return { id: item.id, name: item.name, price: item.price } as ProductType // 运行时错误:missing required property - 正确示例:
return { id: itemObj.getString('id') ?? '', merchant_id: itemObj.getString('merchant_id') ?? '', category_id: itemObj.getString('category_id') ?? '', name: itemObj.getString('name') ?? '未知商品', description: itemObj.getString('description') ?? '', images: images, price: itemObj.getNumber('base_price') ?? 0, original_price: itemObj.getNumber('market_price') ?? 0, stock: itemObj.getNumber('total_stock') ?? 0, sales: itemObj.getNumber('sale_count') ?? 0, status: 1, created_at: itemObj.getString('created_at') ?? '' } as ProductType
- UTS 类型定义中的非可选字段(不带
-
运行时错误提示
- 错误信息:"Failed to construct type, missing required property: xxx"
- 原因:类型定义中有必填字段未提供
- 解决:
- 检查类型定义,确认哪些字段是必填的(不带
?) - 为所有必填字段提供值,即使是空字符串或默认值
- 检查类型定义,确认哪些字段是必填的(不带
-
最佳实践
- 查看类型定义,确认哪些字段是必填的(不带
?) - 使用
??运算符提供默认值 - 对于可选字段,可以不提供或使用
null
- 查看类型定义,确认哪些字段是必填的(不带
================================================================================ 五十一、回调函数不能是 async(重要)
-
API 回调函数不能使用 async 修饰
- uni API 的回调函数(如 showModal 的 success)不支持 async 函数
- 错误示例:
uni.showModal({ title: '确认', content: '确定要删除吗?', success: async (res) => { // 错误:回调函数不能是 async if (res.confirm) { const result = await someAsyncFunction() } } }) - 正确示例:
uni.showModal({ title: '确认', content: '确定要删除吗?', success: (res) => { if (res.confirm) { // 使用 Promise.then() 代替 await someAsyncFunction().then((result) => { // 处理结果 }) } } })
-
编译错误提示
- 错误信息:"参数类型不匹配:实际类型为 'Function1<..., UTSPromise>',预期类型为 'Function1<..., Unit>?'"
- 原因:回调函数返回 Promise 而非 void
- 解决:使用
.then()代替await
-
最佳实践
- 在回调函数中使用
.then()处理异步操作 - 将异步逻辑封装为单独的函数,在回调中调用
- 在回调函数中使用
================================================================================ 五十二、类型转换前必须检查类型(重要)
-
使用
as类型转换前必须检查实际类型- 直接使用
as string转换可能导致运行时类型转换异常 - 错误示例:
const idVal = item['id'] const id = idVal as string // 错误:如果 idVal 是其他类型会崩溃 - 正确示例:
const idVal = item['id'] const id = (idVal != null && typeof idVal == 'string') ? (idVal as string) : ''
- 直接使用
-
运行时错误提示
- 错误信息:"null cannot be cast to non-null type kotlin.String"
- 错误信息:"java.lang.Boolean cannot be cast to java.lang.String"
- 原因:直接类型转换时,实际类型与目标类型不匹配
- 解决:使用
typeof检查类型后再转换
-
最佳实践
- 使用
typeof检查类型 - 使用
!= null检查空值 - 提供默认值防止空指针异常
- 使用
================================================================================ 五十三、UTSJSONObject 必须正确转换(重要)
-
as UTSJSONObject不会添加方法- 从数据库返回的数据需要正确转换为 UTSJSONObject
- 错误示例:
const item = rawList[i] const brandObj = item as UTSJSONObject // 错误:brandObj.getString 不存在 - 正确示例:
const item = rawList[i] const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const id = brandObj.getString('id') ?? ''
-
运行时错误提示
- 错误信息:"getString is not a function"
- 原因:对象没有正确转换为 UTSJSONObject
- 解决:使用
JSON.parse(JSON.stringify())进行转换
-
最佳实践
- 对于从数据库/API 返回的数据,统一使用
JSON.parse(JSON.stringify())转换 - 使用
.getString()、.getNumber()等方法安全访问属性
- 对于从数据库/API 返回的数据,统一使用
================================================================================ 五十四、getBoolean 方法可能导致类型转换异常(重要)
-
UTSJSONObject.getBoolean()可能导致类型转换异常- 当数据库字段类型与预期不符时,getBoolean() 可能抛出异常
- 错误示例:
const isFeatured = prodObj.getBoolean('is_featured') ?? false // 可能抛出异常 - 正确示例:
const isFeaturedVal = prodObj.get('is_featured') const isFeatured = (isFeaturedVal != null && typeof isFeaturedVal == 'boolean') ? (isFeaturedVal as boolean) : false
-
运行时错误提示
- 错误信息:"java.lang.Boolean cannot be cast to java.lang.String"
- 原因:数据库返回的字段类型与 UTSJSONObject 方法期望的类型不匹配
- 解决:使用
.get()方法获取原始值,然后手动检查类型
-
最佳实践
- 避免使用
.getBoolean(),改用.get()+typeof检查 - 在 SQL 查询中明确指定需要的字段,避免
SELECT * - 对于布尔值,使用
typeof val == 'boolean'检查类型
- 避免使用
================================================================================ 五十五、SELECT * 可能导致类型转换问题(重要)
-
避免使用
SELECT *查询所有字段- 数据库可能包含前端不需要的字段,导致类型转换异常
- 错误示例:
.select('*') // 可能返回意外的字段类型 - 正确示例:
.select('id, name, description, base_price, market_price, main_image_url')
-
最佳实践
- 只查询需要的字段
- 参考数据库文档确认字段类型
- 对于视图(如
ml_products_detail_view),注意字段名可能与基础表不同
================================================================================ 五十六、创建辅助函数处理数据转换(重要)
-
创建辅助函数统一处理数据类型转换
- 避免在每个方法中重复写类型检查代码
- 示例:
// 辅助函数:安全获取字符串值 function safeGetString(obj: UTSJSONObject, key: string): string { try { const val = obj.get(key) if (val == null) return '' if (typeof val == 'string') return val as string if (typeof val == 'number') return (val as number).toString() if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false' return '' } catch (e) { console.error('safeGetString error for key:', key, e) return '' } } // 辅助函数:安全获取数值 function safeGetNumber(obj: UTSJSONObject, key: string): number { try { const val = obj.get(key) if (val == null) return 0 if (typeof val == 'number') return val as number if (typeof val == 'string') { const parsed = parseFloat(val as string) return isNaN(parsed) ? 0 : parsed } return 0 } catch (e) { console.error('safeGetNumber error for key:', key, e) return 0 } } // 辅助函数:安全获取布尔值 function safeGetBoolean(obj: UTSJSONObject, key: string): boolean { try { const val = obj.get(key) if (val == null) return false if (typeof val == 'boolean') return val as boolean if (typeof val == 'string') return (val as string) === 'true' if (typeof val == 'number') return (val as number) !== 0 return false } catch (e) { console.error('safeGetBoolean error for key:', key, e) return false } } // 辅助函数:从原始数据解析商品 function parseProductFromRaw(item: any): Product { const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject return { id: safeGetString(prodObj, 'id'), name: safeGetString(prodObj, 'name'), base_price: safeGetNumber(prodObj, 'base_price'), is_featured: safeGetBoolean(prodObj, 'is_featured'), // ... } as Product }
-
优点
- 代码复用,减少重复
- 统一处理各种类型转换异常
- 易于维护和修改
-
最佳实践
- 将辅助函数放在文件顶部
- 对所有从数据库获取的数据使用辅助函数
- 处理所有可能的类型转换情况
================================================================================ 五十七、视图字段名可能与基础表不同(重要)
-
数据库视图的字段名可能与基础表不同
ml_products_detail_view视图中没有image_url字段- 只有
main_image_url和image_urls字段 - 错误示例:
.select('id, name, image_url') // 错误:视图没有 image_url 字段 - 正确示例:
.select('id, name, main_image_url, image_urls') // 正确
-
运行时错误提示
- 错误信息:"column ml_products_detail_view.image_url does not exist"
- 提示:"Perhaps you meant to reference the column 'ml_products_detail_view.image_urls'"
- 原因:查询了视图中不存在的字段
- 解决:参考数据库文档确认视图字段名
-
最佳实践
- 查询视图前先确认字段名
- 使用明确字段列表而非
SELECT *
================================================================================ 五十八、辅助函数应使用 try-catch 包装(重要)
-
辅助函数应使用 try-catch 包装防止崩溃
- 在 Android 端,类型转换可能抛出异常
- 使用 try-catch 包装可以防止整个应用崩溃
- 示例:
function safeGetString(obj: UTSJSONObject, key: string): string { try { const val = obj.get(key) if (val == null) return '' if (typeof val == 'string') return val as string if (typeof val == 'number') return (val as number).toString() if (typeof val == 'boolean') return (val as boolean) ? 'true' : 'false' return '' } catch (e) { console.error('safeGetString error for key:', key, e) return '' } } function toUTSJSONObject(item: any): UTSJSONObject { if (item instanceof UTSJSONObject) { return item as UTSJSONObject } try { const str = JSON.stringify(item) return JSON.parse(str) as UTSJSONObject } catch (e) { console.error('toUTSJSONObject error:', e) return new UTSJSONObject() } }
-
优点
- 防止单个字段解析失败导致整个应用崩溃
- 提供详细的错误日志便于调试
- 返回默认值保证应用继续运行
-
最佳实践
- 所有辅助函数都应使用 try-catch 包装
- 在 catch 中记录错误日志
- 返回合理的默认值
================================================================================ 五十九、商品视图没有 shop_id 字段(重要)
-
ml_products_detail_view视图没有shop_id字段- 商品通过
merchant_id关联商家/店铺 - 错误示例:
.select('id, name, shop_id') // 错误:视图没有 shop_id 字段 - 正确示例:
.select('id, name, merchant_id') // 正确:使用 merchant_id
- 商品通过
-
数据库字段对应关系
ml_products表:merchant_id关联商家ml_shops表:user_id等于商家的merchant_id- 视图中通过
merchant_idJOINml_shops获取店铺信息
-
最佳实践
- 查询商品时使用
merchant_id而非shop_id
- 查询商品时使用
================================================================================ 六十、any 类型不支持索引访问(重要)
-
UTS Android 中
any类型不支持索引访问- 不能使用
obj[key]语法访问any类型对象的属性 - 错误示例:
function safeGetString(obj: any, key: string): string { const val = obj[key] // 错误:unresolved reference } - 正确示例:
function safeGetString(obj: UTSJSONObject, key: string): string { const val = obj.get(key) // 正确:使用 UTSJSONObject 的 get 方法 }
- 不能使用
-
编译错误提示
- 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch"
- 原因:
any类型不支持索引访问 - 解决:先将对象转换为
UTSJSONObject,再使用.get()方法
-
最佳实践
- 使用
toUTSJSONObject()函数将any转换为UTSJSONObject - 使用
.get()、.getString()、.getNumber()等方法访问属性 - 辅助函数参数类型应为
UTSJSONObject而非any
- 使用
================================================================================ 六十一、使用 getString/getNumber/getBoolean 方法(重要)
-
UTSJSONObject 提供了类型安全的访问方法
getString(key)- 直接返回字符串或 nullgetNumber(key)- 直接返回数值或 nullgetBoolean(key)- 直接返回布尔值或 nullgetArray(key)- 直接返回数组或 null- 这些方法比
.get()更安全,会自动进行类型转换
-
推荐用法:
// 推荐使用 const name = obj.getString('name') ?? '' const price = obj.getNumber('price') ?? 0 const isActive = obj.getBoolean('is_active') ?? false const images = obj.getArray('images') as string[] ?? [] // 不推荐使用 .get() 后手动类型检查 const val = obj.get('key') if (typeof val == 'string') { ... } -
最佳实践
- 优先使用
getString()、getNumber()、getBoolean()、getArray() - 使用
??提供默认值 - 在 catch 块中处理异常
- 优先使用
================================================================================ 六十二、item as UTSJSONObject 不会添加方法(重要)
-
item as UTSJSONObject不会让对象获得getString等方法- 直接使用
as UTSJSONObject只是类型断言,不会改变运行时对象 - 错误示例:
const item = rawList[i] const prodObj = item as UTSJSONObject // 错误:getString 不存在 const id = prodObj.getString('id') // 运行时错误 - 正确示例:
const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const id = prodObj.getString('id') ?? '' // 正确
- 直接使用
-
运行时错误提示
- 错误信息:"getString is not a function"
- 原因:对象没有正确转换为 UTSJSONObject
- 解决:使用
JSON.parse(JSON.stringify())进行转换
-
最佳实践
- 对于从数据库/API 返回的数据,统一使用
JSON.parse(JSON.stringify())转换 - 使用
.getString()、.getNumber()等方法安全访问属性
- 对于从数据库/API 返回的数据,统一使用
================================================================================ 六十三、自定义导航栏 Android 端适配(重要)
- .smart-navbar 样式规范
- 问题:在 Android 端沉浸式导航栏中,若 .smart-navbar 设置了 paddingTop: statusBarHeight,且 flex-direction 未显式设置为 column,内部元素(标题、按钮)可能会错位。
- 修复:.smart-navbar 必须显式设置 flex-direction: column,内部内容容器 .nav-container 设置为 flex-direction: row。
- 正确示例:
.smart-navbar { display: flex; flex-direction: column; justify-content: flex-start; } .nav-container { display: flex; flex-direction: row; align-items: center; justify-content: space-between; height: 44px; }
================================================================================ 六十四、常见修复模式速查
-
取反操作修复模式
// 错误 if (!variable) { ... } if (!isValid.value) { ... } if (!validate()) { ... } // 正确 - 根据类型选择 if (variable == null || variable == '') { ... } // 字符串判空 if (isValid.value == false) { ... } // 布尔值取反 if (validate() == false) { ... } // 函数返回布尔值取反 -
解构赋值修复模式
// 错误 const { data, error } = await someAsyncCall() // 正确 const result = await someAsyncCall() const data = result.data const error = result.error -
typeof 检查修复模式
// 错误 if (typeof err === 'object') { ... } if (typeof xxx === 'function') { ... } // 正确 try { const e = err as Error // 使用 e } catch (e2) { // 处理转换失败 } -
as unknown as 修复模式
// 错误 const timer = setInterval(...) as unknown as number // 正确 const timer = setInterval(...) as number
================================================================================ 六十五、错误处理最佳实践
-
统一错误处理模式
try { const result = await someAsyncCall() if (result.error != null) { const errorMsg = (result.error as Error).message uni.showToast({ title: errorMsg, icon: 'none' }) return } // 处理成功结果 } catch (e) { console.error('操作失败:', e) uni.showToast({ title: '操作失败', icon: 'none' }) } -
可空类型安全访问
// 安全访问对象属性 const value = obj != null ? obj.property : null // 安全调用方法 const result = obj != null ? obj.method() : null -
数组安全访问
// 安全访问数组元素 if (arr.length > index) { const item = arr[index] // 使用 item }
================================================================================ 六十六、scroll-view 正确使用方式(重要)
-
scroll-view 属性语法
- 在 uvue 中,scroll-view 的滚动属性必须使用绑定语法
- 错误示例:
<scroll-view scroll-y class="content"> <scroll-view direction="vertical" class="content"> - 正确示例:
<scroll-view :scroll-y="true" class="content">
-
scroll-view 高度约束
- scroll-view 必须有明确的高度约束才能正常滚动
- 父容器需要设置
flex: 1和height: 0px(或height: 0) - scroll-view 本身设置
flex: 1和height: 100% - 正确示例:
.parent-container { flex: 1; height: 0px; /* 关键:配合 flex: 1 使用 */ display: flex; flex-direction: row; } .scroll-content { flex: 1; height: 100%; /* 继承父容器高度 */ }
-
常见问题
- 问题:scroll-view 内容无法滚动
- 原因:缺少高度约束或
height: 0px未设置 - 解决:确保父容器有
height: 0px,scroll-view 有height: 100%
================================================================================ 六十七、switchTab 页面间参数传递(重要)
-
switchTab 不支持 URL 参数
uni.switchTab不能像uni.navigateTo那样传递 URL 参数- 错误示例:
uni.switchTab({ url: '/pages/category?categoryId=123' // 参数会被忽略 })
-
使用 Storage 传递参数
- 正确示例:
// 发送页面 uni.setStorageSync('selectedCategory', categoryId) uni.switchTab({ url: '/pages/category' }) // 接收页面 onShow(() => { const savedCategoryId = uni.getStorageSync('selectedCategory') if (savedCategoryId != null && savedCategoryId != '') { uni.removeStorageSync('selectedCategory') // 清除,避免重复使用 // 处理参数 } })
- 正确示例:
-
时序问题处理
- 问题:onShow 可能在数据加载完成前执行
- 解决:使用 pending 变量暂存参数,等待数据加载完成后处理
- 正确示例:
const pendingCategoryId = ref('') onShow(() => { const savedCategoryId = uni.getStorageSync('selectedCategory') if (savedCategoryId != null && savedCategoryId != '') { uni.removeStorageSync('selectedCategory') if (primaryCategories.value.length > 0) { // 数据已加载,直接处理 selectPrimaryCategory(savedCategoryId as string) } else { // 数据未加载,暂存等待 pendingCategoryId.value = savedCategoryId as string } } }) async function loadCategories(): Promise<void> { // ... 加载数据 ... // 检查是否有待处理的参数 if (pendingCategoryId.value != '') { const id = pendingCategoryId.value pendingCategoryId.value = '' selectPrimaryCategory(id) } }
================================================================================ 六十八、函数定义顺序严格检查(重要)
-
UTS 中函数必须先定义后使用
- UTS 不支持 JavaScript 的函数提升
- 被调用的函数必须在调用之前定义
- 错误示例:
// 错误:selectPrimaryCategory 未定义 async function loadCategories(): Promise<void> { // ... selectPrimaryCategory(categoryId) // 编译错误 } async function selectPrimaryCategory(id: string): Promise<void> { // ... } - 正确示例:
// 正确:先定义被调用的函数 async function selectPrimaryCategory(id: string): Promise<void> { // ... } async function loadCategories(): Promise<void> { // ... selectPrimaryCategory(categoryId) // 正确 }
-
变量使用前必须定义
- 错误示例:
if (systemInfo.windowWidth > 1025) { // 错误:systemInfo 未定义 // ... } const systemInfo = uni.getSystemInfoSync() - 正确示例:
const systemInfo = uni.getSystemInfoSync() // 先定义 if (systemInfo.windowWidth > 1025) { // 正确 // ... }
- 错误示例:
-
推荐的代码组织顺序
<script setup lang="uts"> // 1. 导入语句 import { ref, onMounted, onShow } from 'vue' // 2. 类型定义 type LocalCategory = { id: string, name: string } // 3. 响应式变量声明 const pendingCategoryId = ref('') const primaryCategories = ref<LocalCategory[]>([]) // 4. 工具函数(不依赖其他函数) const getUserId = (): string => { ... } // 5. 核心业务函数(被其他函数调用) async function selectPrimaryCategory(id: string): Promise<void> { ... } async function loadProducts(): Promise<void> { ... } // 6. 高层业务函数(调用核心函数) async function loadCategories(): Promise<void> { // 可以安全调用 selectPrimaryCategory } // 7. 生命周期钩子(最后) onMounted(() => { ... }) onShow(() => { ... }) </script>
================================================================================ 六十九、CSS 渐变背景兼容性(重要)
- linear-gradient 在 uvue 中可能不支持
- 问题:
background: linear-gradient(...)在 Android 端可能不显示 - 解决:使用纯色背景
background-color替代 - 错误示例:
.navbar { background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); } - 正确示例:
.navbar { background-color: #4CAF50; }
- 问题:
================================================================================ 七十、避免重复定义生命周期钩子(重要)
- 不要重复定义相同的生命周期钩子
- UTS 中如果定义了两个
onShow,它们都会被执行 - 这会导致逻辑冲突和意外行为
- 错误示例:
onShow(() => { // 第一个 onShow selectPrimaryCategory(id1) }) onShow(() => { // 第二个 onShow 也会执行 // 可能覆盖第一个的处理结果 }) - 正确示例:
onShow(() => { // 合并所有逻辑到一个 onShow 中 // 处理 Storage 参数 // 处理页面参数 // 其他逻辑 })
- UTS 中如果定义了两个
================================================================================ 七十一、二级分类ID与一级分类ID的处理(重要)
-
分类页可能收到二级分类ID
- 首页点击二级分类(如"男装")跳转到分类页
- 分类页左侧栏显示的是一级分类(如"服装鞋帽")
- 需要将二级分类ID转换为其父级分类ID
-
处理方案
async function selectPrimaryCategory(categoryId: string): Promise<void> { // 检查传入的是否是一级分类ID let targetCategoryId = categoryId const foundInPrimary = primaryCategories.value.find(c => c.id === categoryId) if (foundInPrimary == null) { // 传入的可能是二级分类ID,查找父级分类 const categoryInfo = await supabaseService.getCategoryById(categoryId) if (categoryInfo != null && categoryInfo.parent_id != null) { targetCategoryId = categoryInfo.parent_id } } activePrimary.value = targetCategoryId // ... 后续处理 } -
最佳实践
- 在服务层添加
getCategoryById方法获取单个分类信息 - 通过
parent_id字段确定父级分类 - 如果找不到父级,回退到默认分类
- 在服务层添加
================================================================================ 七十二、Supabase .single() 方法兼容性(重要)
-
.single()在 UTS 中可能返回不同格式- 问题:
.single()返回的数据可能导致类型转换异常 - 错误信息:
UTSArray cannot be cast to UTSJSONObject - 错误示例:
const response = await supa .from('table') .select('*') .eq('id', id) .single() .execute() const rawData = response.data const obj = JSON.parse(JSON.stringify(rawData)) as UTSJSONObject // 可能报错
- 问题:
-
解决方案:使用
.limit(1)替代const response = await supa .from('table') .select('*') .eq('id', id) .limit(1) // 使用 limit(1) 替代 single() .execute() const rawData = response.data if (rawData == null) { return null } // 作为数组处理 const rawList = rawData as any[] if (rawList.length == 0) { return null } const item = rawList[0] const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject -
最佳实践
- 避免使用
.single(),改用.limit(1) - 始终将返回数据作为数组处理
- 检查数组长度后再取第一个元素
- 避免使用
================================================================================ 七十三、字符串比较必须使用 == 而不是 ===(重要)
-
UTS Android 中
===比较字符串可能失败- 问题:即使两个字符串内容相同,
===也可能返回false - 这会影响
find、findIndex、filter等数组方法 - 错误示例:
const found = array.find((item) => item.id === targetId) // 可能失败 const index = array.findIndex((item) => item.id === targetId) // 可能失败 if (str1 === str2) { ... } // 可能失败
- 问题:即使两个字符串内容相同,
-
解决方案:使用
==或 for 循环// 方案1:使用 == 替代 === const found = array.find((item) => item.id == targetId) if (str1 == str2) { ... } // 方案2:使用 for 循环(推荐,更可靠) let foundItem: Item | null = null for (let i = 0; i < array.length; i++) { if (array[i].id == targetId) { foundItem = array[i] break } } -
受影响的场景
- 数组查找:
find、findIndex、filter、some、every - 字符串比较:
if (a === b) - 对象属性比较:
if (obj.id === targetId)
- 数组查找:
-
最佳实践
- 在 UTS Android 中,始终使用
==而不是===进行字符串比较 - 对于关键逻辑,使用
for循环替代数组方法 - 添加调试日志确认比较结果
- 在 UTS Android 中,始终使用
================================================================================ 七十四、Supabase 查询应在数据库层面过滤(重要)
-
问题:先获取数据再手动过滤导致数据不足
- 错误示例:
// 错误:先获取 limit 条数据,再手动过滤 const response = await supa .from('ml_products') .select('*') .limit(20) .execute() // 手动过滤 category_id for (let i = 0; i < rawList.length; i++) { if (itemCatId == categoryId) { products.push(item) } } // 结果:可能返回 0 个商品,因为匹配的商品不在前 20 条中
- 错误示例:
-
解决方案:在数据库层面进行过滤
// 正确:在数据库层面过滤 const response = await supa .from('ml_products') .select('*') .eq('category_id', categoryId) // 数据库层面过滤 .eq('status', '1') // 使用字符串 '1' .limit(20) .execute() -
需要手动过滤的场景
- 某些字段(如
is_hot、is_new、is_featured)可能无法在数据库层面过滤 - 解决方案:获取更多数据(limit * 5),然后手动过滤
const response = await supa .from('ml_products') .select('*') .eq('status', '1') .limit(limit * 5) // 获取更多数据 .execute() // 手动过滤 is_hot for (let i = 0; i < rawList.length; i++) { if (isHotBool) { products.push(item) if (products.length >= limit) break } } - 某些字段(如
-
最佳实践
- 优先在数据库层面使用
.eq()、.in()等方法过滤 - 整数字段使用字符串:
.eq('status', '1')而不是.eq('status', 1) - 无法在数据库层面过滤时,获取更多数据再手动过滤
- 优先在数据库层面使用
================================================================================ 七十五、URL 参数类型转换(重要)
-
问题:URL 参数可能是不同类型
- 从 URL 传递的参数可能是
string、number或其他类型 - 错误示例:
const priceOpt = opts['price'] as string // 错误:可能是 number this.product.price = parseFloat(priceOpt) // 运行时错误:java.lang.Double cannot be cast to java.lang.String
- 从 URL 传递的参数可能是
-
解决方案:检查类型后再转换
const priceOpt = opts['price'] if (typeof priceOpt == 'number') { this.product.price = priceOpt as number } else if (typeof priceOpt == 'string') { this.product.price = parseFloat(priceOpt as string) } else { this.product.price = 0 } -
通用类型安全获取函数
function getNumberParam(opts: UTSJSONObject, key: string): number { const val = opts.get(key) if (typeof val == 'number') return val as number if (typeof val == 'string') return parseFloat(val as string) return 0 } function getStringParam(opts: UTSJSONObject, key: string): string { const val = opts.get(key) if (typeof val == 'string') return val as string if (typeof val == 'number') return (val as number).toString() return '' }
================================================================================ 七十六、避免使用 .single() 和 .executeAs()(重要)
-
问题:
.single()和.executeAs<T>()在 UTS Android 中可能失败- 错误示例:
const response = await supa .from('ml_products') .select('*') .eq('id', productId) .single() .executeAs<Product>() // 可能报错:UTSArray cannot be cast to UTSJSONObject
- 错误示例:
-
解决方案:使用
.limit(1)和手动解析const response = await supa .from('ml_products') .select('*') .eq('id', productId) .limit(1) // 使用 limit(1) 替代 single() .execute() const rawData = response.data if (rawData == null) return null const rawList = rawData as any[] if (rawList.length == 0) return null const item = rawList[0] const product = parseProductFromRaw(item) // 使用解析函数 return product -
最佳实践
- 使用
.limit(1)替代.single() - 使用
.execute()替代.executeAs<T>() - 使用统一的解析函数(如
parseProductFromRaw)处理数据
- 使用
================================================================================ 七十七、排序切换状态管理(重要)
-
问题:点击同一排序按钮时无法切换排序方向
- 需求:二次点击"价格"时,切换升序/降序
-
解决方案:使用状态变量记录排序方向
const priceAscending = ref(true) // 价格排序方向 const switchSort = (sortId: string) => { // 如果点击的是价格排序,切换升序/降序 if (sortId === 'price' && activeSort.value === 'price') { priceAscending.value = !priceAscending.value } else { // 切换到其他排序时,重置价格排序为升序 if (sortId !== 'price') { priceAscending.value = true } activeSort.value = sortId } // 重新加载数据 loadData() } -
在数据加载时使用排序方向
switch (activeSort.value) { case 'price': products = await supabaseService.getProductsByPrice(limit, priceAscending.value) break // ... }
================================================================================ 七十八、加载更多逻辑(重要)
-
问题:加载更多时数据被替换而不是追加
- 错误示例:
const loadMore = async () => { const nextLimit = currentCount + 6 await loadHotProducts(nextLimit) // 这个函数会替换数据 }
- 错误示例:
-
解决方案:根据当前排序方式调用对应函数
const loadMore = async () => { const currentCount = hotProducts.value.length const additionalLimit = 6 let newProducts: Product[] = [] switch (activeSort.value) { case 'sales': newProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit) break case 'price': newProducts = await supabaseService.getProductsByPrice(currentCount + additionalLimit, priceAscending.value) break // ... 其他排序方式 } // 检查是否还有更多数据 if (newProducts.length <= currentCount) { hasMore.value = false } else { hotProducts.value = newProducts } } -
最佳实践
- 每个排序方式对应独立的数据获取函数
- 传递
currentCount + additionalLimit作为新的 limit - 比较返回数量判断是否还有更多数据
================================================================================ 七十九、二级分类自适应宽度布局(重要)
-
问题:二级分类项宽度不一致,超出屏幕
-
解决方案:使用 flex 布局自适应宽度
.sub-category-list { display: flex; flex-direction: row; flex-wrap: nowrap; width: 100%; justify-content: space-between; } .sub-category-item { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 8px 4px; background: #f5f5f5; border-radius: 8px; flex: 1; /* 自动扩展填充空间 */ min-width: 50px; /* 最小宽度保证可读性 */ } -
布局效果
- 二级分类项均匀分布在一行内
- 根据容器宽度自适应
- 不会超出屏幕右边
================================================================================ 八十、Product 类型与服务层返回类型一致性(重要)
-
问题:页面期望的数据类型与服务层返回类型不匹配
- 服务层
getProductById返回Product类型 - 页面可能期望
UTSJSONObject类型 - 导致数据无法正确映射
- 服务层
-
解决方案:统一使用服务层返回的类型
// 正确:直接使用 Product 对象 const dbProduct = await supabaseService.getProductById(productId) if (dbProduct != null) { this.product = { id: dbProduct.id, name: dbProduct.name, stock: dbProduct.stock ?? dbProduct.total_stock ?? 0, sales: dbProduct.sale_count ?? 0, // ... } } -
类型定义需要包含所有使用到的字段
- 确保
Product类型包含所有必要字段 - 如
specification、usage、expiry_date等
- 确保
================================================================================ 八十一、parseProductFromRaw 辅助函数(重要)
-
使用统一的解析函数处理数据库返回数据
function parseProductFromRaw(item: any): Product { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = itemObj.get(key) if (val == null) return '' if (typeof val == 'string') return val if (typeof val == 'number') return val.toString() return '' } const getSafeNumber = (key: string): number => { const val = itemObj.get(key) if (val == null) return 0 if (typeof val == 'number') return val if (typeof val == 'string') return parseFloat(val) ?? 0 return 0 } return { id: getSafeString('id'), name: getSafeString('name'), // ... } as Product } -
优点
- 统一处理类型转换
- 避免
ClassCastException错误 - 提供默认值防止空指针
================================================================================ 八十二、点击事件处理(重要)
-
问题:点击事件条件判断不完整导致无响应
- 错误示例:
goToShop() { if (this.merchant.user_id != null && this.merchant.user_id !== '') { uni.navigateTo({ url: '...' }) } // 如果条件不满足,没有任何反馈 }
- 错误示例:
-
解决方案:添加后备方案和用户反馈
goToShop() { const merchantId = this.merchant.id ?? this.product.merchant_id ?? '' if (merchantId != '') { uni.navigateTo({ url: `/pages/shop?merchantId=${merchantId}` }) } else { uni.showToast({ title: '店铺信息加载中', icon: 'none' }) } } -
最佳实践
- 使用
??提供后备值 - 条件不满足时给用户反馈
- 添加日志便于调试
- 使用
================================================================================ 八十三、类型定义必须与实际数据类型一致(重要)
-
问题:类型定义与实际数据类型不匹配
ProductSku.specifications定义为string(JSON string)- 但代码尝试传入
UTSJSONObject - 编译错误:
参数类型不匹配:实际类型为 'UTSJSONObject',预期类型为 'String'
-
解决方案:确保类型一致
// 错误:specifications 是 string 类型,不能传 UTSJSONObject const sku: ProductSku = { specifications: specs as UTSJSONObject // 错误 } // 正确:转换为 JSON string let specsStr = '' if (rawSpecs != null) { if (typeof rawSpecs == 'string') { specsStr = rawSpecs as string } else { specsStr = JSON.stringify(rawSpecs) } } const sku: ProductSku = { specifications: specsStr // 正确 } -
最佳实践
- 检查类型定义,确认字段类型
- JSON 数据存储为
string类型时,需要序列化 - 使用时再反序列化
================================================================================ 八十四、数组类型字段需要正确处理(重要)
-
问题:数据库存储的 JSON 字符串不能直接赋值给数组类型
Product.tags在数据库中是string类型(JSON 字符串)ProductType.tags是Array<string>类型- 直接赋值导致编译错误
-
解决方案:解析 JSON 字符串后赋值
// 错误:string 不能直接赋值给 Array<string> tags: dbProduct.tags ?? [] as string[] // 错误 // 正确:先初始化空数组,再解析 JSON tags: [] as string[] // 然后解析 if (dbProduct.tags != null && dbProduct.tags != '') { try { const parsedTags = JSON.parse(dbProduct.tags) if (Array.isArray(parsedTags)) { this.product.tags = (parsedTags as any[]).map((t: any): string => t as string) } } catch(e) {} } -
常见需要解析的 JSON 字段
tags- 标签数组image_urls- 图片 URL 数组specifications- 规格对象attributes- 属性对象
================================================================================ 八十五、服务层返回类型必须显式解析(重要)
-
问题:直接使用
as Type强制转换会失败- 错误信息:
UTSJSONObject cannot be cast to Shop - 原因:Supabase 返回的是
UTSJSONObject,不能直接转换为自定义类型
- 错误信息:
-
解决方案:使用解析函数显式转换
// 错误:直接强制转换 return (response.data as any[])[0] as Shop // 运行时错误 // 正确:使用解析函数 const shopData = (response.data as any[])[0] const shop = this.parseShopFromRaw(shopData) return shop // 解析函数示例 parseShopFromRaw(item: any): Shop { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = itemObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } return { id: getSafeString('id'), shop_name: getSafeString('shop_name'), // ... } as Shop } -
受影响的场景
getShopByMerchantId- 返回 Shop 类型fetchShopCoupons- 返回优惠券数组getProductSkus- 返回 SKU 数组- 所有从数据库获取并返回自定义类型的函数
================================================================================ 八十六、数组遍历不要使用 map 方法(重要)
-
问题:
map方法中的类型转换可能失败- 错误信息:
ProductSku cannot be cast to UTSJSONObject - 原因:
map回调中的类型推断可能不正确
- 错误信息:
-
解决方案:使用
for循环替代map// 错误:使用 map this.productSkus = skus.map((skuData): ProductSkuType => { const sku = skuData as UTSJSONObject // 可能失败 return { ... } }) // 正确:使用 for 循环 this.productSkus = [] for (let i = 0; i < skus.length; i++) { const skuData = skus[i] // 直接使用 skuData 的属性 const sku: ProductSkuType = { id: skuData.id, product_id: skuData.product_id, // ... } this.productSkus.push(sku) } -
最佳实践
- 避免在数组方法中使用类型转换
- 使用
for循环手动构建数组 - 直接访问对象属性而不是强制转换
================================================================================ 八十七、类型定义字段必须完全匹配(重要)
-
问题:构建对象时字段名或类型不匹配
- 错误信息:
No parameter with name 'xxx' found或No value passed for parameter 'xxx' - 原因:类型定义中的字段名与代码中使用的不一致
- 错误信息:
-
解决方案:检查类型定义,确保字段完全匹配
// 类型定义 export type CouponTemplateType = { id: string name: string discount_type: number // 注意:是 discount_type 不是 coupon_type discount_value: number total_quantity: number // 注意:是 total_quantity 不是 total_count usage_limit: number // 必填字段 category_ids: Array<string> product_ids: Array<string> // ... } // 错误:字段名不匹配 const coupon: CouponTemplateType = { usage_scope: 1, // 错误:没有这个字段 total_count: 100, // 错误:应该是 total_quantity // 缺少 usage_limit } // 正确:字段完全匹配 const coupon: CouponTemplateType = { id: '...', name: '优惠券', discount_type: 1, discount_value: 10, total_quantity: 100, usage_limit: 1, category_ids: [] as string[], product_ids: [] as string[], // 所有必填字段都要提供 } -
常见错误
- 字段名拼写错误:
total_countvstotal_quantity - 缺少必填字段:
usage_limit、discount_type - 类型不匹配:
stringvsnumber、Array<string>vsstring
- 字段名拼写错误:
-
最佳实践
- 在构建对象前,先查看类型定义
- 确保所有必填字段都有值
- 数组类型字段初始化为空数组:
[] as string[]
================================================================================ 八十八、服务层返回对象直接使用属性(重要)
-
问题:尝试将服务层返回的类型对象转换为 UTSJSONObject
- 错误信息:
Shop cannot be cast to UTSJSONObject - 原因:服务层函数(如
getShopByMerchantId)已经返回解析后的类型对象
- 错误信息:
-
解决方案:直接使用返回对象的属性
// 错误:尝试转换为 UTSJSONObject const shopResponse = await supabaseService.getShopByMerchantId(merchantId) const shop = shopResponse as UTSJSONObject // 错误! this.merchant = { shop_name: shop['shop_name'] as string } // 正确:直接使用 Shop 对象的属性 const shopResponse = await supabaseService.getShopByMerchantId(merchantId) if (shopResponse != null) { this.merchant = { id: shopResponse.id, shop_name: shopResponse.shop_name, shop_logo: shopResponse.shop_logo ?? '/static/default-shop.png', // ... } as MerchantType } -
受影响的函数
getShopByMerchantId()- 返回Shop类型getProductById()- 返回Product类型getProductSkus()- 返回ProductSku[]类型- 所有使用
parseXxxFromRaw()解析的函数
-
最佳实践
- 检查服务层函数的返回类型
- 如果返回类型是具体类型(如
Shop),直接使用属性 - 如果返回类型是
any或UTSJSONObject,才需要手动解析
================================================================================ 八十九、数组类型返回值必须手动解析(重要)
-
问题:直接使用
as Type[]转换数组会失败- 错误信息:
UTSJSONObject cannot be cast to Notification或ClassCastException - 原因:Supabase 返回的是
UTSJSONObject[],不能直接转换为自定义类型数组
- 错误信息:
-
解决方案:使用 for 循环手动解析每个元素
// 错误:直接转换数组 return response.data as Notification[] // 运行时错误 // 正确:手动解析每个元素 const rawData = response.data if (rawData == null) return [] const notifications: Notification[] = [] const rawList = rawData as any[] for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const note: Notification = { id: getSafeString('id'), title: getSafeString('title'), content: getSafeString('content'), is_read: getSafeBoolean('is_read'), // ... } notifications.push(note) } return notifications -
受影响的函数
getUserNotifications()- 返回Notification[]getChatRooms()- 返回ChatRoom[]- 所有返回自定义类型数组的服务层函数
-
最佳实践
- 创建
getSafeString、getSafeNumber、getSafeBoolean辅助函数 - 使用
for循环而不是map方法 - 确保类型定义中的所有字段都被正确赋值
- 创建
================================================================================ 九十、reverse() + map() 组合在 UTS Android 中的问题(重要)
-
问题:
array.reverse().map()组合可能失败reverse()会修改原数组map()中的类型转换可能失败- 组合使用时可能导致数据丢失或类型错误
-
解决方案:使用 for 循环反向遍历
// 错误:使用 reverse() + map() messages.value = rawMsgs.reverse().map((m: ChatMessage): UiChatMessage => { return { id: m.id, content: m.content } }) // 正确:使用 for 循环反向遍历 const uiMessages: UiChatMessage[] = [] for (let i = rawMsgs.length - 1; i >= 0; i--) { const m = rawMsgs[i] const uiMsg: UiChatMessage = { id: m.id, content: m.content ?? '' } uiMessages.push(uiMsg) } messages.value = uiMessages -
优点
- 不修改原数组
- 类型安全
- 更好的性能
================================================================================ 九十一、实时订阅中的字符串比较(重要)
-
问题:实时订阅回调中使用
===和!==比较字符串失败- 症状:收到消息但条件判断失败,消息不显示
- 原因:
===和!==在 UTS Android 中可能对字符串比较失败
-
解决方案:使用
==和!=进行字符串比较// 错误:使用 === 和 !== if (senderId === currentUserId.value) { return // 可能失败 } if (receiverId === currentUserId.value) { if (senderId !== merchantId.value) { return // 可能失败 } } // 正确:使用 == 和 != if (senderId == currentUserId.value) { return // 正确工作 } if (receiverId == currentUserId.value) { if (senderId != merchantId.value) { return // 正确工作 } } -
调试技巧
- 在条件判断前添加日志输出变量值
- 比较字符串时使用
==和!= - 确保变量不为 null 或 undefined
================================================================================ 九十二、多端实时同步消息处理(重要)
-
问题:多端同时在线时,自己发送的消息在其他端不显示
- 原因:实时订阅中跳过了自己发送的消息
- 错误逻辑:
if (senderId == currentUserId.value) return
-
解决方案:正确处理自己发送的消息
// 错误:跳过自己发送的消息 if (senderId == currentUserId.value) { return // 导致其他端看不到自己发的消息 } // 正确:判断消息类型并显示 const isMyMessage = (senderId == currentUserId.value) const isForMe = (receiverId == currentUserId.value) if (isMyMessage || isForMe) { const incomingMsg: UiChatMessage = { id: msgId, type: isMyMessage ? 'sent' : 'received', // 区分发送/接收 content: newMsg.getString('content') ?? '', time: timeStr } messages.value.push(incomingMsg) } -
避免重复消息
- 发送消息时不要使用乐观更新
- 等待实时订阅推送消息
- 检查消息 ID 是否已存在
// 错误:乐观更新 + 实时订阅 = 重复消息 messages.value.push(newMessage) // 乐观更新 await supabaseService.sendMessage(...) // 实时订阅会再推送一条 // 正确:只依赖实时订阅 await supabaseService.sendMessage(...) // 实时订阅推送消息 // 检查重复 for (let i = 0; i < messages.value.length; i++) { if (messages.value[i].id == msgId) { return // 已存在,跳过 } } -
最佳实践
- 统一使用实时订阅处理所有消息(包括自己发送的)
- 发送消息后不进行乐观更新
- 通过消息 ID 去重
================================================================================ 九十三、避免使用 .single() 和 forEach(重要)
-
问题:
.single()和forEach在 UTS Android 中可能失败.single()可能导致类型转换错误forEach中的类型判断可能失败
-
解决方案:使用
.limit(1)和for循环// 错误:使用 .single() const response = await supa .from('ml_orders') .select('*') .eq('id', orderId) .single() .execute() return response.data // 正确:使用 .limit(1) const response = await supa .from('ml_orders') .select('*') .eq('id', orderId) .limit(1) .execute() const rawList = response.data as any[] if (rawList.length == 0) return null return rawList[0] -
forEach 替换为 for 循环
// 错误:使用 forEach notes.forEach((note: Notification) => { if (note.type === 'order') { // === 可能失败 orderMessages.push(item) } }) // 正确:使用 for 循环 for (let i = 0; i < notes.length; i++) { const note = notes[i] if (note.type == 'order') { // 使用 == orderMessages.push(item) } }
================================================================================ 九十四、嵌套对象数据解析(重要)
-
问题:Supabase 返回的嵌套对象无法直接访问
dataObj['ml_order_items']可能返回 undefineddataObj['shipping_address']类型转换失败
-
解决方案:使用
JSON.parse(JSON.stringify())和UTSJSONObject.get()// 错误:直接访问属性 const dataObj = data as Record<string, any> const items = dataObj['ml_order_items'] // 可能失败 orderItems.value = items as OrderItemType[] // 正确:使用 UTSJSONObject 解析 const dataObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject // 解析嵌套数组 const itemsRaw = dataObj.get('ml_order_items') if (itemsRaw != null && Array.isArray(itemsRaw)) { const items = itemsRaw as any[] orderItems.value = [] for (let i = 0; i < items.length; i++) { const itemObj = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject const orderItem: OrderItemType = { id: (itemObj.get('id') ?? '') as string, product_name: (itemObj.get('product_name') ?? '') as string, // ... } orderItems.value.push(orderItem) } } // 解析嵌套对象 const addressRaw = dataObj.get('shipping_address') if (addressRaw != null) { const addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject deliveryAddress.value = { name: (addressObj.get('name') ?? '') as string, phone: (addressObj.get('phone') ?? '') as string, // ... } as AddressType } -
最佳实践
- 所有嵌套数据都需要显式解析
- 使用
??提供默认值 - 添加日志确认数据解析成功
================================================================================ 九十五、避免使用 .single() 查询单条数据(重要)
-
问题:
.single()在 UTS Android 中可能导致类型转换错误- 错误信息:
UTSArray cannot be cast to UTSJSONObject - 原因:
.single()返回的数据格式与预期不符
- 错误信息:
-
解决方案:使用
.limit(1)替代// 错误:使用 .single() const response = await supa .from('ml_user_profiles') .select('*') .eq('user_id', userId) .single() .execute() return response.data // 正确:使用 .limit(1) const response = await supa .from('ml_user_profiles') .select('*') .eq('user_id', userId) .limit(1) .execute() const rawData = response.data if (rawData == null) return null const rawList = rawData as any[] if (rawList.length == 0) return null return rawList[0] -
最佳实践
- 所有查询单条数据的场景都应使用
.limit(1) - 始终将返回数据作为数组处理
- 检查数组长度后再取第一个元素
- 所有查询单条数据的场景都应使用
================================================================================ 九十六、避免使用 .map() 处理数组转换(重要)
-
问题:
.map()中的类型转换可能在 Android 端失败- 错误信息:类型转换异常或数据丢失
- 原因:
.map()回调中的类型推断可能不正确
-
解决方案:使用
for循环替代// 错误:使用 .map() const products = rawData.map((item: any): Product => { const obj = item as UTSJSONObject return { id: obj.getString('id') ?? '', name: obj.getString('name') ?? '' } as Product }) // 正确:使用 for 循环 const products: Product[] = [] for (let i = 0; i < rawData.length; i++) { const item = rawData[i] const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const product: Product = { id: obj.getString('id') ?? '', name: obj.getString('name') ?? '' } as Product products.push(product) } -
最佳实践
- 所有数组转换都应使用
for循环 - 使用
JSON.parse(JSON.stringify())确保正确转换为 UTSJSONObject - 显式声明结果数组类型
- 所有数组转换都应使用
================================================================================ 九十七、scroll-view 必须正确配置(重要)
-
问题:scroll-view 在 Android 端无法滚动
- 原因1:使用了错误的属性语法
scroll-y而不是:scroll-y="true" - 原因2:缺少高度约束
- 原因1:使用了错误的属性语法
-
解决方案
<!-- 错误 --> <scroll-view scroll-y class="content"> <!-- 正确 --> <scroll-view :scroll-y="true" class="content">/* 父容器 */ .parent-container { flex: 1; height: 0px; /* 关键:配合 flex: 1 使用 */ display: flex; flex-direction: column; } /* scroll-view */ .content { flex: 1; height: 0px; /* 关键:确保有高度约束 */ } -
最佳实践
- scroll-view 的 scroll-y 属性必须使用绑定语法
:scroll-y="true" - 父容器和 scroll-view 都需要设置
flex: 1和height: 0px - 确保 scroll-view 有明确的高度约束
- scroll-view 的 scroll-y 属性必须使用绑定语法
================================================================================ 九十八、Record<string, any> 访问属性(重要)
-
问题:
any类型无法直接访问属性- 错误信息:
找不到名称 "xxx" - 原因:
any类型不支持直接属性访问
- 错误信息:
-
解决方案:转换为
Record<string, any>后使用索引访问// 错误:直接访问 any 类型属性 const order = orders[i] as any const status = order.status // 错误! // 正确:转换为 Record<string, any> const order = orders[i] as Record<string, any> const status = order['status'] as number // 正确 -
最佳实践
- 对于需要访问属性的对象,使用
Record<string, any>类型 - 使用索引访问
obj['property']而非点访问obj.property - 访问后使用
as Type进行类型转换
- 对于需要访问属性的对象,使用
================================================================================ 九十九、保留嵌套数据结构(重要)
-
问题:类型定义不包含嵌套字段导致数据丢失
- 例如:
OrderType不包含ml_order_items字段 - 强制转换后会丢失嵌套数据
- 例如:
-
解决方案:使用
any或Record<string, any>保留完整数据// 错误:强制转换丢失嵌套数据 const order: OrderType = { id: o.getString('id') ?? '', // ... 其他字段 } as OrderType // ml_order_items 数据丢失! // 正确:保留嵌套数据 const orderItem: Record<string, any> = { id: o.getString('id') ?? '', // ... 其他字段 ml_order_items: o.get('ml_order_items') // 保留嵌套数据 } -
最佳实践
- 如果需要访问嵌套数据,不要使用不包含这些字段的类型
- 使用
Record<string, any>或any保留完整数据结构 - 在访问嵌套数据时进行类型转换
================================================================================ 一百、Supabase 查询中整数字段处理(重要)
-
问题:Supabase 查询时整数字段导致类型转换异常
- 错误信息:
java.lang.Integer cannot be cast to java.lang.String - 原因:UTS Android 中 Supabase 查询的
.eq()、.in()等方法对整数字段需要使用字符串形式
- 错误信息:
-
解决方案:整数字段使用字符串形式
// 错误:直接使用数字 .eq('target_type', 1) .eq('status', 1) .in('category_id', [1, 2, 3]) // 正确:使用字符串形式 .eq('target_type', '1') .eq('status', '1') .in('category_id', ['1', '2', '3']) -
insert 语句中同样需要使用字符串
// 错误:直接使用数字 .insert({ target_type: 1, status: 1 }) // 正确:使用字符串形式 .insert({ target_type: '1', status: '1' }) -
常见需要处理的整数字段
target_type- 收藏类型(1:商品, 2:店铺)status- 各种状态字段order_status- 订单状态payment_status- 支付状态shipping_status- 发货状态
-
最佳实践
- 所有 Supabase 查询中的整数值都使用字符串形式
- 使用 UTSJSONObject.set() 设置整数字段时也使用字符串
- 从数据库读取的整数值需要安全转换后再使用
================================================================================ 一百零一、数据库字段类型安全转换(重要)
-
问题:数据库返回的字段类型可能与预期不符
- 错误信息:
java.lang.Integer cannot be cast to java.lang.String - 原因:UUID 字段可能返回为 Integer,需要安全转换
- 错误信息:
-
解决方案:使用类型检查后转换
// 安全获取字符串类型的 ID const targetIdRaw = itemObj.get('target_id') let targetId = '' if (targetIdRaw != null) { if (typeof targetIdRaw === 'string') { targetId = targetIdRaw as string } else if (typeof targetIdRaw === 'number') { targetId = (targetIdRaw as number).toString() } } -
通用辅助函数
// 安全获取字符串值 function safeGetString(obj: UTSJSONObject, key: string): string { const val = obj.get(key) if (val == null) return '' if (typeof val === 'string') return val as string if (typeof val === 'number') return (val as number).toString() return '' } // 安全获取数值 function safeGetNumber(obj: UTSJSONObject, key: string): number { const val = obj.get(key) if (val == null) return 0 if (typeof val === 'number') return val as number if (typeof val === 'string') return parseFloat(val as string) ?? 0 return 0 } -
最佳实践
- 所有从数据库获取的字段都进行类型检查
- 不要假设字段类型,使用 typeof 检查
- 提供默认值防止空指针异常
================================================================================ 一百零二、JSON.parse() 返回值处理(重要)
-
问题:JSON.parse() 返回 Any? 类型
- 错误信息:
参数类型不匹配:实际类型为 'Any?',预期类型为 'Any' - 原因:JSON.parse() 返回的是可空类型,不能直接传给需要 Any 类型的函数
- 错误信息:
-
解决方案:检查 null 后再使用
// 错误:直接传递 JSON.parse() 结果 const parsed = JSON.parse(specs) return formatSpecs(parsed) // 错误!parsed 是 Any? 类型 // 正确:先检查 null const parsed = JSON.parse(specs) if (parsed != null) { return formatSpecs(parsed) } return '' -
完整示例
const getSpecText = (specs: any): string => { if (specs == null) return '' if (typeof specs === 'string') { if (specs == '') return '' try { const parsed = JSON.parse(specs) if (parsed != null) { return formatSpecs(parsed) } return specs } catch (e) { return specs } } return formatSpecs(specs) } -
最佳实践
- JSON.parse() 返回值必须检查 null
- 使用 try-catch 包裹 JSON.parse() 防止解析异常
- 提供默认值或后备方案
================================================================================ 一百零三、响应式对象不能直接转换为 Map/Record(重要)
-
问题:ref 对象的值不能直接转换为 Record<string, any>
- 错误信息:
AddressTypeReactiveObject cannot be cast to Map - 原因:Vue 的响应式对象在 UTS Android 中是特殊的代理类型,不能直接转换
- 错误信息:
-
错误示例
const deliveryAddress = ref<AddressType | null>(null) const getFullAddress = (addr: any): string => { if (addr == null) return '' if (typeof addr === 'string') return addr // 错误:响应式对象不能直接转换为 Record const addrObj = addr as Record<string, any> return addrObj['province'] as string // 运行时错误 } // 模板中调用 {{ getFullAddress(deliveryAddress as any) }} -
正确解决方案:使用 JSON.parse(JSON.stringify()) 转换
const getFullAddress = (addr: any): string => { if (addr == null) return '' if (typeof addr === 'string') return addr try { // 正确:先序列化再解析为 UTSJSONObject const addrObj = JSON.parse(JSON.stringify(addr)) as UTSJSONObject const addressField = addrObj.getString('address') if (addressField != null && addressField != '') return addressField const province = addrObj.getString('province') ?? '' const city = addrObj.getString('city') ?? '' const district = addrObj.getString('district') ?? '' const detail = addrObj.getString('detail') ?? '' return province + city + district + detail } catch (e) { console.error('[getFullAddress] 解析地址失败:', e) return '' } } -
最佳实践
- 响应式对象传递给函数时,必须使用 JSON.parse(JSON.stringify()) 转换
- 转换后使用 UTSJSONObject 的 getString()、getNumber() 方法访问属性
- 使用 try-catch 包裹防止转换异常
- 对于简单场景,可以直接在模板中访问响应式对象的属性
================================================================================ 一百零四、数据库关联查询可能返回 null(重要)
-
问题:Supabase 关联查询可能返回 null
- 症状:
ml_order_items: null即使数据库有关联数据 - 原因:
- 关联表中确实没有数据
- 外键关系配置不正确
- 查询语法问题
- 症状:
-
检查步骤
// 1. 检查原始数据 console.log('[loadOrderDetail] 订单商品数据:', itemsRaw) // 2. 正确处理 null 情况 if (itemsRaw != null && Array.isArray(itemsRaw)) { // 处理数据 } else { console.warn('[loadOrderDetail] 订单商品数据为空') } -
确保 Supabase 查询正确
// 正确的关联查询语法 const response = await supa .from('ml_orders') .select(` *, ml_order_items ( id, product_id, product_name, price, quantity, image_url, specifications ) `) .eq('id', orderId) .limit(1) .execute() -
最佳实践
- 始终检查关联数据是否为 null
- 添加日志输出原始数据便于调试
- 给用户友好的提示(如"暂无商品数据")
================================================================================ 一百零五、as UTSJSONObject 不会添加方法(重要)
-
问题:直接使用
as UTSJSONObject类型断言不会让对象获得 getString/getNumber 等方法- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:类型断言只是编译时行为,不会改变运行时对象的方法
- 错误信息:
-
错误示例
const rawItems = orderData.items as any[] for(let i = 0; i < rawItems.length; i++) { // 错误:直接 as UTSJSONObject 不会添加方法 const item = rawItems[i] as UTSJSONObject // 运行时错误:getString is not a function const productId = item.getString('product_id') } -
正确解决方案:使用 JSON.parse(JSON.stringify()) 进行真正的转换
const rawItems = orderData.items as any[] for(let i = 0; i < rawItems.length; i++) { // 正确:使用 JSON.parse(JSON.stringify()) 转换 const item = JSON.parse(JSON.stringify(rawItems[i])) as UTSJSONObject // 正确工作 const productId = item.getString('product_id') } -
常见场景
- 从 Supabase 查询返回的数组元素
- 从 API 响应获取的数据
- 从页面传递的参数对象
- 嵌套在父对象中的子对象
-
最佳实践
- 所有需要调用 getString/getNumber/getArray 方法的地方,必须先用 JSON.parse(JSON.stringify()) 转换
- 不要依赖
as UTSJSONObject类型断言来添加方法 - 对于复杂对象,统一使用转换函数处理
================================================================================ 一百零六、函数间传递对象需要序列化(重要)
-
问题:在函数间传递对象或数组时,可能遇到类型转换错误
- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:Vue 响应式对象、UTSJSONObject 等特殊类型在传递过程中可能无法正确序列化
- 错误信息:
-
错误示例
// checkout.uvue 中构建商品数组 const items: any[] = [] for (let j = 0; j < group.items.length; j++) { const item = group.items[j] // Vue 响应式对象 items.push({ id: item.id, product_id: item.product_id, // ... }) } // 传递给服务层 await supabaseService.createOrder({ items: items // 可能包含响应式代理对象 }) // 服务层处理 const rawItems = orderData.items as any[] for(let i = 0; i < rawItems.length; i++) { const item = JSON.parse(JSON.stringify(rawItems[i])) as UTSJSONObject // 仍然可能失败,因为 rawItems[i] 可能是特殊对象 } -
正确解决方案:在传递前完全序列化
// 在服务层接收参数时,先将整个数组序列化 const plainItems: any[] = [] for(let k = 0; k < shopItems.length; k++) { const plainItem = JSON.parse(JSON.stringify(shopItems[k])) plainItems.push(plainItem) } // 然后传递给内部函数 const orderId = await this.createOrder({ items: plainItems // 现在是普通对象数组 }) // 在 createOrder 中处理 for(let i = 0; i < rawItems.length; i++) { const itemStr = JSON.stringify(rawItems[i]) const item = JSON.parse(itemStr) as UTSJSONObject // 现在可以正确使用 getString/getNumber } -
最佳实践
- 在函数边界处,将复杂对象序列化为普通对象
- 使用
JSON.parse(JSON.stringify(obj))确保对象是普通 JavaScript 对象 - 添加日志输出对象类型便于调试
- 对于嵌套对象,每一层都需要序列化
================================================================================ 一百零七、CSS 属性值限制(重要)
-
问题:UTS Android 不支持某些 CSS 属性值
gap属性不支持calc()函数不支持font-weight: 800等数值不支持(只支持 normal/bold/400/700)env()和constant()函数不支持
-
错误示例
/* 错误:gap 不支持 */ .container { display: flex; gap: 20px; } /* 错误:calc() 不支持 */ .card { width: calc(50% - 10px); } /* 错误:font-weight 数值不支持 */ .title { font-weight: 800; } /* 错误:env()/constant() 不支持 */ .bottom-bar { padding-bottom: env(safe-area-inset-bottom); } -
正确解决方案
/* 正确:使用 margin 替代 gap */ .container { display: flex; flex-wrap: wrap; } .item { margin: 10px; } /* 正确:使用百分比和 margin 替代 calc() */ .card { width: 48%; margin: 0 1%; } /* 正确:使用 bold 或 700 */ .title { font-weight: bold; } /* 正确:使用固定像素值 */ .bottom-bar { padding-bottom: 30px; } -
font-weight 支持的值
normal(等同于 400)bold(等同于 700)400700
-
最佳实践
- 避免使用
gap,改用margin - 避免使用
calc(),改用百分比和 margin 组合 - font-weight 只使用
normal、bold、400、700 - 安全区域适配使用固定像素值(如 30px)
- 避免使用
-
其他不支持的 CSS 属性
overflow-wrap不支持cursor不支持max-width不支持百分比值,只支持number|pixel
================================================================================ 一百零八、模板中 || 运算符限制(重要)
-
问题:模板中使用
||运算符可能导致编译错误- 错误信息:
Conditional statements must use boolean types - 原因:UTS 模板中
||左边必须是 boolean 类型
- 错误信息:
-
错误示例
<!-- 错误:|| 左边不是 boolean 类型 --> <text>{{ order.shop_name || '自营店铺' }}</text> <text>{{ item.name || '默认名称' }}</text> -
正确解决方案:使用三元表达式
<!-- 正确:使用三元表达式 --> <text>{{ order.shop_name != null && order.shop_name != '' ? order.shop_name : '自营店铺' }}</text> <text>{{ item.name != null && item.name != '' ? item.name : '默认名称' }}</text> -
最佳实践
- 模板中避免使用
||运算符处理默认值 - 使用三元表达式:
condition ? value : default - 对于字符串,使用
!= null && != ''判断
- 模板中避免使用
================================================================================ 一百零九、JSON.stringify 返回 Any? 类型(重要)
-
问题:JSON.stringify() 返回 Any? 类型
- 错误信息:
参数类型不匹配:实际类型为 'Any?',预期类型为 'Any' - 原因:JSON.stringify() 返回可空类型,不能直接使用
- 错误信息:
-
错误示例
const plainItems: any[] = [] for(let k = 0; k < shopItems.length; k++) { const plainItem = JSON.parse(JSON.stringify(shopItems[k])) plainItems.push(plainItem) // 错误!plainItem 是 Any? 类型 } -
正确解决方案:检查 null 后再使用
const plainItems: any[] = [] for(let k = 0; k < shopItems.length; k++) { const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k])) if (plainItemRaw != null) { plainItems.push(plainItemRaw as any) } } -
最佳实践
- JSON.parse() 返回值必须检查 null
- 使用
as any或其他具体类型进行类型转换
================================================================================ 一百一十、类型定义必须包含所有字段(重要)
-
问题:创建类型实例时缺少必填字段
- 错误信息:
No value passed for parameter 'xxx' - 原因:类型定义中的字段都是必填的,必须提供所有字段值
- 错误信息:
-
错误示例
type OrderItem = { id: string, order_no: string, merchant_id: string, // 必填字段 shop_name: string, // 必填字段 products: OrderProduct[] } // 错误:缺少 merchant_id 和 shop_name typedOrders.push({ id: mo['id'] as string, order_no: mo['order_no'] as string, products: mo['products'] as OrderProduct[] }) -
正确解决方案:提供所有必填字段
typedOrders.push({ id: mo['id'] as string, order_no: mo['order_no'] as string, merchant_id: (mo['merchant_id'] ?? '') as string, shop_name: (mo['shop_name'] ?? '自营店铺') as string, products: mo['products'] as OrderProduct[] }) -
最佳实践
- 检查类型定义,确认所有字段
- 使用
??提供默认值 - 对于可能为空的字段,在类型定义中使用
string | null或提供默认值
================================================================================ 一百一十一、UTSJSONObject.get() 返回值需要类型转换(重要)
-
问题:UTSJSONObject.get() 返回 Any? 类型,不能直接传给需要具体类型的函数
- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:get() 返回的是 Any? 类型,需要先转换为具体类型
- 错误信息:
-
错误示例
const item = JSON.parse(itemStr) as UTSJSONObject // 错误:get() 返回 Any?,不能直接传给 set() itemJson.set('product_id', item.get('product_id')) itemJson.set('product_name', item.get('product_name') ?? '') -
正确解决方案:先转换为具体类型
const itemParsed = JSON.parse(itemStr) if (itemParsed == null) { console.error('解析失败') continue } const item = itemParsed as UTSJSONObject // 正确:先获取值,再转换类型 let pId = item.get('product_id') if (pId == null) { pId = item.get('id') } const productId = (pId ?? '') as string itemJson.set('product_id', productId) const productName = (item.get('product_name') ?? '') as string itemJson.set('product_name', productName) -
最佳实践
- JSON.parse() 返回值先检查 null,再 as UTSJSONObject
- get() 返回值使用
??提供默认值,再as string或其他类型 - 对于可选字段,始终提供默认值
================================================================================ 一百一十二、onBackPress 生命周期问题(重要)
-
问题:onBackPress 在 <script setup> 中可能导致编译失败
- 错误信息:
Failed to fetch dynamically imported module - 原因:UTS 中 onBackPress 的调用方式可能与标准 Vue 不同
- 错误信息:
-
错误示例
<script setup lang="uts"> import { onBackPress } from '@dcloudio/uni-app' // 错误:在 setup 顶层直接调用可能导致问题 onBackPress((options) => { // ... return false }) </script> -
解决方案
- 移除 onBackPress 钩子,或使用其他方式处理返回事件
- 如果需要拦截返回,考虑在页面跳转逻辑中处理
-
最佳实践
- 避免在 <script setup> 中使用 onBackPress
- 使用页面跳转参数或状态管理处理导航逻辑
================================================================================ 一百一十三、UTSJSONObject 不能直接用于 Supabase insert(重要)
-
问题:UTSJSONObject 不能直接传给 Supabase 的 insert 方法
- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:Supabase insert 需要普通 JavaScript 对象,不是 UTSJSONObject
- 错误信息:
-
错误示例
const itemJson = new UTSJSONObject() itemJson.set('order_id', orderId) itemJson.set('product_id', productId) // ... // 错误:直接传 UTSJSONObject const response = await supa .from('ml_order_items') .insert(itemJson) // 错误! .execute() -
正确解决方案:转换为普通对象
const itemJson = new UTSJSONObject() itemJson.set('order_id', orderId) itemJson.set('product_id', productId) // ... // 正确:先转换为普通对象 const itemObjStr = JSON.stringify(itemJson) const itemObj = JSON.parse(itemObjStr) as Record<string, any> const response = await supa .from('ml_order_items') .insert(itemObj) // 正确 .execute() -
最佳实践
- Supabase insert/update 操作必须使用普通对象
- 使用 JSON.stringify + JSON.parse 转换 UTSJSONObject
- 转换后使用
as Record<string, any>类型 - 重要:JSON.parse 返回 Any?,必须先检查 null 再转换
-
完整示例
// 错误:JSON.parse 返回 Any?,不能直接 as const itemObj = JSON.parse(itemObjStr) as Record<string, any> // 正确:先检查 null const itemObjParsed = JSON.parse(itemObjStr) if (itemObjParsed == null) { console.error('转换失败') continue } const itemObj = itemObjParsed as Record<string, any>
================================================================================ 一百一十四、数组元素不能直接 as Record<string, any>(重要)
-
问题:从 Supabase 查询返回的数组元素不能直接转换为 Record<string, any>
- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:数组元素是特殊的 UTS 对象,不能直接类型断言
- 错误信息:
-
错误示例
const queryData = queryResponse.data as any if (Array.isArray(queryData) && queryData.length > 0) { // 错误:不能直接 as Record<string, any> const firstItem = queryData[0] as Record<string, any> const orderId = firstItem['id'] as string } -
正确解决方案:使用 JSON 序列化转换
const queryData = queryResponse.data as any if (Array.isArray(queryData) && queryData.length > 0) { const firstItemRaw = queryData[0] // 正确:使用 JSON 序列化转换 const firstItemStr = JSON.stringify(firstItemRaw) const firstItemParsed = JSON.parse(firstItemStr) if (firstItemParsed == null) { console.error('解析失败') return null } const firstItem = firstItemParsed as UTSJSONObject const orderId = (firstItem.getString('id') ?? '') as string } -
最佳实践
- 从 Supabase 返回的数组元素必须先序列化再访问
- 使用 UTSJSONObject 的 getString/getNumber 方法访问属性
- 始终检查 JSON.parse 返回值是否为 null
================================================================================ 一百一十五、currentPage.options 不能直接类型转换(重要)
-
问题:currentPage.options 不能直接 as Record<string, any>
- 错误信息:
OnLoadOptions cannot be cast to Map - 原因:页面选项是特殊的 UTS 类型,不能直接转换
- 错误信息:
-
错误示例
import { onMounted } from 'vue' onMounted(() => { const pages = getCurrentPages() const currentPage = pages[pages.length - 1] // 错误:不能直接 as Record<string, any> const options = currentPage.options as Record<string, any> const orderId = options['orderId'] }) -
正确解决方案:使用 onLoad 生命周期获取页面参数
import { onLoad } from '@dcloudio/uni-app' // 正确:使用 onLoad 获取页面参数 onLoad((options) => { if (options != null) { const orderIdValue = options['orderId'] if (orderIdValue != null) { orderId.value = orderIdValue as string } } }) -
最佳实践
- 使用 onLoad 生命周期获取页面参数,而不是 getCurrentPages()
- onLoad 的 options 参数可以直接通过 key 访问
- 始终检查 options 和参数值是否为 null
================================================================================ 一百一十六、uni.getStorageSync 返回值不能直接类型转换(重要)
-
问题:uni.getStorageSync 返回的存储数据不能直接 as Record<string, any>
- 错误信息:
UTSJSONObject cannot be cast to io.dcloud.uts.Map - 原因:存储数据可能是特殊的 UTS 类型,不能直接转换
- 错误信息:
-
错误示例
const userStore = uni.getStorageSync('userInfo') if (userStore != null) { // 错误:不能直接 as Record<string, any> const userObj = userStore as Record<string, any> const id = userObj['id'] } -
正确解决方案:使用 JSON 序列化转换
const userStore = uni.getStorageSync('userInfo') if (userStore != null) { // 正确:使用 JSON 序列化转换 const userStr = JSON.stringify(userStore) const userParsed = JSON.parse(userStr) if (userParsed != null) { const userObj = userParsed as UTSJSONObject const id = userObj.getString('id') if (id != null) { return id } } } -
最佳实践
- uni.getStorageSync 返回值必须先序列化再访问
- 使用 UTSJSONObject 的 getString/getNumber 方法访问属性
- 始终检查 JSON.parse 返回值是否为 null
================================================================================ 一百一十七、JSON.parse 空字符串会报错(重要)
-
问题:JSON.parse 空字符串会抛出异常
- 错误信息:
JSON.parse error: input text is empty - 原因:空字符串不是有效的 JSON
- 错误信息:
-
错误示例
const ordersStr = uni.getStorageSync('orders') if (ordersStr != null) { // 错误:ordersStr 可能是空字符串 '' const parsed = JSON.parse(ordersStr as string) } -
正确解决方案:检查空字符串
const ordersStr = uni.getStorageSync('orders') if (ordersStr != null && ordersStr !== '') { const parsed = JSON.parse(ordersStr as string) // ... } -
最佳实践
- JSON.parse 前检查字符串是否为空
- 使用
!= null && !== ''双重检查 - uni.getStorageSync 可能返回空字符串
================================================================================ 一百一十八、自定义类型不能转换为 Record<string, any>(重要)
-
问题:自定义 type 类型不能直接转换为 Record<string, any>
- 错误信息:
OrderItem cannot be cast to io.dcloud.uts.Map - 原因:UTS 中的自定义类型是独立的类,不是 Map
- 错误信息:
-
错误示例
type OrderItem = { id: string, order_no: string, create_time: string } const orders: OrderItem[] = [...] // 错误:尝试将 OrderItem 转换为 Record orders.sort((a: any, b: any) => { const aObj = a as Record<string, any> // 错误! const bObj = b as Record<string, any> // 错误! return new Date(aObj['create_time']).getTime() - new Date(bObj['create_time']).getTime() }) -
正确解决方案:直接使用类型属性
type OrderItem = { id: string, order_no: string, create_time: string } const orders: OrderItem[] = [...] // 正确:直接使用 OrderItem 类型访问属性 orders.sort((a: OrderItem, b: OrderItem) => { return new Date(a.create_time).getTime() - new Date(b.create_time).getTime() }) -
最佳实践
- 自定义类型数组排序时,直接使用类型声明参数
- 不要尝试将自定义类型转换为 Record<string, any>
- 使用点号访问属性,而不是方括号
================================================================================ 一百一十九、函数参数类型必须精确匹配(重要)
-
问题:函数参数类型必须与传入的类型精确匹配
- 错误信息:
参数类型不匹配:实际类型为 'UTSArray<OrderItem>',预期类型为 'UTSArray<Any>' - 原因:UTS 是强类型语言,不支持隐式类型转换
- 错误信息:
-
错误示例
type OrderItem = { id: string, status: number } // 错误:参数声明为 any[] const updateTabsCounts = (allOrders: any[]) => { const countPending = allOrders.filter((o: any) => { const item = o as OrderItem // 多余的转换 return item.status === 1 }).length } const orders: OrderItem[] = [...] updateTabsCounts(orders) // 错误!类型不匹配 -
正确解决方案:使用精确的类型声明
type OrderItem = { id: string, status: number } // 正确:参数声明为 OrderItem[] const updateTabsCounts = (allOrders: OrderItem[]) => { const countPending = allOrders.filter((o: OrderItem) => o.status === 1).length } const orders: OrderItem[] = [...] updateTabsCounts(orders) // 正确!类型匹配 -
最佳实践
- 函数参数类型必须与实际传入类型一致
- 避免使用 any[] 作为自定义类型数组的参数
- 在 filter/map/sort 等回调中直接使用具体类型
================================================================================ 一百二十、Object.keys() 不支持 UTSJSONObject(重要)
-
问题:Object.keys() 不能用于 UTSJSONObject
- 错误信息:
找不到名称"keys" - 原因:UTSJSONObject 有自己的 keys() 方法
- 错误信息:
-
错误示例
const obj = orderParsed as UTSJSONObject // 错误:Object.keys() 不支持 UTSJSONObject const keys = Object.keys(obj) -
正确解决方案:使用 UTSJSONObject.keys() 方法
const obj = orderParsed as UTSJSONObject // 正确:使用 UTSJSONObject 的 keys() 方法 const keys = obj.keys() for (let i = 0; i < keys.length; i++) { const key = keys[i] const value = obj.get(key) console.log(key, value) } -
最佳实践
- UTSJSONObject 使用
obj.keys()获取键列表 - 使用
obj.get(key)获取值 - 使用
obj.getString(key)/obj.getNumber(key)获取特定类型的值
- UTSJSONObject 使用
================================================================================ 一百二十一、Supabase 关联查询语法(重要)
-
问题:多行模板字符串可能导致关联查询失败
- 现象:关联表数据返回 null
- 原因:Supabase JavaScript 客户端处理多行字符串时可能有问题
-
错误示例
// 可能失败的多行语法 .select(` *, ml_order_items (*) `) -
正确解决方案:使用单行字符串
// 正确:单行字符串 .select('*, ml_order_items(*)') -
最佳实践
- Supabase 查询使用单行字符串
- 关联查询格式:
'*, related_table(*)' - 多个关联:
'*, table1(*), table2(*)'
================================================================================ 一百二十二、UTSJSONObject.keys() 方法在 Android 端不可用(重要)
-
问题:
UTSJSONObject.keys()方法在 Android 端编译失败- 错误信息:
找不到名称"keys" - 原因:Android 端 UTSJSONObject 可能没有 keys() 方法
- 错误信息:
-
错误示例
const specObj = objParsed as UTSJSONObject // 错误:keys() 方法不可用 const keys = specObj.keys() for (let i = 0; i < keys.length; i++) { const key = keys[i] // ... } -
正确解决方案:直接获取已知字段或使用 JSON 字符串处理
const specObj = objParsed as UTSJSONObject // 方案一:直接获取已知字段 const colorVal = specObj.getString('Color') const sizeVal = specObj.getString('Size') const defaultVal = specObj.getString('默认') // 方案二:使用 JSON 字符串处理 const specObjStr = JSON.stringify(specObj) const formatted = specObjStr.replace(/[{}"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ') -
最佳实践
- 避免使用
UTSJSONObject.keys()方法 - 如果知道字段名,直接使用
getString()获取 - 如果需要遍历,使用 JSON 字符串处理
- 避免使用
================================================================================ 一百二十三、函数声明顺序必须正确(重要)
-
问题:使用
const声明的函数在调用前必须已定义- 错误信息:
找不到名称"xxx" - 原因:
const箭头函数在定义前无法被调用
- 错误信息:
-
错误示例
// 错误:formatSpecObj 在定义前被调用 const parseSpecText = (specs: any): string => { return formatSpecObj(specs) // 错误!formatSpecObj 还未定义 } const formatSpecObj = (obj: any): string => { return '...' } -
正确解决方案:使用
function声明或调整顺序// 方案一:使用 function 声明(推荐) function formatSpecObj(obj: any): string { return '...' } function parseSpecText(specs: any): string { return formatSpecObj(specs) // 正确!function 声明会提升 } // 方案二:调整定义顺序 const formatSpecObj = (obj: any): string => { return '...' } const parseSpecText = (specs: any): string => { return formatSpecObj(specs) // 正确!formatSpecObj 已定义 } -
最佳实践
- 优先使用
function声明,避免顺序问题 - 如果使用
const箭头函数,确保定义在被调用之前
- 优先使用
================================================================================ 一百二十四、String() 构造函数不支持任意类型(重要)
-
问题:
String(value)不支持任意类型转换- 错误信息:
None of the following candidates is applicable - 原因:UTS 中 String() 只接受特定类型
- 错误信息:
-
错误示例
const value: any = obj.get(key) // 错误:String() 不接受 any 类型 const str = String(value) -
正确解决方案:使用类型判断
const value: any = obj.get(key) let valueStr = '' if (typeof value === 'string') { valueStr = value } else if (typeof value === 'number') { valueStr = value.toString() } else { valueStr = JSON.stringify(value) } -
最佳实践
- 使用
typeof判断类型后再转换 - 数字使用
.toString()方法 - 对象使用
JSON.stringify()
- 使用
================================================================================ 一百二十五、scroll-view 必须设置确定高度(重要)
-
问题:scroll-view 无法滚动
- 现象:页面内容超出屏幕但无法向下滚动
- 原因:scroll-view 没有确定的高度
-
错误示例
.orders-content { flex: 1; } -
正确解决方案:添加 height: 0 配合 flex: 1
.orders-content { flex: 1; height: 0; /* 重要:配合 flex: 1 使用 */ } -
最佳实践
- scroll-view 必须有确定的高度
- 使用
flex: 1; height: 0;组合 - 或者直接设置具体高度如
height: 500px
================================================================================ 一百二十六、Supabase 多表关联查询(重要)
-
问题:需要从关联表获取数据
- 场景:订单表需要获取商家店铺名称
- 前提:必须在 Supabase 中设置正确的外键关系
-
外键设置要求
- 在 Supabase 控制台设置外键关系
- 例如:
ml_orders.merchant_id->ml_merchants.id - 没有外键关系会导致 400 Bad Request 错误
-
示例:订单关联商家和订单项
// 关联查询多个表(需要先设置外键) const response = await supa .from('ml_orders') .select('*, ml_order_items(*), ml_merchants(shop_name)') .eq('user_id', userId) .execute() -
如果没有外键关系的替代方案
// 方案一:使用默认值 let shopName = '自营店铺' if (merchantId != null && merchantId != '') { shopName = '商家店铺' } // 方案二:单独查询商家表 const merchantResponse = await supa .from('ml_merchants') .select('shop_name') .eq('id', merchantId) .single() .execute() -
最佳实践
- 关联查询前确保外键关系已设置
- 使用单行字符串格式:
'*, table1(*), table2(field1, field2)' - 如果关联查询失败,使用替代方案
================================================================================ 一百二十七、Android 特有 UI 与交互规范(最新补充)
-
图标兼容性(Emoji 风险)
- 现象:在 Android 真机上直接使用 Emoji(如 🛒、❤️)可能导致显示不全、模糊或样式不统一。
- 规范:所有功能图标必须使用本地 PNG 图片。
- 路径:底部导航图标放在
static/tabbar/,业务小图标放在pages/mall/consumer/icons/。 - TabBar 特别强调:
pages.json中的tabBar图标路径严禁使用.svg,必须使用.png以确保安卓稳定性。
-
布局高度计算修复
- 场景:Chat 页面或带有固定底部的详情页。
- 规范:
scroll-view的父级必须是flex: column,且调用scroll-view时应配合flex: 1; height: 0;。 - 双重滚动校准:Android 初始渲染可能导致滚动不到底。
setTimeout(() => { this.scrollTop = 99999 }, 200); // 首次快速定位 setTimeout(() => { this.scrollTop = 99999 + Math.random() }, 500); // 二次精准校准
-
空状态展示(去行业化文案)
- 规范:在数据加载中或为空时,严禁使用行业特定干扰词(如“暂无药品”)。
- 方案:统一使用“正在加载商品...”或“该分类下暂无商品”,并辅以
loading-state动画,提升加载感知。
-
SKU 解析与展示规范
- 规范:从数据库返回的 JSON 规格数据(如
sku_spec_attr)必须通过格式化函数转换为易读文本。 - 示例:
[{"key":"规格","value":"10mg*7片"}]->规格: 10mg*7片。 - 技巧:在
uts中处理UTSJSONObject时,推荐使用getString("value")进行容错处理。
- 规范:从数据库返回的 JSON 规格数据(如
================================================================================ 文档结束
最后更新:2026-03-04