微信小程序 - 02 基础概念层与核心能力层

本文目标

本文覆盖微信小程序基础概念和核心能力,重点解决"能不能写出正确功能"的问题。学习完成后,应能独立实现页面、组件、数据绑定、事件、路由、请求、登录、存储和基础业务交互。

本章节知识图谱

基础概念层与核心能力层
前置知识
JavaScript
CSS盒模型
Promise
HTTP
基础结构
app.json
页面文件
WXML
WXSS
JS逻辑
JSON配置
页面机制
Page
生命周期
data
setData
事件绑定
组件机制
Component
properties
triggerEvent
slot
externalClasses
核心API
路由
网络请求
登录授权
本地存储
文件上传
面试重点
setData原理
生命周期顺序
路由栈限制
组件通信
登录流程

1. 小程序项目结构

是什么

微信小程序项目通常由全局配置、页面、组件、工具方法、服务接口和静态资源组成。最小项目包括 app.jsonapp.jsapp.wxss 和至少一个页面。

为什么重要

项目结构决定团队协作效率。早期结构混乱会导致后续组件复用困难、接口封装重复、页面间耦合严重。

底层原理

小程序启动时先读取全局配置,加载入口脚本和首屏页面。页面路径必须在 app.json 中注册,基础库根据配置创建页面实例并触发生命周期。

使用场景

  • 新项目初始化。
  • 团队规范制定。
  • 老项目重构。

代码示例

json 复制代码
{
  "pages": [
    "pages/home/index",
    "pages/detail/index",
    "pages/profile/index"
  ],
  "window": {
    "navigationBarTitleText": "知识库示例",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/home/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/profile/index",
        "text": "我的"
      }
    ]
  }
}

推荐目录:

text 复制代码
miniprogram/
  app.js
  app.json
  app.wxss
  pages/
    home/index.js
    home/index.wxml
    home/index.wxss
    detail/index.js
  components/
    product-card/
  services/
    request.js
    product.js
  store/
  utils/
  config/
  assets/

常见错误

  • 页面文件存在,但没有在 app.json 注册。
  • 所有代码都写在页面里,后期无法复用。
  • 静态资源随意放置,包体不可控。

最佳实践

  • 页面只负责展示和轻量交互。
  • 接口放到 services
  • 工具方法放到 utils
  • 通用 UI 放到 components
  • 配置按环境拆分。

关联知识点

项目结构关联页面机制、组件化、工程实践、分包和长期维护。

面试考察方式

面试官通常会让候选人描述小程序目录结构,并追问为什么要拆 servicescomponentsutils

2. WXML 与 WXSS

是什么

WXML 是小程序模板语言,负责描述页面结构;WXSS 是样式语言,类似 CSS,但增加了 rpx、样式隔离等小程序特性。

为什么重要

WXML/WXSS 是页面表现层基础。写不好会导致布局不稳定、适配差、组件样式互相污染。

底层原理

WXML 会被基础库编译成可供视图层渲染的数据结构。逻辑层通过 setData 改变数据后,视图层根据模板和新数据更新界面。WXSS 经过转换后作用于对应页面或组件。

使用场景

  • 页面布局。
  • 条件渲染。
  • 列表渲染。
  • 响应式适配。

代码示例

xml 复制代码
<!-- pages/home/index.wxml -->
<view class="page">
  <view class="title">课程列表</view>
  <block wx:for="{{courses}}" wx:key="id">
    <view class="course" bindtap="goDetail" data-id="{{item.id}}">
      <image class="cover" src="{{item.cover}}" mode="aspectFill" />
      <view class="info">
        <view class="name">{{item.name}}</view>
        <view class="desc">{{item.desc}}</view>
      </view>
    </view>
  </block>
  <view wx:if="{{courses.length === 0}}" class="empty">暂无课程</view>
</view>
css 复制代码
/* pages/home/index.wxss */
.page {
  padding: 24rpx;
  background: #f6f7f9;
  min-height: 100vh;
}

.title {
  font-size: 36rpx;
  font-weight: 600;
  margin-bottom: 20rpx;
}

.course {
  display: flex;
  padding: 20rpx;
  margin-bottom: 16rpx;
  background: #fff;
  border-radius: 8rpx;
}

.cover {
  width: 160rpx;
  height: 120rpx;
  margin-right: 20rpx;
}

常见错误

  • wx:for 不设置稳定 wx:key
  • 用固定 px 写移动端布局,适配差。
  • 在模板里写过多复杂表达式。
  • 图片不设置尺寸导致布局抖动。

最佳实践

  • 列表必须使用稳定唯一 key。
  • 优先用 rpx 做移动端适配。
  • 条件渲染保持简单,复杂逻辑放 JS。
  • 图片、按钮、卡片设置稳定尺寸。

关联知识点

WXML/WXSS 与 setData、渲染性能、组件样式隔离密切相关。

