跳至主要內容

微信小程序

俞文健大约 8 分钟

小程序配置

项目配置

详见 设置 / 项目配置文件open in new window

{
  "projectname": "mini-app",            // 项目名称
  "miniprogramRoot": "miniprogram/",    // 小程序源码目录
  "srcMiniprogramRoot": "miniprogram/", // 对应目录下的右键菜单快捷新建页面和组件文件
  "compileType": "miniprogram",         // 编译类型
  "libVersion": "3.4.10",               // 基础库版本
  "setting": {
    "coverView": true,                 // 使用工具渲染 CoverView
    "es6": true,                       // 将 ES6 编译为 ES5
    "postcss": true,                   // 上传代码时样式自动补全
    "minified": true,                  // 自动压缩脚本文件
    "enhance": true,                   // 开启增强编译
    "showShadowRootInWxmlPanel": true, // 开启调试器 WXML 面板展示 shadow-root
    "packNpmManually": true,           // 手动配置构建 npm 的路径
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",     // package.json 的路径
        "miniprogramNpmDistDir": "./miniprogram" // miniprogram_npm 的路径
      }
    ],
    "useCompilerPlugins": [ // 插件配置,仅支持 typescript less sass
      "sass"
    ],
    "babelSetting": {
      "ignore": [],         // 跳过 Babel 编译的文件或目录
      "outputPath": "",     // Babel 辅助函数的输出目录,默认为 @babel/runtime
      "disablePlugins": []
    }
  },
  "condition": {},
  "editorSetting": {
    "tabIndent": "insertSpaces",
    "tabSize": 2
  },
  "packOptions": {
    "ignore": [],
    "include": []
  }
}

全局配置

详见 小程序配置 / 全局配置open in new window

{
  /* 页面路径 */
  "pages": [
    "pages/index/index",
    "pages/about/about"
  ],
  /* 全局页面窗口 */
  "window": {
    "navigationBarTitleText": "Mini App",       // 导航栏标题
    "navigationBarTextStyle": "white",          // 导航栏标题颜色 [black | white]
    "navigationBarBackgroundColor": "#ff0000",  // 导航栏背景颜色
    "navigationStyle": "default",               // 导航栏样式 [default | custom]
    
    "enablePullDownRefresh": true,              // 开启下拉刷新
    "backgroundColor": "#ff0000",               // 下拉窗口背景颜色
    "backgroundTextStyle": "dark"               // 下拉 loading 样式 [dark | light]
  },
  /* 底部 Tab */
  "tabBar": {
    "color": "#000",             // 文本颜色
    "selectedColor": "#ff0000",  // 选中时的文本颜色
    "backgroundColor": "#fff",   // 背景颜色
    "list": [                    // Tab 列表,最少 2 个,最多 5 个
      {
        "pagePath": "pages/index/index",         // 页面路径
        "text": "Home",                          // 文本内容
        "iconPath": "static/home.png",           // 图片路径 [81px * 81px, size < 40kb]
        "selectedIconPath": "static/home-s.png"  // 选中时的图片路径
      },
      {
        "pagePath": "pages/about/about",
        "text": "About"
      }
    ]
  }
}

页面配置

详见 小程序配置 / 页面配置open in new window

会覆盖 app.json 中相同的配置项。

组件

image

图片。默认尺寸为 320px * 240px。

  • mode:图片裁剪模式。

    • scaleToFill:(默认)不保持宽高比,图片会被拉伸到刚好填满容器。

    • aspectFit:保持宽高比,完全显示图片。容器可能有空白。

    • aspectFill:保持宽高比,不留空白。图片可能完全覆盖或者超出容器。

    • widthFix:保持宽高比,宽度不变,高度自适应。

    • heightFix:保持宽高比,高度不变,宽度自适应。

  • show-menu-by-longpress:长按打开菜单(发送给朋友、收藏、保存图片)。

  • lazy-load:图片懒加载。

input

输入框

  • type:键盘类型。

    • text:文本键盘。

    • number:数字键盘。

  • password:密码框。

  • bind:input:键盘输入时触发。event.detail = { value, cursor, keyCode }

  • bind:focus:聚焦时触发。

  • bind:blur:失焦时触发。

  • bind:confirm:点击 “完成” 按钮时触发。

swiper

轮播图

  • autoplay:是否自动切换。

  • interval:自动切换时间间隔。

  • circular:是否开启无缝滚动。

  • indicator-dots:是否显示面板指示点。

  • indicator-color:指示点颜色。

  • indicator-active-color:当前指示点颜色。

导航

  • open-type:跳转方式。

    • navigate:跳转到非 tabbar 页面。保留当前页面,可返回上一页。

    • redirect:重定向到非 tabbar 页面。关闭当前页面,不能返回上一页。

    • switchTab:切换到 tabbar 页面。

    • reLaunch:重定向到任意页面。关闭所有页面,不能返回上一页。

    • navigateBack:返回上一页。关闭当前页面。

  • deltanavigateBack 的回退层数。

