微信小程序 wx.navigateToMiniProgram 使用指南:参数、回传和真机失败原因

小程序之间要打开另一个小程序,用 wx.navigateToMiniProgram。登录授权、订单支付、会员互通这类流程都靠它:当前小程序把用户送过去,对方处理完再带数据回来。

接口从基础库 1.3.0 开始可用,支持 Promise。微信 Windows、Mac、鸿蒙客户端都可以调用。它只负责「打开」,不负责「等对方做完」。对方返回的数据要在自己的 App.onShow 里收。

先看清三条平台限制

调用前先记住这三件事,后面大部分失败都能对上。

必须由用户点击触发。 基础库 2.3.0 起,用户没有点过当前页面,就不能自动跳转。写在 onLoad、onShow、定时器里,真机经常失败,errMsg 里会带 can only be invoked by user TAP gesture。

跳转前一定有系统确认弹窗。 同样从 2.3.0 开始,微信会先问用户是否打开另一个小程序。用户点取消,回调是 fail,errMsg 为 navigateToMiniProgram:fail cancel。这是用户主动取消,按失败重试去弹 Toast 会打扰用户。

2020 年 4 月 24 日起不再需要配置跳转名单,也不再限制跳转数量。 旧文里的 app.json → navigateToMiniProgramAppIdList(最多 10 个)已经失效。使用时仍要遵守《微信小程序平台运营规范》,互推、小程序盒子一类行为会被下架。

另外,envVersion 只在当前小程序是开发版或体验版时生效。当前包是正式版时,打开的目标小程序一定是正式版。

参数

属性类型默认值说明
appIdstring-目标小程序 AppID。传了 shortLink 时可以不传
pathstring首页打开的页面路径。? 后面是 query
extraDataobject-交给目标小程序的数据,在对方 App.onLaunch / App.onShow 里取
envVersionstringreleasedevelop 开发版、trial 体验版、release 正式版
shortLinkstring-小程序链接,基础库 2.18.1。传入后可以不传 appId 和 path
noRelaunchIfPathUnchangedbooleanfalse基础库 2.24.0。目标小程序还活着,且本次 path、query 与上次相同,就回到它退后台时的页面,不重新启动
success / fail / completefunction-success 表示跳转已发起,不表示用户已经从目标小程序返回

path 不要写成带前导 / 的绝对路径。官方示例是 page/index/index?id=123。query 会出现在目标页 Page.onLoad(query),也会出现在 App.onLaunch、App.onShow 的 options.query。

extraData 走的是另一条通道,只在 App.onLaunch、App.onShow 的 options.referrerInfo.extraData 里。页面 onLoad 拿不到它。

最小可用写法

按钮点击里直接调。success 只说明用户已确认、目标小程序已打开。

Page({
  openPartner() {
    wx.navigateToMiniProgram({
      appId: 'wx1234567890abcdef',
      path: 'pages/auth/index?from=order',
      extraData: {
        biz: 'pay',
        orderId: '202610190001',
      },
      envVersion: 'release',
      success() {
        // 已打开目标小程序,业务结果还要等 onShow
      },
      fail(err) {
        const message = err.errMsg || ''
        if (message.indexOf('cancel') !== -1) {
          return
        }
        wx.showToast({ title: '暂时无法打开', icon: 'none' })
      },
    })
  },
})

基础库支持 Promise 风格,不传回调时可以直接 await:

async openPartner() {
  try {
    await wx.navigateToMiniProgram({
      appId: 'wx1234567890abcdef',
      path: 'pages/auth/index',
    })
  } catch (err) {
    const message = (err && err.errMsg) || ''
    if (message.indexOf('cancel') !== -1) return
    wx.showToast({ title: '暂时无法打开', icon: 'none' })
  }
}

Taro 的封装同名,参数一致:

import Taro from '@tarojs/taro'

function openPartner() {
  return Taro.navigateToMiniProgram({
    appId: 'wx1234567890abcdef',
    path: 'pages/auth/index',
    extraData: { biz: 'pay' },
    envVersion: 'release',
  })
}