面试考察方式

常考 wx:ifhidden 区别、wx:for 的 key、rpx 适配机制。

3. Page、生命周期与数据更新

是什么

Page 用于注册页面实例,生命周期描述页面从加载、显示、隐藏到卸载的过程。data 是页面渲染数据,setData 用于把逻辑层数据同步到视图层。

为什么重要

页面生命周期影响接口请求、状态恢复、定时器清理和性能。setData 是小程序最核心、也最容易误用的能力。

底层原理

小程序逻辑层和视图层分离。直接修改 this.data 只改逻辑层内存,不会通知视图层。setData 会把数据序列化后通过桥传给视图层,触发视图更新,因此过大、过频繁的 setData 会造成性能问题。

使用场景

  • 页面首次加载请求数据。
  • 页面重新显示刷新状态。
  • 用户交互后更新视图。
  • 页面卸载时清理定时器。

代码示例

js 复制代码
// pages/home/index.js
Page({
  data: {
    loading: false,
    courses: []
  },

  onLoad() {
    this.fetchCourses();
  },

  onShow() {
    console.log("页面显示,可在这里刷新轻量状态");
  },

  onUnload() {
    if (this.timer) {
      clearInterval(this.timer);
    }
  },

  async fetchCourses() {
    this.setData({ loading: true });
    try {
      const courses = await Promise.resolve([
        { id: 1, name: "小程序基础", desc: "页面与组件", cover: "/assets/course.png" },
        { id: 2, name: "工程实践", desc: "请求封装与分包", cover: "/assets/course.png" }
      ]);
      this.setData({ courses });
    } finally {
      this.setData({ loading: false });
    }
  },

  goDetail(event) {
    const { id } = event.currentTarget.dataset;
    wx.navigateTo({
      url: `/pages/detail/index?id=${id}`
    });
  }
});

错误写法与正确写法

错误写法:

js 复制代码
this.data.courses.push(newCourse);

正确写法:

js 复制代码
this.setData({
  courses: [...this.data.courses, newCourse]
});

局部更新正确写法:

js 复制代码
const index = 0;
this.setData({
  [`courses[${index}].name`]: "更新后的课程名"
});

常见错误

  • 直接改 this.data 后界面不更新。
  • 一次 setData 传整个大对象。
  • 高频滚动中不断 setData
  • onShow 中重复发起重型请求。

最佳实践

  • 只传视图需要的数据。
  • 合并多次小更新。
  • 长列表用分页、懒加载或虚拟列表。
  • onUnload 清理定时器和订阅。

关联知识点

关联双线程模型、渲染性能、生命周期、状态管理。

面试考察方式

高频问题:为什么不能直接修改 this.datasetData 有什么性能风险?

4. 事件系统

是什么

事件系统负责用户交互,包括点击、输入、提交、滚动等。小程序支持 bindcatch 两类绑定方式,catch 会阻止事件冒泡。

为什么重要

业务交互都依赖事件。错误的事件绑定会导致重复触发、参数获取失败、冒泡引发误操作。

底层原理

视图层捕获事件后,通过事件对象把事件类型、触发节点、数据集和表单值传给逻辑层。dataset 来源于 WXML 中的 data-* 属性。

使用场景

  • 点击卡片跳转详情。
  • 表单输入。
  • 弹窗阻止冒泡。
  • 列表项操作。

代码示例

xml 复制代码
<view class="mask" catchtap="closeDialog">
  <view class="dialog" catchtap="noop">
    <input value="{{keyword}}" bindinput="onKeywordInput" placeholder="请输入关键词" />
    <button bindtap="search" data-source="dialog">搜索</button>
  </view>
</view>
js 复制代码
Page({
  data: {
    keyword: ""
  },

  onKeywordInput(event) {
    this.setData({
      keyword: event.detail.value.trim()
    });
  },

  search(event) {
    const { source } = event.currentTarget.dataset;
    console.log("搜索来源", source);
  },

  closeDialog() {
    this.setData({ showDialog: false });
  },

  noop() {}
});

常见错误

  • event.target.dataset 取数据,点击子元素时取不到。
  • 弹窗内部点击冒泡导致弹窗关闭。
  • 输入框每次输入都发请求。

最佳实践

  • 优先使用 event.currentTarget.dataset
  • 弹窗内部使用 catchtap 阻止冒泡。
  • 输入搜索做防抖。

关联知识点

关联组件通信、表单、性能优化、用户体验。

面试考察方式

常问 targetcurrentTarget 区别、bindtapcatchtap 区别。

5. Component 与组件通信

是什么

Component 用于注册可复用组件。组件通过 properties 接收外部数据,通过 triggerEvent 向父级派发事件,通过 slot 承载内容。

为什么重要

组件化是小程序可维护性的核心。它能减少重复代码、统一交互和样式、隔离业务复杂度。