事件

事件类型

  • tap:触摸(点击)事件。

  • touchstart:触摸开始。

  • touchmove:触摸后移动。

  • touchcancel:触摸被打断(来电提醒,弹窗)。

  • touchend:触摸结束。

事件冒泡

使用 bind 绑定的事件,会触发事件冒泡。如果想阻止事件冒泡,可以使用 catch 来绑定事件。

<view bind:tap="bubbleFn">
  <view catch:tap="touchFn"></view>
</view>

自定义数据

使用 data- 绑定自定义数据。通过 event.target.dataset 获取。

或者使用 mark: 传递自定义数据。通过 event.mark 获取(含冒泡元素的 mark 数据)。

注意

  • event.currentTarget:绑定事件的元素。

  • event.target:触发事件的元素。

生命周期

应用生命周期

App({
  /**
   * 小程序初始化完成时触发(冷启动)
   */
  onLaunch() {
  
  },
  
  /**
   * 小程序启动、或切入前台时触发
   */
  onShow(options) {
  
  },
  
  /**
   * 小程序切入后台时触发
   */
  onHide() {
  
  },
  
  /**
   * 小程序发生脚本错误、或 api 调用失败时触发
   */
  onError(msg) {
  
  }
})

页面生命周期

Page({
  /**
   * 页面加载完成时触发(冷启动)
   */
  onLoad(options) {
  
  },
  
  /**
   * 页面初次渲染完成时触发
   */
  onReady() {
  
  },
  
  /**
   * 页面显示、或切入前台时触发
   */
  onShow() {
  
  },
  
  /**
   * 页面隐藏、或切入后台时触发
   */
  onHide() {
  
  },
  
  /**
   * 页面卸载时触发(重定向跳转)
   */
  onUnload() {
  
  }
})

页面处理函数

Page({
  /**
   * 页面滚动时触发
   */
  onPageScroll() {
  
  },
  
  /**
   * 页面尺寸变化时触发
   */
  onResize() {
  
  },
  
  /**
   * 用于触发下拉刷新
   */
  onPullDownRefresh() {
  
  },
  
  /**
   * 用于触发上拉加载
   */
  onReachBottom() {
  
  },
  
  /**
   * 用户分享时触发
   */
  onShareAppMessage() {
  
  }
})

自定义组件

基本选项

Component({
  // 配置项
  options: {
  
  },
  
  // 属性
  properties: {
  
  },
  
  // 组件的数据
  data: {
  
  },
  
  // 组件的方法
  methods: {
  
  }
})

组件注册

app.json 中注册就是全局组件;在 pages/*.json 中注册就是局部组件。

{
  "usingComponents": {
    "custom-component": "/components/custom-component/custom-component"
  }
}

数据监听器

使用 setData 改变 data 中的数据时,会触发数据监听器。

Component({
  data: {
    time: "2024-07-22",
    page: {
      current: 1,
      size: 10
    }
  },
  observers: {
    // 监听一个数据
    time(value) {
      wx.request({ /* ... */ })
    },
    // 监听多个数据
    "page.current, page.size"(current, size) {
      wx.request({ /* ... */ })
    },
    // 监听所有数据
    "**"() {
      wx.request({ /* ... */ })
    }
  }
})

组件通信

监听事件。

<!-- vue: @update="updateFn" -->
<custom-component bind:update="updateFn" />
Page({
  updateFn(event) {
    event.detail // 触发事件时携带的额外参数
  }
})

触发事件。

Component({
  methods: {
    updateEmit() {
      // vue: emits("update", args)
      this.triggerEvent("update", this.data.args /* datail */, {} /* option */)
    }
  }
})

组件生命周期

组件自身的生命周期。

Component({
  lifetimes: {
    /**
     * 组件实例创建完成时触发
     */
    created() {
    
    },
    /**
     * 组件实例挂载到页面时触发
     */
    attached() {
    
    },
    /**
     * 组件实例卸载时触发
     */
    detached() {
    
    },
    /**
     * 组件在视图层布局完成后触发
     */
    ready() {
    
    },
    /**
     * 组件被移动时触发
     */
    moved() {
    
    }
  }
})

组件所在页面的生命周期。

Component({
  pageLifetimes: {
    /**
     * 组件所在页面显示、或切入前台时触发
     */
    show() {
    
    },
    /**
     * 组件所在页面隐藏、或切入后台时触发
     */
    hide() {
    
    },
    /**
     * 组件所在页面尺寸变化时触发
     */
    resize() {
    
    }
  }
})

Behavious

类似 mixin。

详见 自定义组件 / behaviorsopen in new window

分包

分包介绍

分包可以让小程序容量更大、功能更多。分包在用户使用时按需加载,优化了小程序首次启动的下载时间。

每个使用分包的小程序必须有一个主包,主包中包含默认启动页面、TabBar 页面以及一些公共资源。

