Skip to content

小程序项目目录分析(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 文件;路由配置和跳转路径的斜杠规则不要混。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

代码示例 ​

示例一:页面跳转(声明式 / 编程式)+ 生命周期 + 路由传参 ​

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。

常见错误 ​

  1. 跳转报 page "pages/detail/detail.js" is not found 现象:点击后页面不动,控制台报路径找不到。 原因:把 url 写成了某个文件的路径(带 .js / .wxml 后缀),或目录层级漏写。 修法:跳转的是"页面"不是文件,写 /pages/detail/detail,后缀由框架自己补。

  2. 首页跳详情正常,详情跳首页却失效 现象:同一个 navigator 写法,换到二级页面点了没反应。 原因:url 用了相对路径 pages/detail/detail,页面层级一变就解析错。 修法:navigator 的 url 一律以 / 开头写绝对路径。

  3. wx.navigateTo 跳不过去,报 can not navigateTo a tabbar page 现象:配好 tabBar 后,原来的跳转代码全部报错。 原因:navigateTo / redirectTo 只能跳非 tabBar 页面。 修法:跳 tabBar 页面用 wx.switchTab,navigator 上加 open-type="switchTab"。

  4. onLoad 里打印 options 是 undefined 现象:跳转时明明带了 ?id=1,详情页取不到。 原因:在 onShow / onReady 里取;或参数拼在了 app.json 的路由配置里而不是跳转时。 修法:只有 onLoad 会注入 options,onLoad(options) 里取完立刻存进 data。

  5. 右上角没有"转发",或转发出去还是默认标题 现象:页面点三个点没有分享入口,或分享内容不生效。 原因:页面没定义 onShareAppMessage(不定义就不显示转发入口);或定义了但没 return 对象,return 写在了判断分支外。 修法:函数体内必须 return { title, path, imageUrl },path 以 / 开头,需要区分来源时用 res.from 判断后再 return。

  6. tabBar 不显示,或只有文字没有图标 现象:底部栏没出来 / 图标区域空白。 原因:list 少于 2 项或超过 5 项;path 没在 app.json 的 pages 里注册;图标用了网络地址或超过 40KB。 修法:list 保持 2–5 项、path 与 pages 完全一致、图标用本地图片(81×81,<40KB)。

  7. onPageScroll 里 this 是 undefined,或滚动明显掉帧 现象:滚动时控制台报错,或页面一滚就卡。 原因:用了普通函数导致 this 丢失;每滚动一次就 setData 一次。 修法:用箭头函数或在 onLoad 里缓存 this;只在状态翻转(跨过阈值)时才 setData。

  8. swiper 高度塌陷,图片只露一角 现象:轮播区域高度为 0,或图片撑不满容器。 原因:swiper 没给高度;image 没设宽高,默认 320×240 不跟随容器。 修法:给 swiper 定高(如 300rpx),image 宽高各 100%,按需配 mode。

练习 ​

  1. 带参跳转:首页放三个商品块,点击分别跳到详情页,跳转时携带 id 和 name。详情页在 onLoad 中接收并渲染到页面,同时在控制台打印完整 options。 验收:刷新详情页能拿到参数;返回首页再点另一个商品,参数随之变化。

  2. 两种跳转对比:首页用 navigator 跳详情,详情页分别用 wx.navigateTo 和 wx.redirectTo 回首页各试一次。在首页和详情页的 onLoad、onShow、onHide、onUnload 里都打日志。 验收:能用自己的话说明两次返回时首页分别走了哪几个钩子、为什么。

  3. 自定义分享:详情页实现 onShareAppMessage,自定义标题、带 id 的路径、自定义图片;再加一个 open-type="share" 的 button,用 res.from 判断来源,系统转发和按钮转发返回不同标题。 验收:真机点右上角三个点能转发,点按钮也能转发,两者标题不同,点进去落在详情页。

  4. tabBar + 轮播:配置 3 个 tabBar 页面(首页 / 购物车 / 个人中心),首页放 swiper 三张图,开启指示点、自动播放、衔接滑动,自定义指示点颜色。 验收:切换 tab 不报错;轮播自动播放且首尾无缝;从首页用 navigateTo 跳 tabBar 页面时能说清为什么失败。

  5. 回到顶部:商品列表滚动超过 300 时显示右下角箭头,点击后用 wx.pageScrollTo 平滑回到顶部,回到顶部后箭头自动隐藏。 验收:快速来回滚动不卡顿,箭头显隐无闪烁。

原视频对照 ​

#视频时长
1微信小程序页面的跳转19:06
2微信小程序的生命周期函数27:04
3微信小程序的分享功能17:46
4小练习59:49

小结 ​

  1. 声明式导航负责"结构里的固定入口",编程式导航负责"逻辑判断后再跳",两者能力对等——navigator 的 open-type 就是 navigateTo / redirectTo / switchTab 的翻版。
  2. 记牢三个"不能":不能跳 tabBar 页面、路径不能带文件后缀、不能省前导斜杠。这三条占了跳转报错的大半。
  3. navigateTo 和 redirectTo 的差别不在跳转结果,而在页面栈:前者让上一页 onHide,后者让上一页 onUnload,直接决定返回时会不会重新 onLoad。
  4. 生命周期重点记页面级 5 个,应用级 4 个知道即可;日常真正高频的只有 onLoad(取参、发请求)和 onShow(刷新数据)。
  5. 参数只有 onLoad 的 options 能拿到,拿到就立刻存 data,不要指望 onShow 再取一次。
  6. onShareAppMessage 是个开关:不写就没有转发入口,写了就必须 return 对象。
  7. 样式是硬功夫——很多"组件用不出来"的问题,本质是父容器没给宽高、flex 忘了 wrap、子项宽度没算清间距。移动端 flex + rpx 必须练到不用想。
  8. 遇到没见过的配置项或 API,先看官方文档里的参数提示和示例代码,别凭感觉猜。