主题
小程序项目目录分析(Day1 2/2)
本页对应 day1,共 4 段视频,约 2 小时 3 分。
学习目标
- 能依据小程序目录结构新建页面,正确配置页面路径与页面标题,并说明
.wxml、.wxss、.js、.json四类文件的职责。 - 能分别使用
navigator组件和wx.navigateTo、wx.redirectTo实现页面跳转,正确书写路径与 query 参数,并通过返回栈和生命周期日志验证页面保留或销毁差异。 - 能列举应用级生命周期
onLaunch、onShow、onHide、onError,并通过埋点日志验证其触发时机。 - 能列举页面级生命周期
onLoad、onShow、onReady、onHide、onUnload,通过日志验证触发顺序,并在onLoad中获取路由参数。 - 能定义
onShareAppMessage实现页面分享,区分系统菜单与button open-type="share"触发方式,返回自定义title、path、imageUrl并完成真机验证。 - 能完成 tabBar、轮播图、分类/商品列表与返回顶部小练习,使用滚动监听和
wx.pageScrollTo实现交互,并检查移动端布局适配效果。
知识点
先看项目根目录:配置文件与应用文件
为什么需要:刚拿到一个小程序项目,如果不知道每个文件管什么,就会出现“样式改了没生效、页面跳转找不到、搜索搜不到”的问题。先认目录,是为了知道改哪里、为什么改。
怎么想:把根目录分成三类看——配置文件(sitemap.json、project.config.json)、应用级文件(app.js、app.json、app.wxss)、页面目录(pages)。
怎么做:按笔记整理出的结构先认一遍:
text
├─ sitemap.json
├─ project.config.json
├─ app.js
├─ app.json
├─ app.wxss
└─ pages/
└─ 页面目录/
├─ 页面.wxml
├─ 页面.wxss
├─ 页面.js
└─ 页面.json易错点:不要把根目录的 app.* 和 pages 里的页面文件混在一起。app.* 是应用级,pages 里才是具体页面。
sitemap.json:控制微信内搜索
为什么需要:小程序可能被用户在微信里搜索到,是否允许索引、索引哪些页面,需要配置。
怎么想:它属于“项目配置”而不是页面结构;先知道它管搜索索引,不要在页面样式里找答案。
怎么做:在根目录找到 sitemap.json,按项目需要配置搜索索引规则;本节先记住它的作用。
易错点:转写稿里把“路由”写成“录由”,要纠正为“路由”;sitemap.json 不是页面路由文件,别和 app.json 的 pages 混淆。
project.config.json:项目配置文件
为什么需要:开发者工具打开项目时,需要知道项目配置,比如工具层面的项目设置。
怎么想:它服务于“项目怎么被工具打开和配置”,不是页面业务逻辑。
怎么做:在根目录识别 project.config.json,项目配置优先看它;不要把页面标题、页面路径写到这里。
易错点:页面标题、路由应去 app.json 或页面 json 里找,不要都塞进 project.config.json。
app.js:创建小程序应用
为什么需要:小程序需要一个应用实例,应用级逻辑要有入口。
怎么想:笔记写得很明确:app.js 里是 App 函数,用于创建微信小程序应用函数。转写稿里应用级生命周期也写在 app.js,说明它是应用级入口。
怎么做:在 app.js 中通过 App() 注册小程序;应用级逻辑放这里,页面逻辑放页面 js。
易错点:不要把某个页面的业务逻辑写进 app.js;页面级生命周期应写在 pages 下对应页面的 js 里。
app.json:导航栏与页面路由
为什么需要:小程序有多个页面时,必须告诉应用“有哪些页面、导航栏长什么样”。
怎么想:app.json 是全局配置,笔记说它管“小程序应用导航栏、页面路由的位置”。转写稿调试时也反复在 app.json 的 pages 里补页面路径。
怎么做:在 app.json 的 pages 中登记页面路径;转写稿里新建 detail 页面后,要手动补路由,路径形如 pages/detail/detail。跳转时转写稿调试发现:页面跳转路径要带前导斜杠,如 /pages/detail/detail;而 app.json 里配路由不带这个前导斜杠。每个页面还可以用自己的 json 单独设置标题。
易错点:转写稿里的“录由”应写作“路由”,“详企页”应写作“详情页”。最常见错误是 app.json 路由和跳转 url 的斜杠写法混用。
app.wxss:全局样式
为什么需要:多个页面共用的样式,如果每个页面都复制一遍,维护成本高。
怎么想:笔记说 app.wxss 是“书写全局样式的地方,应用当中全部页面都可以使用样式”。所以公共样式放这里,页面私有样式放页面 wxss。
怎么做:把全页面通用的基础样式写进 app.wxss;某个页面独有的布局和颜色写进该页面的 .wxss。
易错点:页面样式不生效时,先分清是写在 app.wxss 还是页面 wxss;不要把所有样式都堆到全局。
pages:页面目录与四文件
为什么需要:小程序的页面不是单个文件,而是一个目录下的四个文件,分别管结构、样式、逻辑、配置。
怎么想:转写稿里新建 detail 页面时,不是只建一个文件;调试跳转时也强调“不是跳到哪一个文件上,是跳到这个页面上”,因为一个页面由四个文件组成。
怎么做:在 pages 下建页面目录,页面需要四个文件:.wxml 写页面结构;.wxss 写页面样式;.js 写当前页面逻辑;.json 写当前页面配置。新建页面后,记得去 app.json 的 pages 里补路由;页面跳转时写页面路径,不写某个具体文件。
易错点:转写稿里“table bar”应纠正为 tabBar,但本节先记住:pages 是页面目录;页面跳转路径要指向“页面”,不是 .wxml/.js 文件;路由配置和跳转路径的斜杠规则不要混。
课堂画面