底层原理

组件实例有独立数据和生命周期。父组件或页面传入属性后,组件内部根据属性渲染;组件内部事件通过自定义事件向上通知,避免直接修改父级状态。

使用场景

  • 商品卡片。
  • 订单状态标签。
  • 空状态组件。
  • 弹窗组件。
  • 表单项组件。

代码示例

json 复制代码
{
  "component": true
}
xml 复制代码
<!-- components/product-card/index.wxml -->
<view class="card" bindtap="handleTap">
  <image class="cover" src="{{product.cover}}" mode="aspectFill" />
  <view class="name">{{product.name}}</view>
  <view class="price">¥{{product.price}}</view>
  <slot />
</view>
js 复制代码
// components/product-card/index.js
Component({
  properties: {
    product: {
      type: Object,
      value: {}
    }
  },

  methods: {
    handleTap() {
      this.triggerEvent("select", {
        id: this.properties.product.id
      });
    }
  }
});

页面使用:

json 复制代码
{
  "usingComponents": {
    "product-card": "/components/product-card/index"
  }
}
xml 复制代码
<product-card
  wx:for="{{products}}"
  wx:key="id"
  product="{{item}}"
  bind:select="goProductDetail"
/>
js 复制代码
Page({
  data: {
    products: [{ id: 1, name: "精品课程", price: 99, cover: "/assets/product.png" }]
  },

  goProductDetail(event) {
    wx.navigateTo({
      url: `/pages/detail/index?id=${event.detail.id}`
    });
  }
});

常见错误

  • 组件内部直接修改父页面数据。
  • properties 传入过大对象,导致更新成本高。
  • 组件既负责 UI 又负责复杂业务请求。

最佳实践

  • 组件输入用 properties,输出用 triggerEvent
  • 通用组件只做展示和交互,不耦合业务请求。
  • 业务组件可以封装业务状态,但要有清晰边界。
  • 使用样式隔离避免污染。

关联知识点

关联工程实践、状态管理、组件库建设。

面试考察方式

常问父子组件通信方式、组件生命周期、通用组件和业务组件区别。

6. 路由与页面栈

是什么

小程序路由用于页面跳转和返回。常用 API 包括 navigateToredirectToswitchTabreLaunchnavigateBack

为什么重要

路由决定用户流程。错误的跳转方式会导致页面栈过深、返回异常、Tab 页面无法跳转。

底层原理

小程序维护页面栈。navigateTo 会压栈,redirectTo 替换当前页面,switchTab 切换 Tab 并清理非 Tab 页面,reLaunch 关闭所有页面后打开新页面。

使用场景

  • 列表到详情:navigateTo
  • 登录成功替换登录页:redirectToreLaunch
  • 切换底部 Tab:switchTab
  • 清空流程栈回首页:reLaunch

代码示例

js 复制代码
const Router = {
  toDetail(id) {
    wx.navigateTo({ url: `/pages/detail/index?id=${encodeURIComponent(id)}` });
  },

  toHome() {
    wx.switchTab({ url: "/pages/home/index" });
  },

  resetToLogin() {
    wx.reLaunch({ url: "/pages/login/index" });
  },

  back(delta = 1) {
    wx.navigateBack({ delta });
  }
};

module.exports = Router;

常见错误

  • navigateTo 打开 Tab 页面。
  • 页面栈超过限制。
  • URL 参数没有编码。
  • 复杂对象直接拼到 URL。

最佳实践

  • 路由方法集中封装。
  • 参数尽量使用短 ID,复杂数据走缓存或全局状态。
  • 登录、下单等流程结束后合理清栈。

关联知识点

关联登录、订单流程、页面生命周期、用户体验。

面试考察方式

高频考察不同路由 API 的区别和页面栈限制。

7. 网络请求封装

是什么

网络请求封装是在 wx.request 外统一处理 baseURL、Header、Token、错误码、Loading、重试和响应格式。

为什么重要

真实项目接口多,如果每个页面单独写请求,会造成重复代码、错误处理不一致和维护困难。

底层原理

wx.request 是宿主环境提供的异步 API。封装层把回调形式转换为 Promise,并在请求前后统一注入配置和处理结果。

使用场景

  • API 调用。
  • 登录鉴权。
  • 统一错误提示。
  • 接口 Mock。

代码示例

js 复制代码
// services/request.js
const config = {
  baseURL: "https://api.example.com"
};

function request(options) {
  const token = wx.getStorageSync("token");

  return new Promise((resolve, reject) => {
    wx.request({
      url: `${config.baseURL}${options.url}`,
      method: options.method || "GET",
      data: options.data || {},
      header: {
        "content-type": "application/json",
        Authorization: token ? `Bearer ${token}` : "",
        ...options.header
      },
      success(res) {
        const { statusCode, data } = res;
        if (statusCode < 200 || statusCode >= 300) {
          reject(new Error(`HTTP ${statusCode}`));
          return;
        }
        if (data && data.code !== 0) {
          reject(new Error(data.message || "业务请求失败"));
          return;
        }
        resolve(data.data);
      },
      fail(err) {
        reject(err);
      }
    });
  });
}