声明式写法用 navigator,底层同样是这个接口,确认弹窗和点击限制一样存在:

<navigator
  target="miniProgram"
  open-type="navigate"
  app-id="wx1234567890abcdef"
  path="pages/auth/index"
  version="release"
  bindsuccess="onJumpSuccess"
  bindfail="onJumpFail"
>
  打开对方小程序
</navigator>

extra-data 在组件上是对象。复杂对象用 API 更稳妥,组件属性容易被当成字符串。

数据怎么去、怎么回

一次完整跳转有两个方向。

去程。 当前小程序调用 wx.navigateToMiniProgram。目标小程序冷启动时场景值是 1037(小程序打开小程序)。path 上的 query 进页面参数,extraData 进 referrerInfo。

// 目标小程序 app.js
App({
  onShow(options) {
    if (options.scene !== 1037) return
    const fromAppId = options.referrerInfo && options.referrerInfo.appId
    const extraData = (options.referrerInfo && options.referrerInfo.extraData) || {}
    // extraData.orderId、extraData.biz
  },
})

部分基础库在没有来源时 referrerInfo 是 undefined,读取前先判断。

回程。 目标小程序只能在「自己是被别的小程序打开的」前提下调用 wx.navigateBackMiniProgram。调用方回到前台时场景值是 1038(从另一个小程序返回),数据在同一次 App.onShow 的 referrerInfo.extraData。

// 目标小程序,授权完成后
wx.navigateBackMiniProgram({
  extraData: {
    code: 'auth-code-from-server',
  },
})
// 调用方 app.js
App({
  onShow(options) {
    if (options.scene !== 1038) return
    const extraData = options.referrerInfo && options.referrerInfo.extraData
    if (!extraData || !extraData.code) return
    // 用 code 向自己的服务端换登录态,不要把 code 当登录态直接存
  },
})

extraData 只在场景值 1037 和 1038 下有效。来源小程序的 AppID 在 referrerInfo.appId,收到回包后先核对是不是预期的那个小程序。

两条传参通道的差别:

path queryextraData
适合放页面定位:打开哪一页、简单字符串参数业务上下文:订单号、授权范围、一次性票据
谁能读到Page.onLoad、App.onShow 的 query只有 App.onLaunch / App.onShow 的 referrerInfo
形态URL 字符串对象
返回时用不上navigateBackMiniProgram 的 extraData 原路带回

敏感票据放 extraData,同时在服务端把票据和跳转前生成的 state 绑在一起,并设置很短的有效期。只靠前端回传的 code 不能证明这次回调就是刚才那次点击。

回传会被重复触发

success 不是业务完成。用户从目标小程序回来、切后台再切回、甚至进程被回收后用上次启动参数重新打开,App.onShow 都可能再次带上同一次 scene = 1038。

安卓还有一个平台行为:按 Home 回到桌面再进小程序时,系统拿不到新的场景值,会保留上一次的场景值。于是旧的 1038 和旧的 extraData 会再走一遍。

处理方式:

  1. 只在 scene === 1038 时解析回包。
  2. 用 referrerInfo.appId 校验来源。
  3. 对 code 做一次性消费。内存里记已处理的 code,拦截同一次前台里的并发 onShow。
  4. 内存去重挡不住进程重启。跳转前把 state 写入本地存储,回调里读出后立刻删掉。同一个 code 再来时,本地已经没有 state,直接忽略。
  5. 给 state 一个短 TTL(例如 5 分钟)。超时的回包丢掉,提示用户重新发起。
const STORAGE_KEY = 'mp_jump_state'
const processedCodes = new Set()

function saveState(state) {
  wx.setStorageSync(STORAGE_KEY, { state, timestamp: Date.now() })
}

function consumeState() {
  const stored = wx.getStorageSync(STORAGE_KEY)
  wx.removeStorageSync(STORAGE_KEY)
  if (!stored || !stored.state) return ''
  if (Date.now() - stored.timestamp > 5 * 60 * 1000) return ''
  return stored.state
}