代码示例
示例一:页面跳转(声明式 / 编程式)+ 生命周期 + 路由传参
app.json
json
{
"pages": [
"pages/home/home",
"pages/detail/detail"
],
"window": {
"navigationBarTitleText": "首页"
}
}pages/detail/detail.json(页面级配置,单独设置标题)
json
{
"navigationBarTitleText": "详情页"
}pages/home/home.wxml
xml
<view class="home">
<!-- 声明式导航:navigator 组件 -->
<navigator url="/pages/detail/detail">
<view class="icon iconfont icon-xiaomubiao"></view>
</navigator>
<view>声明式导航</view>
<!-- 编程式导航:按钮绑定事件 -->
<view>编程式导航</view>
<button type="primary" size="mini" bindtap="goDetail">
<text class="icon iconfont icon-xiaomubiao"></text>点击我进入详情页
</button>
</view>pages/home/home.js
js
Page({
data: {},
// 编程式导航:保留当前页面,A 页面不会被销毁
goDetail() {
wx.navigateTo({
url: '/pages/detail/detail?A=1&B=2'
})
},
// ---- 页面级生命周期(5 个)----
onLoad(options) {
console.log('onLoad 页面加载', options)
},
onShow() {
console.log('onShow 页面显示')
},
onReady() {
console.log('onReady 页面初次渲染完成')
},
onHide() {
console.log('onHide 页面隐藏')
},
onUnload() {
console.log('onUnload 页面卸载')
}
})pages/detail/detail.js
js
Page({
data: {
a: '',
b: ''
},
// 只有 onLoad 会注入路由参数,onShow/onReady 等都拿不到
onLoad(options) {
console.log('详情页 onLoad 参数:', options) // { A: '1', B: '2' }
this.setData({
a: options.A,
b: options.B
})
},
// 关闭当前页面跳转(相当于 vue-router 的 replace)
backToHome() {
wx.redirectTo({
url: '/pages/home/home'
})
}
})app.js(应用级生命周期,4 个)
js
App({
onLaunch() {
console.log('onLaunch 小程序初始化完成,全局只触发一次')
},
onShow() {
console.log('onShow 小程序启动或从后台进入前台')
},
onHide() {
console.log('onHide 小程序从前台进入后台')
},
onError(msg) {
console.log('onError 小程序发生错误', msg)
}
})要点解释与易错点
navigator的url与wx.navigateTo的url必须带前导斜杠/pages/detail/detail,且路径是“页面”(不带.js/.wxml后缀),不是文件。app.json里的pages路由不写前导斜杠,而页面跳转时要写前导斜杠,这是最容易混的一点。wx.navigateTo保留当前页面(A 不销毁,只触发onHide);wx.redirectTo关闭当前页面(A 被销毁,触发onUnload)。- 两者都不能跳转 tabBar 页面,只能
wx.switchTab。 - 页面级钩子 5 个:
onLoad / onShow / onReady / onHide / onUnload;应用级 4 个:onLaunch / onShow / onHide / onError。 - 路由参数只有
onLoad(options)能拿到,onShow、onReady都不会注入参数。 Page配置对象里还有很多配置项(如onPageScroll),它们不是生命周期钩子,只是页面事件处理函数。
示例二:微信小程序分享功能(onShareAppMessage)
pages/detail/detail.wxml
xml
<view class="detail">我是详情页</view>
<!-- 自定义分享按钮:必须写 open-type="share" -->
<button type="primary" size="mini" open-type="share">分享</button>pages/detail/detail.js
js
Page({
data: {},
// 用户点击右上角“三个点”转发,或点击 open-type="share" 的按钮时触发
onShareAppMessage(options) {
console.log('from:', options.from) // 'menu' 系统右上角 / 'button' 自定义按钮
if (options.from === 'menu') {
return {
title: '我来自系统右上角三个点',
path: '/pages/detail/detail',
imageUrl: 'https://img.alicdn.com/imgextra/i1/xxx.jpg'
}
}
return {
title: '我来自自定义按钮',
path: '/pages/detail/detail',
imageUrl: 'https://img.alicdn.com/imgextra/i2/xxx.jpg'
}
}
})要点解释与易错点
- 只有页面里定义了
onShareAppMessage,右上角的转发按钮才会出现;首页没定义就没有分享入口。 - 该函数必须
return一个对象,常用字段:title(转发标题)、path(转发页面路径,必须以/开头)、imageUrl(支持本地图片或网络图片)。 options.from用来区分分享来源:menu表示系统右上角,button表示页面内open-type="share"的按钮。- 自定义分享按钮必须加
open-type="share",只写bindtap不会触发分享。 onShareAppMessage不是生命周期钩子,是页面配置函数。- 真机上验证分享时,可在右上角“三个点 → 开发调试”里打开调试,方便看日志。
示例三:tabBar 配置 + swiper 轮播 + flex 分类 + 返回顶部
app.json
json
{
"pages": [
"pages/home/home",
"pages/cart/cart",
"pages/center/center"
],
"window": {
"navigationBarTitleText": "首页"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#ff0000",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/static/tabbar/home.png",
"selectedIconPath": "/static/tabbar/home-active.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "/static/tabbar/cart.png",
"selectedIconPath": "/static/tabbar/cart-active.png"
},
{
"pagePath": "pages/center/center",
"text": "个人中心",
"iconPath": "/static/tabbar/center.png",
"selectedIconPath": "/static/tabbar/center-active.png"
}
]
}
}pages/home/home.wxml
xml
<view class="home">
<!-- 轮播图 -->
<swiper class="swiper"
indicator-dots
indicator-color="rgba(255,255,255,.5)"
indicator-active-color="red"
autoplay
interval="1000"
circular>
<swiper-item>
<image class="img" src="https://img.alicdn.com/imgextra/i1/banner1.jpg"></image>
</swiper-item>
<swiper-item>
<image class="img" src="https://img.alicdn.com/imgextra/i2/banner2.jpg"></image>
</swiper-item>
<swiper-item>
<image class="img" src="https://img.alicdn.com/imgextra/i3/banner3.jpg"></image>
</swiper-item>
</swiper>
<!-- 分类:两行,每项 20% -->
<view class="cate">
<view class="item" wx:for="{{10}}" wx:key="*this">
<view class="icon iconfont icon-xiaomubiao"></view>
<text class="title">前端开发</text>
</view>
</view>
<!-- 商品列表:每项约 45% -->
<view class="goods">
<view class="item" wx:for="{{100}}" wx:key="*this">
<image class="img" src="https://img.alicdn.com/imgextra/i4/kaoya.jpg"></image>
<view class="name">烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭烤鸭</view>
</view>
</view>
<!-- 回到顶部 -->
<view class="arrow" wx:if="{{isShow}}" bindtap="goTop">
<text class="icon iconfont icon-shang"></text>
</view>
</view>pages/home/home.wxss
css
.home {
width: 100%;
height: 100%;
}
/* 轮播图:宽 750rpx(iPhone6 满屏),高 400rpx */
.swiper {
width: 750rpx;
height: 400rpx;
}
.swiper .img {
width: 750rpx;
height: 400rpx;
}
/* 分类:flex 换行,每项 20% */
.cate {
display: flex;
flex-wrap: wrap;
}
.cate .item {
width: 20%;
height: 150rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.cate .title {
font-size: 24rpx;
margin-top: 10rpx;
}
/* 商品:flex 换行 + 两端对齐,每项 45% */
.goods {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.goods .item {
width: 45%;
}
.goods .img {
width: 100%;
height: 300rpx;
}
/* 标题两行溢出省略 */
.goods .name {
height: 90rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
/* 回到顶部:固定定位 */
.arrow {
position: fixed;
right: 20rpx;
bottom: 200rpx;
}pages/home/home.js
js
Page({
data: {
isShow: false // 控制箭头显示与隐藏
},
// 页面滚动时触发,e.scrollTop 为滚动距离
onPageScroll(e) {
const isShow = e.scrollTop >= 300
if (isShow !== this.data.isShow) {
this.setData({ isShow })
}
},
// 点击箭头回到顶部
goTop() {
wx.pageScrollTo({
scrollTop: 0,
duration: 300
})
}
})要点解释与易错点
tabBar.list最少 2 个、最多 5 个页面,且每个pagePath必须在pages中已定义;color/selectedColor是 tabBar 级别的配置。iconPath/selectedIconPath只支持本地图片,不支持网络图片,大小限制 40KB,建议 81×81。- tabBar 页面之间只能用
wx.switchTab,navigateTo/redirectTo跳不过去。 swiper里只能放swiper-item;swiper-item宽高自动 100%,所以要给swiper定尺寸(宽 750rpx、高 400rpx),再让内部image与之同宽高。- 轮播常用属性:
indicator-dots(指示点)、indicator-color、indicator-active-color、autoplay、interval(毫秒)、circular(衔接滑动,不加会从最后一张“倒回”第一张)。 - flex 换行必须写
flex-wrap: wrap,否则分类会挤成一行;justify-content写错成just之类不会报错但布局不生效。 - 商品两行省略要写全四行:
overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;,少一行就看不到省略号。 - 移动端样式一般按 iPhone6(375px = 750rpx)写死尺寸,别写死 px。
onPageScroll是页面事件处理函数(不是 5 个生命周期钩子之一),wx.pageScrollTo的scrollTop: 0即回到顶部,duration默认约 300ms。
常见错误
跳转报
page "pages/detail/detail.js" is not found现象:点击后页面不动,控制台报路径找不到。 原因:把 url 写成了某个文件的路径(带.js/.wxml后缀),或目录层级漏写。 修法:跳转的是"页面"不是文件,写/pages/detail/detail,后缀由框架自己补。首页跳详情正常,详情跳首页却失效 现象:同一个
navigator写法,换到二级页面点了没反应。 原因:url 用了相对路径pages/detail/detail,页面层级一变就解析错。 修法:navigator的 url 一律以/开头写绝对路径。wx.navigateTo跳不过去,报can not navigateTo a tabbar page现象:配好 tabBar 后,原来的跳转代码全部报错。 原因:navigateTo / redirectTo 只能跳非 tabBar 页面。 修法:跳 tabBar 页面用wx.switchTab,navigator上加open-type="switchTab"。onLoad里打印options是 undefined 现象:跳转时明明带了?id=1,详情页取不到。 原因:在onShow/onReady里取;或参数拼在了 app.json 的路由配置里而不是跳转时。 修法:只有onLoad会注入 options,onLoad(options)里取完立刻存进 data。右上角没有"转发",或转发出去还是默认标题 现象:页面点三个点没有分享入口,或分享内容不生效。 原因:页面没定义
onShareAppMessage(不定义就不显示转发入口);或定义了但没return对象,return写在了判断分支外。 修法:函数体内必须return { title, path, imageUrl },path 以/开头,需要区分来源时用res.from判断后再 return。tabBar 不显示,或只有文字没有图标 现象:底部栏没出来 / 图标区域空白。 原因:list 少于 2 项或超过 5 项;path 没在 app.json 的 pages 里注册;图标用了网络地址或超过 40KB。 修法:list 保持 2–5 项、path 与 pages 完全一致、图标用本地图片(81×81,<40KB)。
onPageScroll里this是 undefined,或滚动明显掉帧 现象:滚动时控制台报错,或页面一滚就卡。 原因:用了普通函数导致 this 丢失;每滚动一次就setData一次。 修法:用箭头函数或在onLoad里缓存this;只在状态翻转(跨过阈值)时才setData。swiper 高度塌陷,图片只露一角 现象:轮播区域高度为 0,或图片撑不满容器。 原因:swiper 没给高度;
image没设宽高,默认 320×240 不跟随容器。 修法:给 swiper 定高(如 300rpx),image宽高各 100%,按需配 mode。
练习
带参跳转:首页放三个商品块,点击分别跳到详情页,跳转时携带
id和name。详情页在onLoad中接收并渲染到页面,同时在控制台打印完整 options。 验收:刷新详情页能拿到参数;返回首页再点另一个商品,参数随之变化。两种跳转对比:首页用
navigator跳详情,详情页分别用wx.navigateTo和wx.redirectTo回首页各试一次。在首页和详情页的onLoad、onShow、onHide、onUnload里都打日志。 验收:能用自己的话说明两次返回时首页分别走了哪几个钩子、为什么。自定义分享:详情页实现
onShareAppMessage,自定义标题、带id的路径、自定义图片;再加一个open-type="share"的 button,用res.from判断来源,系统转发和按钮转发返回不同标题。 验收:真机点右上角三个点能转发,点按钮也能转发,两者标题不同,点进去落在详情页。tabBar + 轮播:配置 3 个 tabBar 页面(首页 / 购物车 / 个人中心),首页放 swiper 三张图,开启指示点、自动播放、衔接滑动,自定义指示点颜色。 验收:切换 tab 不报错;轮播自动播放且首尾无缝;从首页用 navigateTo 跳 tabBar 页面时能说清为什么失败。
回到顶部:商品列表滚动超过 300 时显示右下角箭头,点击后用
wx.pageScrollTo平滑回到顶部,回到顶部后箭头自动隐藏。 验收:快速来回滚动不卡顿,箭头显隐无闪烁。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 微信小程序页面的跳转 | 19:06 |
| 2 | 微信小程序的生命周期函数 | 27:04 |
| 3 | 微信小程序的分享功能 | 17:46 |
| 4 | 小练习 | 59:49 |
小结
- 声明式导航负责"结构里的固定入口",编程式导航负责"逻辑判断后再跳",两者能力对等——
navigator的open-type就是navigateTo / redirectTo / switchTab的翻版。 - 记牢三个"不能":不能跳 tabBar 页面、路径不能带文件后缀、不能省前导斜杠。这三条占了跳转报错的大半。
navigateTo和redirectTo的差别不在跳转结果,而在页面栈:前者让上一页onHide,后者让上一页onUnload,直接决定返回时会不会重新onLoad。- 生命周期重点记页面级 5 个,应用级 4 个知道即可;日常真正高频的只有
onLoad(取参、发请求)和onShow(刷新数据)。 - 参数只有
onLoad的 options 能拿到,拿到就立刻存 data,不要指望onShow再取一次。 onShareAppMessage是个开关:不写就没有转发入口,写了就必须 return 对象。- 样式是硬功夫——很多"组件用不出来"的问题,本质是父容器没给宽高、flex 忘了
wrap、子项宽度没算清间距。移动端 flex + rpx 必须练到不用想。 - 遇到没见过的配置项或 API,先看官方文档里的参数提示和示例代码,别凭感觉猜。