module.exports = request;
js 复制代码
// services/product.js
const request = require("./request");

function getProductList(params) {
  return request({
    url: "/products",
    data: params
  });
}

module.exports = {
  getProductList
};

常见错误

  • 页面里到处写 wx.request
  • 只判断 HTTP 状态,不判断业务状态码。
  • Token 过期没有统一处理。
  • 请求失败后没有空态和重试入口。

最佳实践

  • 统一 Promise 封装。
  • 区分网络错误、HTTP 错误、业务错误。
  • 登录失效统一跳转登录。
  • 高频接口做缓存、合并或防抖。

关联知识点

关联登录鉴权、状态管理、错误监控、安全设计。

面试考察方式

常让候选人手写请求封装,并追问登录失效和重复请求如何处理。

8. 登录授权

是什么

小程序登录通常通过 wx.login 获取临时 code,发送到服务端换取业务登录态。用户信息、手机号等能力需要按平台规则发起授权。

为什么重要

登录是用户体系、订单、支付、消息和权限的基础。错误设计会导致登录态不稳定、隐私违规或安全风险。

底层原理

wx.login 返回的 code 是临时凭证,服务端用 code 调微信接口换取 openidsession_key 等信息,再生成业务 Token 返回给小程序。前端不应直接持有或信任敏感会话信息。

使用场景

  • 静默登录。
  • 用户下单前登录。
  • 获取手机号。
  • 权限校验。

代码示例

js 复制代码
// services/auth.js
const request = require("./request");

function wxLogin() {
  return new Promise((resolve, reject) => {
    wx.login({
      success: res => resolve(res.code),
      fail: reject
    });
  });
}

async function login() {
  const code = await wxLogin();
  const session = await request({
    url: "/auth/wechat-login",
    method: "POST",
    data: { code }
  });
  wx.setStorageSync("token", session.token);
  wx.setStorageSync("userId", session.userId);
  return session;
}

module.exports = {
  login
};

常见错误

  • code 当长期凭证。
  • 前端自行解密和保存敏感信息。
  • 未处理用户拒绝授权。
  • 登录失败没有兜底流程。

最佳实践

  • 业务 Token 由服务端签发。
  • Token 设置有效期和刷新机制。
  • 授权动作放在明确业务场景中触发。
  • 隐私协议、授权文案和数据使用范围要合规。

关联知识点

关联安全、支付、用户体系、接口鉴权。

面试考察方式

高频考察 wx.login 流程、openid 与业务用户的关系、Token 存储和过期处理。

9. 本地存储

是什么

小程序提供同步和异步本地存储 API,用于保存轻量数据,如 Token、用户偏好、缓存数据。

为什么重要

合理缓存能提升体验和性能;错误缓存会导致数据不一致和安全风险。

底层原理

本地存储由宿主环境管理,容量有限,不适合存储大量或敏感数据。同步 API 简单但会阻塞当前逻辑,异步 API 更适合较复杂场景。

使用场景

  • 登录 Token。
  • 搜索历史。
  • 用户偏好。
  • 字典缓存。

代码示例

js 复制代码
const Storage = {
  set(key, value) {
    wx.setStorageSync(key, {
      value,
      savedAt: Date.now()
    });
  },

  get(key, maxAge) {
    const record = wx.getStorageSync(key);
    if (!record) return null;
    if (maxAge && Date.now() - record.savedAt > maxAge) {
      wx.removeStorageSync(key);
      return null;
    }
    return record.value;
  },

  remove(key) {
    wx.removeStorageSync(key);
  }
};

module.exports = Storage;

常见错误

  • 缓存永不过期。
  • 把身份证、银行卡等敏感信息放本地。
  • 缓存结构没有版本号,升级后解析失败。

最佳实践

  • 设置缓存过期时间。
  • 敏感信息只放服务端。
  • 缓存加版本字段,升级时清理。
  • 优先缓存可恢复、可重新拉取的数据。

关联知识点

关联登录态、性能优化、安全合规、离线体验。

面试考察方式

常问本地存储适合放什么、不适合放什么,以及缓存失效策略。

代码示例体系

示例名称:商品列表到详情

场景说明

首页展示商品列表,点击商品跳转详情页。覆盖 WXML、事件、路由、setData

错误写法

js 复制代码
goDetail(event) {
  wx.navigateTo({
    url: "/pages/detail/index?product=" + JSON.stringify(event.currentTarget.dataset.product)
  });
}

问题:URL 传复杂对象容易超长、编码错误,也不利于详情页刷新。

正确写法