App({
  onShow(options) {
    if (options.scene !== 1038) return
    const extra = (options.referrerInfo && options.referrerInfo.extraData) || {}
    if (!extra.code || processedCodes.has(extra.code)) return
    processedCodes.add(extra.code)

    const state = consumeState()
    if (!state) return
    // exchange(extra.code, state)
  },
})

页面级 onShow 适合刷新 UI。登录态、票据兑换放在 App.onShow(或 wx.onAppShow),避免页面还没创建时把回包丢掉。热启动时用 wx.getEnterOptionsSync() 读最近一次进入参数;wx.getLaunchOptionsSync() 一直是冷启动那次,不适合拿来读「刚刚返回」的数据。

开发版、体验版和开发者工具

联调目标小程序的开发版时:

  1. 当前小程序本身也必须是开发版或体验版,envVersion: 'develop' 才会生效。
  2. 扫码的微信号要在目标小程序的开发者名单里,否则开发版打不开。
  3. 开发者工具里调用这个 API 不会真的打开另一个小程序,工具只校验这次调用是否成功。接收参数要在工具的「模拟跳转」里调试,完整回跳以真机为准。

shortLink 从基础库 2.18.1 可用。链接来自目标小程序菜单里的「复制链接」,或服务端 generateShortLink。传入后可以不传 appId 和 path:

wx.navigateToMiniProgram({
  shortLink: '#小程序://示例/页面/xxxxxxxx',
})

noRelaunchIfPathUnchanged 适合重复打开同一个页面、希望保留目标小程序现场的场景。它生效要同时满足:目标小程序进程还在,并且本次 path、query 与上次启动相同。不满足时仍会重新启动。

真机上常见的 fail

现象原因处理
fail cancel用户在系统弹窗点了取消静默结束,不要当成系统错误重试
can only be invoked by user TAP gesture不在点击行为里调用放到按钮 bindtap 里,点击后尽快调用
点击后先 await 一个慢接口再跳转点击上下文已经丢失跳转所需参数在点击前准备好
开发者工具显示成功,真机没打开工具只做校验用真机预览
正式版包里 envVersion: 'develop' 仍打开正式版正式版忽略该参数用开发版或体验版包联调
返回后 onShow 没有 extraData看错了生命周期,或目标小程序没有调用 navigateBackMiniProgram在 App.onShow 看 scene === 1038
同一次授权回调走了两遍1038 被再次带上按 code 去重,并一次性消费 state
插件页里调用失败插件页与宿主小程序不能互相调用该接口在对应一方的页面里调用;插件侧需要基础库 ≥ 2.18.1

多端框架(把小程序编译成 App)不支持 wx.navigateToMiniProgram,对应能力是 wx.miniapp.launchMiniProgram,参数是小程序原始 ID userName,和这里的 appId 不是同一个字段。

朋友圈快照(browseOnly)、视频号原生功能化场景、聊天工具场景里,这个接口会被禁用。半屏打开用 wx.openEmbeddedMiniProgram,限制与全屏跳转不同,不要混用。

一个可落地的登录跳转顺序

  1. 用户点击「使用 xx 账号登录」。
  2. 点击前或点击同步阶段向自己的服务端要一个短期 state,立刻写入本地存储。
  3. 调用 wx.navigateToMiniProgram,extraData 里带上授权范围和业务标识。
  4. 用户在系统弹窗确认后进入目标小程序,对方 onShow 读到场景值 1037。
  5. 对方完成授权,调用 wx.navigateBackMiniProgram,extraData 带回 code。
  6. 自己的 App.onShow 见到场景值 1038,校验 appId,消费 state,用 code + state 向服务端换会话。
  7. 用户取消弹窗时什么都不做。

success 里不要开始轮询登录结果。登录结果只来自 1038 的那一次回包,以及随后的服务端兑换。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