主题
uni-app 生命周期与适配(一)
本页对应 uni-app 生命周期与适配,共 7 段视频,约 1 小时 7 分。
学习目标
- 能够使用
env(safe-area-inset-*)为页面设置安全区间距,并解释 H5 浏览器中该值为 0 的原因。 - 能够使用
#ifdef、#ifndef、#endif编写 uni-app 条件编译,并正确选择APP、H5、MP-WEIXIN等平台标识。 - 能够在 JS、样式和模板中分别编写条件编译代码,并验证不同平台下的执行结果。
- 能够按“创建查询器 → 查询节点 → 执行查询”三步获取节点宽高尺寸,并将查询逻辑放入合适的生命周期中。
- 能够区分
select、selectAll、selectViewport的查询结果,并使用boundingClientRect、scrollOffset获取节点位置或滚动信息。 - 能够按 easycom 规范在
components目录下创建组件,保证组件名与目录名一致,并实现免导入、免注册直接使用。
知识点
一、先复现问题:内容为什么会被“小条”和“刘海”吃掉
为什么需要:手机屏幕越来越不规则,顶部有刘海/状态栏,底部有 Home Indicator 小条。页面如果直接从屏幕最上、最下开始写,内容就可能被遮住。转写稿里先恢复代码复现:页面内容跑到了底部小条下面;如果这个位置恰好有 A 链接或 tabBar,点击时就会和小条手势冲突,甚至点不住。顶部同理,加文字后“111”会被截掉一部分。
怎么想:安全区不是“某一个固定高度”,而是屏幕留给内容的矩形区域。顶部刘海往下、底部小条往上,才是安全可写区域。既然遮挡来自边界,就应把页面内容往安全区内“挤”。
怎么做:先给页面容器加 padding-top / padding-bottom,必要时也可用 margin。转写稿提醒:margin 有可能把父级带下来,实际更常用 padding。
易错点:不要只处理底部或只处理顶部;不要以为页面“看起来没内容”就没问题,交互元素被盖住才是大坑。
二、解决思路:间距不能写死,要拿“变量”
为什么需要:知道加间距能解决,但写多少?写 20、40、50 还是 100?转写稿明确说:写 100 肯定能避开,但留白太大;有的手机没有小条就是 0,有的可能是 20,有的可能是 40。这个值随手机变化。
怎么想:既然间距是变化的,就不能写死固定值,而要获取一个“变量”。昨天通过 JS 获取,今天通过 CSS 获取;两种方式都要掌握。
怎么做:CSS 里使用安全区变量,例如:
css
/* 不推荐:写死间距,换手机可能不够或留白太大 */
.page {
padding-top: 20px;
padding-bottom: 20px;
}
/* 推荐:使用安全区变量,值由设备决定 */
.page {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}转写稿里说 padding-top 后面给的不是 20px,而是一个“挺长的东西”,可以理解成变量:它的值可能是 10,也可能是 20,取决于手机。底部同理,padding-bottom 给安全区变量后,滚到底会出现一块空白,这块空白就是给底部小条让出来的。
易错点:不要写成 env(safe-area-inset-top)px;env() 返回的是长度值,直接使用。不要只记顶部,safe-area-inset-* 是矩形区域,有上下左右,按需处理。
三、真机与模拟器:为什么模拟器里“没生效”
为什么需要:转写稿里加 padding-top: env(safe-area-inset-top) 后,在模拟器里看不出来;改成固定 10px 又能看到。于是容易误判“变量没生效”。
怎么想:先验证固定值能否把内容挤下来,能,说明选择器和写法没问题;再换变量,模拟器没变化,就要怀疑环境差异。转写稿结论是:top 值绝对可以用,但模拟器里看不到效果,需要真机验证。
怎么做:用 iPhone 真机截图验证。转写稿展示:写了 safe-area-inset-top 后,上面出现空白,正好把天线/刘海区域让出来;也就是从显示信号往下写才是安全的。底部变量则让页面滚到底出现空白,避开小条。
易错点:开发工具模拟器本质上是浏览器/WebView,不能完全代表真机安全区。不要因为模拟器没变化就删掉变量;要真机看。
四、补充结论:浏览器里这个值为什么是 0
为什么需要:转写稿最后补充:在浏览器中这个值永远是 0。学生容易问“那是不是没生效”。
怎么想:浏览器对手机来说本身也是一个 APP。浏览器 APP 已经帮你处理过安全区,页面不是从天线底下开始,而是从状态栏往下开始。因此页面再取 safe-area-inset-top 时,值就是 0。
怎么做:理解“浏览器已处理安全区”这个前提;在 H5 里看到 0 是正常现象,不代表真机小程序/App 里也是 0。开发工具模拟器类似浏览器,所以也常看到 0。
易错点:不要把“浏览器里为 0”当成“安全区变量没用”;也不要把安全区只当成小程序知识。转写稿强调:只要在手机上开发,H5、App、安卓、iOS 都有安全区概念,只是获取方式不同。
五、易错点清单
- 写死间距:20/40/100 都不稳,设备不同值不同。
- 只处理底部:顶部刘海同样会截内容。
- 忽略点击冲突:A 链接、tabBar 被小条盖住时,先查安全区。
- 模拟器误判:
safe-area-inset-top在模拟器可能看不到,真机验证。 - 浏览器里为 0 就慌:浏览器已处理安全区,0 是正常结果。
- 忘记两种方式:JS 获取和 CSS 变量获取都要会;本节重点是 CSS 方式。
条件编译先当成“兼容处理”来理解
在 uni-app 里,同一段代码可能运行在 H5、App、微信小程序等不同平台。页面样式会不一样,API 也可能不一样:比如微信登录相关 API 只能在小程序里用,浏览器里没有对应能力。为什么需要条件编译?因为如果不区分平台,代码要么在某个端报错,要么样式互相干扰。转写稿里反复强调:听到“条件编译”,可以先把它对等成“兼容处理”。它的作用就是让不同平台执行不同的代码,从而既兼容网页布局,也兼容不同端的 API 调用。
怎么想?不要把它想成高深编译器,它本质上是一个“带条件的特殊注释”:条件满足,注释包住的代码才参与运行;条件不满足,这段代码就不执行。先接受这个心智模型,再看语法。
基本语法:特殊注释包住代码
怎么做?在 JS 中,普通注释是 //,但条件编译要在注释里写 #ifdef、#endif 这类标记。开始和结束必须成对出现:
js
// #ifdef APP-PLUS
console.log('运行在 APP')
// #endif这里的 ifdef 可以理解为“如果定义了某平台”,endif 表示条件块结束。上面代码的意思是:如果运行在 App 平台,才执行 console.log。在 H5 中不会执行。转写稿中的验证过程是:先写 APP 条件,在 H5 页面看不到日志;把条件改成 H5 后,日志就出来了。
否定条件用 ifndef,n 可以理解为 no:
js
// #ifndef MP-WEIXIN
console.log('不是运行在微信小程序')
// #endif它的意思是:如果不是微信小程序,才执行。转写稿中验证:H5 里能打印,微信小程序里不打印。还可以用两个竖线表示“或”:
js
// #ifdef H5 || APP-PLUS
console.log('运行在 H5 或 App')
// #endif易错点:转写稿特别提醒,只有“或”,没有“且”。因为一段代码通常只运行在一个端里,不能既同时是 App 又是小程序,所以不要想当然写 &&。另外平台名不要凭感觉写,记不住就查官方文档;转写稿提到常见写法如 APP-PLUS、APP-ANDROID、APP-IOS、H5、MP、MP-WEIXIN、MP-ALIPAY,但不必死背。
样式和模板里也能条件编译
为什么需要?因为兼容不只发生在 JS。比如 H5 希望标题往下一点,小程序里间距已经够了,这时就要让样式按平台生效。
怎么做?样式里不能写 //,要用 CSS 注释,但注释内部仍要写 #ifdef:
css
/* #ifdef H5 */
.padding-top {
padding-top: 20rpx;
}
/* #endif */转写稿中的调试过程是:给 H5 加 padding-top 后,H5 间距变大,小程序不变;说明样式条件编译生效。模板里同理,用 HTML 注释:
html
<!-- #ifdef H5 -->
<view class="search-icon">放大镜</view>
<!-- #endif -->这样只有 H5 才显示这个图标,小程序里不显示。易错点:不要用编辑器快捷键直接生成普通注释,样式里必须是 /* ... */,模板里必须是 <!-- ... -->;注释内部的开头标记和结束标记要成对,平台名写错也会导致条件不生效。调试时先确认代码放在正确文件位置,再分别到 H5 和小程序端观察结果。
条件编译不是新语法,而是兼容处理
为什么需要:uni-app 一套代码要跑在 H5、App、小程序等多个端,但不同端的样式、API 并不完全一样。转写稿里举的例子很直接:uni.login 是微信登录相关 API,浏览器里没有;页面布局在网页和小程序里也可能表现不同。所以必须有一种“按平台决定哪段代码生效”的机制。
怎么想:不要被“编译”两个字吓到。转写稿明确说,条件编译本质上就是“兼容处理”。它本身是注释,只是注释里写了特殊标记,编译器看到标记后决定这段代码在哪个端保留。
怎么做:基本语法是成对出现的特殊注释:
js
// #ifdef APP
console.log('运行在 APP')
// #endif
// #ifdef H5
console.log('运行在 H5')
// #endif#ifdef 表示“如果定义了某平台”,#endif 表示结束。反着写用 #ifndef:
js
// #ifndef MP-WEIXIN
console.log('不是运行在微信小程序')
// #endif易错点:第一,#ifdef 和 #endif 必须成对,少一个结束标记会出问题;第二,“或”是两个竖线 ||,转写稿里特意纠正过自己“画了一个竖线”,并强调“只有或,没有且”,因为一段代码不可能同时既跑在 App 又跑在小程序;第三,平台名不要凭感觉写,写错通常不报错,但代码不生效。
在样式中写条件编译:H5 标题下移
为什么需要:转写稿里的实际需求是:H5 端觉得标题离顶部太近,想往下一点;但小程序里这个距离已经够了,不想再动。这时只改 JS 不够,需要让 CSS 也按平台生效。
怎么想:条件编译不仅能写在 JS 里,也能写在样式和模板里。样式里仍然用注释,但必须是 CSS 的标准注释 /* */,不能按快捷键生成 //。
怎么做:找到标题对应的类名,在样式里包一层条件:
css
/* #ifdef H5 */
.uni-title {
margin-top: 20rpx;
}
/* #endif */转写稿的调试过程值得注意:先写 padding-top,保存后 H5 没下来。检查发现组件自身有 style,优先级更高,把 padding 覆盖了;于是换 margin 试,结果“把父亲给拽下来了”,最后改为给父级容器加间距,H5 的间距才正常变大,而小程序不变。
易错点:样式条件编译的注释不要写成 //;遇到不生效先检查是不是被内置样式覆盖;改 margin 时注意可能影响父级布局,必要时给父元素加。
在模板中写条件编译:H5 才显示放大镜
为什么需要:有些结构只希望在某个端出现。转写稿里用“放大镜小图标”演示:H5 端显示,小程序端不显示。
怎么想:模板里同样写特殊注释,把需要条件生效的结构包起来。
怎么做:
html
<!-- #ifdef H5 -->
<view class="search-icon">🔍</view>
<!-- #endif -->保存后到 H5 看,小图标存在;切到小程序,图标消失,说明模板条件编译生效。
易错点:模板注释是 <!-- -->,不要和 JS、CSS 的注释混用;条件标记要包住完整结构,不要只包一半标签。
在 JS 中验证 #ifndef
为什么需要:前面写了 #ifndef MP-WEIXIN,需要确认它到底是不是“不是微信小程序才运行”。
怎么想:在 H5 里应该打印,在小程序里应该不打印。
怎么做:
js
// #ifndef MP-WEIXIN
console.log('不是运行在微信小程序')
// #endif转写稿的验证结果是:H5 里这句话打印出来了,小程序里没有。说明 #ifndef 是否定判断。
易错点:不要把 #ifndef 理解成“所有平台都运行”;它只排除指定平台。
平台名称与查文档
为什么需要:平台很多,名称没必要死记,但写错就不生效。
怎么想:知道常用写法,剩下的查官方文档。转写稿提到在“教程 → 编译器 → 条件编译”里能查到平台名称。
怎么做:常用写法包括 APP、APP-ANDROID、APP-IOS、H5、MP、MP-WEIXIN、MP-ALIPAY 等。MP 表示小程序大类,MP-WEIXIN 表示微信小程序。
易错点:平台名大小写和连字符要按文档写;不要自己发明名称。最后补充一个转写稿里的调试结论:安全区域的 safe-area-inset-top 在真机上有效,但在浏览器/模拟器里通常为 0,因为浏览器本身已经处理过安全区域,不能因为模拟器看不到就误判代码没生效。
代码示例
示例一:用 CSS 变量处理屏幕安全区域(H5 / 真机通用)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>屏幕安全区域</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
.page {
min-height: 100%;
display: flex;
flex-direction: column;
background: #f5f5f5;
/* 顶部刘海区域:值由手机自己决定,不是固定值 */
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
/* 底部小条区域 */
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.content {
flex: 1;
padding: 20px;
}
.tabbar {
height: 50px;
line-height: 50px;
text-align: center;
background: #fff;
border-top: 1px solid #ddd;
/* 不做安全区处理时,小条会压住这个 tabBar,点不住 */
}
</style>
</head>
<body>
<div class="page">
<div class="content">
<h3>页面内容</h3>
<p>111 顶部文字如果不加安全区间距,会被刘海遮掉一部分。</p>
</div>
<div class="tabbar">tabBar 底部按钮</div>
</div>
</body>
</html>要点解释
padding-top: env(safe-area-inset-top)这一行就是“通过样式拿到安全区域的边界值”,它不是固定数值,所以不用纠结写 20 还是 40,手机自己给值。- 一定要写成
constant(...)再写一遍env(...):老系统只认前者,新系统认后者,写两行是兼容写法。 - 把间距加在最外层容器上,而不是加在某个子元素上,否则内容会被挤歪。
- 易错点:在浏览器和 HBuilderX 的模拟器里,这个值永远是 0,因为浏览器本身就是一个 APP,已经帮你把安全区让出来了,页面从刘海下面才开始。看不到效果不是代码没生效,必须上真机验证。
- 易错点:底部不加
padding-bottom时,tabBar或 A 链接会被底部小条盖住,点击会冲突,这就是必须处理的根本原因。
示例二:uni-app 条件编译(模板 / 样式 / JS 三处)
vue
<template>
<view class="page-bar">
<view class="title">uni-app 条件编译</view>
<!-- #ifdef H5 -->
<view class="h5-tip">这段模板只在 H5 渲染</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="other-tip">这段模板在非微信小程序端渲染</view>
<!-- #endif -->
</view>
</template>
<script setup>
import { onMounted } from 'vue'
// #ifdef APP
console.log('运行在 APP')
// #endif
// #ifndef MP-WEIXIN
console.log('不是运行在微信小程序')
// #endif
// #ifdef H5 || APP
console.log('运行在 H5 或者 APP')
// #endif
onMounted(() => {
// #ifdef H5
console.log('H5 端 mounted 时才执行')
// #endif
})
</script>
<style>
/* 注意:这里必须是 CSS 的块注释,不能用 // */
.page-bar {
/* #ifdef H5 */
padding-top: 20rpx;
/* #endif */
}
/* #ifdef MP-WEIXIN */
.page-bar {
padding-top: 0;
}
/* #endif */
</style>要点解释
- 条件编译的本质就是一段特殊的注释:
// #ifdef 平台开始,// #endif结束,中间才是真正生效的代码,这就是“条件编译”名字的由来。 #ifdef是“如果是这个平台才执行”,#ifndef是“如果不是这个平台才执行”,#endif负责收尾,少写结尾会导致整段代码被吞掉。- 多平台用两个竖线表示“或”:
#ifdef H5 || APP。只有“或”、没有“且”,因为一段代码不可能同时跑在两个端上。 - 常用平台名:
APP、APP-ANDROID、APP-IOS、H5、MP(所有小程序)、MP-WEIXIN、MP-ALIPAY,记不住就查官方文档“教程 → 编译器 → 条件编译”。 - 易错点(样式里最容易踩):在
<style>中写条件编译,必须用/* ... */,如果你按编辑器的注释快捷键,它可能给你生成//,样式里会直接失效。 - 易错点:样式条件编译要作用在父级容器上。给子元素写
margin-top会把它父亲一起拽下来(外边距合并),所以示例里给.page-bar加padding-top。 - 易错点:如果组件自身配置里已经有
style,你外面再写同名属性会被覆盖,这时换一个不冲突的属性(比如用margin代替padding)。
示例三:uni-app 节点信息获取(三步走 + 滚动位置)
vue
<template>
<view class="container">
<view class="box"></view>
<button @click="getScroll">获取滚动位置</button>
</view>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 第一步:创建查询器
const query = uni.createSelectorQuery()
// 第二步:查询元素 + 第三步:exec 执行
query
.select('.box')
.boundingClientRect(res => {
console.log('单个盒子信息', res) // width/height/top/left/right/bottom,单位 px
})
.exec()
// 查询全部同名节点,结果一定是数组
uni
.createSelectorQuery()
.selectAll('.box')
.boundingClientRect(res => {
console.log('全部盒子信息', res)
})
.exec()
// 查询视口(不需要传参数)
uni
.createSelectorQuery()
.selectViewport()
.boundingClientRect(res => {
console.log('视口信息', res)
})
.exec()
})
function getScroll() {
uni
.createSelectorQuery()
.selectViewport()
.scrollOffset(res => {
console.log('滚动位置', res) // scrollTop / scrollLeft
})
.exec()
}
</script>
<style>
/* 只是为了让页面能滚动 */
.container { padding-top: 1500rpx; }
.box {
width: 300rpx;
height: 200rpx;
background-color: pink;
margin-top: 100rpx;
margin-left: 120rpx;
}
</style>要点解释
- 固定三步:
uni.createSelectorQuery()创建查询器 →.select('.box')选择节点 →.exec()执行。漏掉exec()回调一次都不会触发,这是最常见的坑。 - 查询必须放在
onMounted这类生命周期里执行。节点还没渲染完就查询,会拿不到属性(报读不到属性的错误),这跟网页里 DOM 未加载就querySelector是同一个道理。 .select()取满足条件的第一个,.selectAll()取全部且永远返回数组(哪怕只有一个),.selectViewport()取视口,它唯一,所以不能传参数。- 回调里的
res含width / height / top / left / right / bottom,单位是 px,而你写样式用的rpx,中间已经做过换算,300rpx/200rpx 拿到的是 150/100。 top、left是相对“已定位的祖先元素”计算的;如果页面里谁都没定位,就是相对视口。scrollOffset只能用于视口和scroll-view,因为它们才存在滚动;boundingClientRect是万能的,谁都能用。- 易错点:把
scrollOffset写在onMounted里,拿到的永远是 0,因为页面刚加载还没滚动。正确做法是绑到按钮点击事件,用户滚动一段距离后再动态获取,这时scrollTop才有值。
常见错误
1. 安全区域间距写成固定像素
- 现象:加了
padding-bottom: 20px,在有 Home 条的机型上仍被遮挡,在无 Home 条的机型上又凭空多出一块空白。 - 原因:安全区域 inset 值随机型变化(可能是 0、20、34…),固定值无法自适应。
- 修法:改用
env(safe-area-inset-bottom)/env(safe-area-inset-top)这类变量值,并真机验证;不要用浏览器或模拟器"没变化"来判断是否失效。
2. 在 H5/模拟器里验证安全区域,得出"没生效"的错误结论
- 现象:
padding-top: env(safe-area-inset-top)写完页面毫无变化,怀疑写错。 - 原因:浏览器本身是宿主 App,已经把安全区域让出来了,所以视口内该值恒为 0;开发工具的内置 webview 同理。
- 修法:结论类验证一律上真机;模拟器只用来调布局结构。
3. 条件编译注释写错位置或漏写结束标记
- 现象:整段代码不执行,或者本该只在 H5 生效的代码在所有端都生效。
- 原因:JS 里写成
/* #ifdef */、CSS 里写成// #ifdef;或只写了#ifdef忘了#endif,导致作用域一直延伸到文件末尾。 - 修法:按文件类型选注释符——模板
<!-- #ifdef H5 -->、JS// #ifdef H5、样式/* #ifdef H5 */;成对书写,写完立刻补#endif。
4. 条件编译里写"且"条件
- 现象:
#ifdef APP || H5手误写成单个|,编译报错或条件失效。 - 原因:条件编译只有"或",没有"且"(同一份代码不可能同时跑在两个端上)。
- 修法:或写两个竖线
||;确实需要"且"的场景,用嵌套的两层#ifdef或改写逻辑。
5. 节点查询写在 setup 顶层或忘记 exec
- 现象:回调不执行、
res为null,或控制台直接报"读不到属性"。 - 原因:一是在节点尚未渲染时执行查询;二是
select().boundingClientRect(cb)只是登记,不调exec()不会真正发起查询。 - 修法:把整段查询放进
onMounted/onReady里;三步链路末尾必须.exec()。
6. 在页面加载时就取 scrollTop,结果永远是 0
- 现象:
scrollOffset拿到的scrollTop始终为 0,滚动后重新打印还是 0。 - 原因:
onMounted只执行一次,此时页面还没滚动。 - 修法:放到滚动之后触发(按钮点击、滚动回调、
onPageScroll)再查询;同时确认查询目标是selectViewport()或scroll-view,其他元素拿不到滚动信息。
7. easycom 组件"不导入直接用"却报组件找不到
- 现象:
<custom-demo />提示组件未注册。 - 原因:组件没放在根目录
components下,或文件夹名与组件文件名不一致,或建组件时先写名字后选模板被工具清空。 - 修法:根目录
components/组件名/组件名.vue严格同名;用右键"新建组件"时先选模板再填名字。
练习
练习 1(安全区域) 给一个带底部 tabBar 的页面做安全区适配:内容区底部留出 Home 条高度、顶部留出刘海高度。完成后说明:为什么在 Chrome 和 HBuilderX 模拟器里看到的都是"没效果",你用什么方式证明它是对的。
练习 2(条件编译三处落地) 在同一页面里完成三件事,每处都用条件编译: ① 页面主标题在 H5 下额外下移 20rpx,小程序不动; ② 模板中一个搜索图标只在 H5 显示; ③ JS 中一段 console.log('H5') 只在非 H5 环境打印。 要求写出三处各自的注释写法,并说明为什么不能都用 //。
练习 3(平台差异兼容) 用条件编译实现:微信小程序端调用 uni.login 走微信登录,H5 端改为打印"请使用微信打开"。写完后分别在两个端运行,确认另一端代码确实没有执行。
练习 4(节点信息三步) 页面上放一个 300rpx × 200rpx 的方块,要求:① 打印它相对视口的宽高与 left/top;② 给方块加 margin-top: 100rpx; margin-left: 60rpx 后再打印一次,对比数值变化;③ 在页面滚动一段距离后,通过按钮点击拿到 scrollTop。 自查:三步链路是否完整?查询放在哪个生命周期里?为什么滚动位置不能在加载时取?
练习 5(easycom 规范) 在根目录新建 components/custom-demo/custom-demo.vue,在首页不导入不注册直接使用,验证能正常渲染。然后故意把文件夹改名、把文件放到 pages 下各试一次,记录报错信息,总结规范的三条硬性要求。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 补充内容-屏幕安全区域 | 10:39 |
| 2 | uni-app条件编译-基本语法 | 13:11 |
| 3 | uni-app条件编译-实际应用 | 08:41 |
| 4 | uni-app节点获取的步骤 | 12:20 |
| 5 | uni-app节点获取方式 | 06:36 |
| 6 | uni-app节点对象信息 | 08:05 |
| 7 | uni-app组件规范easycom | 08:23 |
小结
- 安全区域是"手机屏幕不规则"带来的通用问题,不是小程序专有;H5、App、原生开发都会遇到,区别只在获取方式。
- 安全区域间距的本质是"值不固定",所以固定像素一定错,必须用变量;样式侧和 JS 侧两条路都要会。
- 浏览器和模拟器里安全区域值恒为 0 是正常现象而非 bug——宿主 App 已代为处理,验证必须上真机。
- 条件编译 = 兼容处理:同一份源码按平台裁剪,是 uni-app 处理样式差异、API 差异的统一手段。
- 条件编译的载体永远是"特殊注释",注释符随文件类型变化(模板/JS/样式三种写法),漏写
#endif是最常见的事故来源。 - 条件编译只有"或"没有"且",平台名以官方文档为准,不必背,用时查。
- 节点查询是"创建查询器 → 选择节点 → exec 执行"的三步链路,缺任何一步都拿不到结果;且必须放在节点渲染完成之后。
boundingClientRect管尺寸位置、scrollOffset管滚动(仅 viewport 和 scroll-view 可用);滚动类信息要在"滚动之后"取,加载时取必然是 0。- easycom 规范的本质是"用命名约定换取免导入",三条硬要求:根目录
components、文件夹与文件同名、按规范新建。