js 复制代码
goDetail(event) {
  const { id } = event.currentTarget.dataset;
  wx.navigateTo({
    url: `/pages/detail/index?id=${encodeURIComponent(id)}`
  });
}

详情页:

js 复制代码
Page({
  data: {
    product: null
  },

  onLoad(query) {
    this.loadProduct(query.id);
  },

  async loadProduct(id) {
    const product = await Promise.resolve({
      id,
      name: "小程序实战课程",
      price: 199
    });
    this.setData({ product });
  }
});

代码解释

列表页只传业务 ID,详情页根据 ID 获取最新数据。这样刷新详情页、分享详情页、处理数据更新都更可靠。

实际项目应用

商品详情、订单详情、文章详情、活动详情都推荐这种模式。

可扩展方向

  • 加入缓存减少重复请求。
  • 加入骨架屏提升体验。
  • 加入错误态和重试按钮。

示例名称:搜索输入防抖

场景说明

用户输入关键词时,不应每输入一个字符就请求接口。

错误写法

js 复制代码
onInput(event) {
  this.search(event.detail.value);
}

正确写法

js 复制代码
Page({
  data: {
    keyword: "",
    results: []
  },

  onInput(event) {
    const keyword = event.detail.value.trim();
    this.setData({ keyword });

    if (this.searchTimer) {
      clearTimeout(this.searchTimer);
    }

    this.searchTimer = setTimeout(() => {
      this.search(keyword);
    }, 300);
  },

  async search(keyword) {
    if (!keyword) {
      this.setData({ results: [] });
      return;
    }
    const results = await Promise.resolve([{ id: 1, title: `搜索结果:${keyword}` }]);
    this.setData({ results });
  },

  onUnload() {
    if (this.searchTimer) {
      clearTimeout(this.searchTimer);
    }
  }
});

代码解释

防抖减少请求次数,降低服务端压力,也避免快速输入导致结果闪烁。

实际项目应用

商品搜索、地址搜索、人员搜索、订单筛选。

可扩展方向

  • 加入请求取消或请求序号,避免旧结果覆盖新结果。
  • 加入搜索历史缓存。

本章节面试题

题目:为什么直接修改 this.data 页面不会更新?

  • 难度:初级
  • 高频:是
  • 考察点:数据绑定、双线程、setData
  • 标准答案:this.data 是逻辑层数据,直接修改只改变 JS 内存,不会把变化同步到视图层。必须调用 setData,基础库才会把变更序列化并通过桥传给视图层,触发模板更新。
  • 深度扩展:setData 有通信成本,过大或过频繁会影响性能,应局部更新、合并更新。
  • 常见错误回答:说"小程序不支持直接赋值",但说不出原因。
  • 面试官追问:长列表中如何减少 setData 成本?
  • 项目应用场景:列表筛选、购物车数量变化、表单状态更新。
  • 对应知识点:Page、setData、双线程模型。

题目:bindtapcatchtap 有什么区别?

  • 难度:入门
  • 高频:是
  • 考察点:事件冒泡。
  • 标准答案:bindtap 绑定事件且允许冒泡,catchtap 绑定事件并阻止冒泡。弹窗遮罩常用 catchtap 避免内部点击触发外层关闭。
  • 深度扩展:可以结合 targetcurrentTarget 说明事件触发节点与绑定节点差异。
  • 常见错误回答:认为二者只是写法不同。
  • 面试官追问:为什么点击子元素时 target.dataset 可能为空?
  • 项目应用场景:弹窗、列表项按钮、卡片点击。
  • 对应知识点:事件系统。

题目:小程序组件如何与父页面通信?

  • 难度:初级
  • 高频:是
  • 考察点:组件设计、单向数据流。
  • 标准答案:父页面通过 properties 向组件传数据,组件通过 triggerEvent 触发自定义事件通知父页面。复杂场景可以结合全局状态或事件总线,但通用组件应避免直接依赖父页面。
  • 深度扩展:通用组件和业务组件边界不同,通用组件应更强调输入输出清晰。
  • 常见错误回答:组件直接调用父页面方法或修改父页面数据。
  • 面试官追问:什么时候该用 slot?
  • 项目应用场景:商品卡片、订单卡片、表单项组件。
  • 对应知识点:Component、组件通信。

题目:navigateToredirectToswitchTabreLaunch 的区别?

  • 难度:初级
  • 高频:是
  • 考察点:页面栈和路由 API。
  • 标准答案:navigateTo 保留当前页面并打开新页面;redirectTo 替换当前页面;switchTab 切换 Tab 页面并关闭其他非 Tab 页面;reLaunch 关闭所有页面后打开指定页面。
  • 深度扩展:navigateTo 不能打开 Tab 页面,页面栈有限制。
  • 常见错误回答:只说都是跳转页面。
  • 面试官追问:登录成功后应该用哪种跳转方式?
  • 项目应用场景:登录、下单、Tab 切换、详情页。
  • 对应知识点:路由与页面栈。