小程序启动时,默认会下载主包并启动主包内的页面,当用户进入分包内某个页面时,客户端会按需下载分包。

小程序分包大小由以下限制:

  • 小程序所有分包大小不能超过 20M。

  • 单个分包和主包大小不能超过 2M。

普通分包

将需要分包的文件单独放入一个文件夹,在 app.json 的 subPackages 字段中声明分包结构。

{
  "subPackages": [
    {
      "root": "packages",      // 分包根目录
      "name": "normalPackage", // 分包别名
      "pages": [               // 分包页面路径
        "pages/shop/shop"
      ]
    }
  ]
}

提示

普通分包可以使用主包的公共资源,例如封装的请求方法、公共样式。

独立分包

独立分包不依赖主包和其他分包。我们可以将具有独立功能的页面配置到独立分包中。

配置 subPackages.independent 字段开启独立分包,其他配置与普通分包相同。

{
  "subPackages": [
    {
      "root": "packages",
      "name": "indepPackage",
      "pages": [
        "pages/cart/cart"
      ],
      "independent": true // 开启独立分包
    }
  ]
}

注意

独立分包不能使用主包的任何内容,包括公共样式。

开放能力

用户信息

获取用户头像和昵称。

<button open-type="chooseAvatar" 	bind:chooseavatar="chooseAvatar">
  <image src="{{avatar}}" />
</button>

<form bind:submit="onSubmit">
  <input type="nickname" name="nickname" />
  <button form-type="submit">set username</button>
</form>
Page({
  data: {
    avatar: "/static/default-avatar.jpg",
    username: ""
  },
  
  chooseAvatar(event) {
    this.setData({
      avatar: event.detail.avatarUrl
    })
  },
  
  onSubmit(event) {
    this.setData({
      username: event.detail.value.nickname
    })
  }
})

转发分享

声明 onShareAppMessage 事件,否则转发功能不可用。

  • 点击右上角三点转发。

  • 自定义按钮转发。会触发 onShareAppMessage 事件。

<button open-type="share">Share</button>
Page({
  onShareAppMessage(event) {
    event /*
      通过右上角三点转发 {form: "menu", target: undefined}
      通过自定义按钮转发 {form: "button", target: {...}}
    */
    
    // 自定义分享封面
    return {
      title: "Share the shop!",    // 封面标题
      path: "/pages/shop/shop",    // 分享的页面,默认为当前页
      imageUrl: "/static/shop.png" // 封面图像
    }
  }
})

设置允许转发后,可以声明 onShareTimeline 事件,开启 “分享到朋友圈” 功能。

只能点击右上角三点分享到朋友圈。

Page({
  onShareAppMessage(event) {
    // ...
  },
  
  onShareTimeline(event) {
    event // {from: "menu", target: undefined}
  }
})

手机号验证

event.detail.code 发送给后端,获得用户手机号。

<!-- 手机号快速验证组件 -->
<button open-type="getPhoneNumber" bind:getphonenumber="getPhoneNumber" />

<!-- 手机号实时验证组件 -->
<button open-type="getRealtimePhoneNumber" bind:getrealtimephonenumber="getRealtimePhoneNumber" />

登录流程

通过 wx.login 得到一个临时授权码 code。将授权码发送给服务器获取 token,再携带 token 请求用户信息。

Page({
  login() {
    wx.login({
      success: async ({ code }) => {
        const token = await apiLogin(code)
        wx.setStorageSync("TOKEN", token)
        
        const userInfo = await apiGetUserInfo()
        wx.setStorageSync("USER_INFO", userInfo)
      }
    })
  }
})

支付流程

提交订单,将订单信息发送给服务器,得到订单号。将订单号发送给服务器,得到 支付参数open in new window。然后调用 wx.requestPayment 传入支付参数,发起微信支付 。等待用户支付成功后,跳转到订单页面。

Page({
  async submitOrder() {
    // 收集订单数据:商品、收件人
    // 获取订单号
    const orderCode = await apiSubmitOrder(orderForm)
    // 获取支付参数:时间戳、支付ID、签名
    const payParams = await apiGetPayParams(orderCode)
    // 发起微信支付
    const payInfo = await wx.requestPayment(payParams)
    if (payInfo.errMsg === "requestPayment:ok") {
      // 查询支付状态
      const payStatus = await apiGetPayStatus(orderCode)
      if (payStatus.code === 200) {
        // 跳转到订单页面
        wx.redirectTo({ url: "/pages/order/order" })
      }
    }
  }
})

更新机制

App({
  onLaunch() {
    // 创建 updateManager 实例,用于管理更新
    const updateManager = wx.getUpdateManager()
    
    // 监听是否有新版本,如果有的话会主动触发下载
    updateManager.onUpdateReady(() => {
      wx.showModal({
        title: "更新提示",
        content: "新版本已经准备好,是否重启应用?",
        success: res => {
          if (res.confirm) {
            // 强制小程序重启并使用新版本
            updateManager.applyUpdate()
          }
        }
      })
    })
  }
})