题目:小程序登录流程如何设计?

  • 难度:中级
  • 高频:是
  • 考察点:wx.login、服务端换取身份、Token。
  • 标准答案:前端调用 wx.login 获取临时 code,把 code 发给服务端,服务端调用微信接口换取 openid/session_key,绑定或创建业务用户,再签发业务 Token 给前端。后续请求携带 Token,Token 过期后重新登录或刷新。
  • 深度扩展:手机号授权、隐私合规、Token 过期、登录态安全都要考虑。
  • 常见错误回答:前端拿到 code 后直接当登录态使用。
  • 面试官追问:为什么支付和订单接口不能只信前端用户 ID?
  • 项目应用场景:用户中心、订单、支付、会员体系。
  • 对应知识点:登录授权、接口鉴权、安全。

基础核心知识点库

文档定位

本节把基础与核心能力拆成更细的工程知识点,便于系统查阅、团队培训和面试复习。

扩展知识图谱

基础核心扩展
项目入口
app.js
app.json
app.wxss
sitemap
project.config
页面系统
页面注册
页面栈
生命周期
页面参数
分享能力
下拉刷新
模板样式
WXML指令
WXSS适配
rpx
样式隔离
选择器限制
数据交互
data
setData
事件对象
dataset
表单
防抖节流
组件系统
properties
observers
lifetimes
behaviors
slot
externalClasses
基础API
请求
存储
路由
登录
授权
设备信息

1. App 实例

是什么

App() 注册小程序全局实例,用于处理小程序启动、显示、隐藏、错误监听和全局数据初始化。

为什么重要

它是小程序运行的入口,适合初始化日志、云能力、远程配置、登录态恢复和全局异常处理。

底层原理

基础库启动后会执行 app.js,创建全局 App 实例。页面实例可以通过 getApp() 获取全局实例,但不应滥用全局状态。

使用场景

  • 初始化云开发。
  • 恢复本地 Token。
  • 读取系统信息。
  • 注册全局错误上报。

代码示例

js 复制代码
App({
  globalData: {
    userInfo: null,
    systemInfo: null
  },

  onLaunch() {
    this.globalData.systemInfo = wx.getSystemInfoSync();
    this.initLogger();
  },

  onShow(options) {
    console.log("小程序进入前台", options.scene);
  },

  onHide() {
    console.log("小程序进入后台");
  },

  onError(error) {
    console.error("全局错误", error);
  },

  initLogger() {
    console.log("日志系统初始化");
  }
});

常见错误

  • 把所有业务数据都放到 globalData
  • onLaunch 做大量同步任务,拖慢启动。
  • 全局错误只打印不采集。

最佳实践

  • App 只做全局初始化。
  • 业务状态放独立 store。
  • 启动阶段只做必要任务。
  • 全局错误带页面、版本、基础库、机型信息上报。

面试追问

  • onLaunch 和首页 onLoad 谁先执行?
  • 小程序从后台切回来会不会重新触发 onLaunch
  • 为什么不建议滥用 getApp().globalData

2. 全局配置 app.json

是什么

app.json 是小程序全局配置,定义页面路径、窗口样式、TabBar、分包、权限、网络超时和页面预加载等。

原理

基础库在启动时读取配置,决定页面注册表、首屏入口、路由能力和宿主权限提示。

关键配置清单

配置项 作用 工程建议
pages 注册主包页面 首页放第一项
window 全局导航栏和页面样式 保持统一视觉
tabBar 底部导航 只放高频入口
subpackages 分包 低频重业务拆分
preloadRule 分包预加载 只对高概率路径使用
permission 权限文案 必须说明真实用途
requiredPrivateInfos 隐私能力声明 定位等能力需声明
networkTimeout 网络超时 根据业务设置合理值

代码示例

json 复制代码
{
  "pages": ["pages/home/index", "pages/cart/index", "pages/profile/index"],
  "window": {
    "navigationBarTitleText": "本地生活",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "color": "#6b7280",
    "selectedColor": "#0f766e",
    "list": [
      { "pagePath": "pages/home/index", "text": "首页" },
      { "pagePath": "pages/cart/index", "text": "购物车" },
      { "pagePath": "pages/profile/index", "text": "我的" }
    ]
  },
  "networkTimeout": {
    "request": 10000,
    "uploadFile": 20000
  }
}

常见错误

  • 忘记注册页面。
  • Tab 页面使用 navigateTo 打开。
  • 权限文案笼统,审核风险高。

面试追问

  • 首页路径由什么决定?
  • 主包和分包页面都要写在 pages 里吗?
  • tabBar 页面跳转为什么必须用 switchTab

3. 页面生命周期细分

生命周期顺序

生命周期 触发时机 典型用途 易错点
onLoad 页面创建并接收参数 首次请求、初始化参数 不会每次返回都触发
onShow 页面显示 刷新轻量状态 避免重复重型请求
onReady 首次渲染完成 获取节点信息 只触发一次
onHide 页面隐藏 暂停轮询、计时器 页面未销毁
onUnload 页面卸载 清理监听、定时器 返回上一页才常见
onPullDownRefresh 下拉刷新 重拉第一页 记得停止刷新
onReachBottom 触底 分页加载 防重复请求
onShareAppMessage 分享 构建分享参数 分享页要可独立打开

代码示例

js 复制代码
Page({
  data: {
    page: 1,
    list: [],
    loading: false
  },

  onLoad(query) {
    this.categoryId = query.categoryId || "";
    this.loadList(true);
  },

  onShow() {
    this.refreshBadge();
  },

  onPullDownRefresh() {
    this.loadList(true).finally(() => wx.stopPullDownRefresh());
  },

  onReachBottom() {
    this.loadList(false);
  },

  onUnload() {
    if (this.timer) clearInterval(this.timer);
  },

  async loadList(reset) {
    if (this.data.loading) return;
    this.setData({ loading: true });
    try {
      const page = reset ? 1 : this.data.page;
      const list = await Promise.resolve([]);
      this.setData({
        list: reset ? list : this.data.list.concat(list),
        page: page + 1
      });
    } finally {
      this.setData({ loading: false });
    }
  },

  refreshBadge() {}
});

面试追问

  • onLoadonShow 分别适合做什么?
  • 返回上一页时,上一页会触发哪些生命周期?
  • 下拉刷新结束为什么要调用 wx.stopPullDownRefresh

4. WXML 指令扩展

核心指令

指令 作用 场景 注意点
wx:if 条件创建节点 登录态、空态 切换成本较高
hidden 控制显示隐藏 频繁切换 节点仍存在
wx:for 列表渲染 商品、订单 必须设置 key
wx:key 列表稳定标识 长列表 用业务唯一 ID
template 模板复用 简单结构复用 复杂逻辑用组件
block 包裹逻辑节点 条件和循环 不生成真实节点

错误与正确对比

错误写法:

xml 复制代码
<view wx:for="{{orders}}">
  {{item.title}}
</view>

正确写法:

xml 复制代码
<block wx:for="{{orders}}" wx:key="id">
  <view class="order">{{item.title}}</view>
</block>

实践建议

  • 复杂复用优先组件,不要滥用 template。
  • 长列表必须有稳定 key。
  • 不要在 WXML 中写复杂表达式。
  • 条件多时在 JS 中先计算展示字段。

5. WXSS 与移动端适配

核心知识

  • rpx 按屏幕宽度自适应,750rpx 等于屏幕宽度。
  • 页面样式默认只作用当前页面。
  • 组件样式默认隔离,可通过配置调整。
  • 小程序选择器能力比浏览器 CSS 有限制。

常见尺寸建议

类型 推荐值
页面左右边距 24rpx32rpx
卡片圆角 8rpx16rpx
主按钮高度 88rpx
标题字号 32rpx40rpx
正文字号 26rpx30rpx

代码示例

css 复制代码
.page {
  min-height: 100vh;
  padding: 24rpx;
  background: #f6f7f9;
  box-sizing: border-box;
}

.button {
  height: 88rpx;
  line-height: 88rpx;
  border-radius: 8rpx;
  background: #0f766e;
  color: #fff;
  text-align: center;
  font-size: 30rpx;
}

面试追问

  • rpxpx 有什么区别?
  • 为什么图片要设置固定宽高?
  • 组件样式如何避免污染?

6. setData 扩展实践

优化等级

等级 做法 适用场景
基础 使用 setData 更新视图 普通交互
进阶 合并多次更新 表单、筛选
中级 路径局部更新 列表项状态
高级 降低更新频率 滚动、输入
专家 减少跨桥数据 长列表、复杂页面

正确实践

js 复制代码
this.setData({
  loading: false,
  empty: list.length === 0,
  list
});

路径更新:

js 复制代码
this.setData({
  [`cartItems[${index}].count`]: count
});

反例

js 复制代码
this.setData({
  response: veryLargeApiResponse
});

问题:把接口全量响应放进视图层,包含大量无需渲染的数据。

面试追问

  • setData 一次最大能传多少?
  • 高频输入时如何避免频繁 setData
  • 为什么长列表更新一个字段不建议传整个列表?

7. 事件对象扩展

事件字段

字段 含义 常见用途
type 事件类型 调试
timeStamp 触发时间 性能分析
target 触发源节点 少用
currentTarget 当前绑定节点 推荐取 dataset
detail 组件或表单数据 input、form
touches 触摸点 手势

正确取值

js 复制代码
handleTap(event) {
  const id = event.currentTarget.dataset.id;
  console.log(id);
}

反例

js 复制代码
const id = event.target.dataset.id;

问题:点击子元素时,target 可能不是绑定 data-id 的节点。

8. 表单与校验

是什么

表单能力包括输入、选择、提交、校验和错误提示。

工程建议

  • 输入项使用受控数据。
  • 提交前做前端基础校验。
  • 服务端做最终校验。
  • 错误提示聚焦到具体字段。

示例

js 复制代码
function validatePhone(phone) {
  return /^1\d{10}$/.test(phone);
}

Page({
  data: {
    form: {
      name: "",
      phone: ""
    }
  },

  onNameInput(event) {
    this.setData({ "form.name": event.detail.value.trim() });
  },

  onPhoneInput(event) {
    this.setData({ "form.phone": event.detail.value.trim() });
  },

  submit() {
    const { name, phone } = this.data.form;
    if (!name) {
      wx.showToast({ title: "请输入姓名", icon: "none" });
      return;
    }
    if (!validatePhone(phone)) {
      wx.showToast({ title: "手机号格式不正确", icon: "none" });
      return;
    }
    console.log("提交", this.data.form);
  }
});

9. 基础 API 速查扩展

API 场景 注意点
wx.request 接口请求 统一封装
wx.uploadFile 文件上传 服务端校验
wx.downloadFile 文件下载 临时文件需保存
wx.setStorage 异步存储 适合非阻塞
wx.setStorageSync 同步存储 少量数据
wx.getSystemInfo 系统信息 新版本建议用拆分 API
wx.showToast 轻提示 不适合长错误
wx.showModal 确认弹窗 避免频繁打扰
wx.openSetting 打开设置 用户触发后使用

10. 本卷面试题扩展

题目:onLoadonShowonReady 的区别是什么?

  • 难度:初级
  • 高频:是
  • 考察点:页面生命周期。
  • 标准答案:onLoad 页面创建时触发,适合接收参数和首次初始化;onShow 页面每次显示触发,适合刷新轻量状态;onReady 首次渲染完成触发,适合获取节点信息。
  • 深度扩展:返回上一页时通常触发上一页 onShow,不会重新触发 onLoad
  • 常见错误回答:三个都可以请求接口。
  • 面试官追问:订单支付后返回订单页,应该在哪个生命周期刷新状态?
  • 项目应用场景:详情页、支付页、Tab 页。
  • 对应知识点:页面生命周期。

题目:为什么 wx:for 建议使用稳定 wx:key

  • 难度:初级
  • 高频:是
  • 考察点:列表渲染。
  • 标准答案:稳定 key 能帮助框架识别列表项身份,减少错误复用和不必要更新。业务唯一 ID 比 index 更稳定。
  • 深度扩展:列表插入、删除、排序时用 index 容易导致状态错乱。
  • 常见错误回答:只是为了不报警告。
  • 面试官追问:什么时候可以用 index?
  • 项目应用场景:商品列表、订单列表、评论列表。
  • 对应知识点:WXML、列表渲染。

题目:小程序中如何做输入框搜索优化?

  • 难度:中级
  • 高频:是
  • 考察点:事件、防抖、请求控制。
  • 标准答案:输入值更新和请求触发分离,输入事件中做防抖,接口请求带关键词和分页,处理旧请求晚返回覆盖新结果的问题。
  • 深度扩展:可使用请求序号或取消机制避免乱序。
  • 常见错误回答:每次 input 都请求接口。
  • 面试官追问:如果搜索接口返回顺序错乱怎么办?
  • 项目应用场景:商品搜索、地址搜索、订单搜索。
  • 对应知识点:事件系统、请求封装、性能优化。
相关推荐
無名路人3 小时前
小程序点餐页吸顶滚动
前端·微信小程序·ai编程
游戏开发爱好者84 小时前
使用Fiddler设置HTTPS抓包诊断Power Query网络问题
android·ios·小程序·https·uni-app·iphone·webview
七月的冰红茶5 小时前
【开发工具】使用cursor实现点单小程序
小程序
Greg_Zhong5 小时前
微信小程序中使用canvas实现雷达图及标签对角显示(实现雷达图标签的标准做法)
微信小程序·小程序canvas实现雷达图·标签不通过canvas绘制
码农客栈6 小时前
小程序学习(十八)之“骨架屏”
小程序
棋宣7 小时前
uni-app编译到微信小程序中,父传子props首次传递数据不接收的bug
微信小程序·uni-app·bug
kyh10033811209 小时前
微信小程序摇骰子功能实现|含源码
微信小程序·小程序·摇骰子小游戏·摇色子源码
程序鉴定师1 天前
西安App开发推荐与业界认可的优秀实践
大数据·小程序
纤纡.1 天前
HarmonyOS 鸿蒙 ArkTS 实战:从零开发生肖集卡抽奖小程序
华为·小程序·harmonyos·deveco studio