前言
大家好,这里是《前端毕业班》,前端开发者的自救互助小组。在 AI 与不确定性并存的时代,我们一起看清焦虑,聊技术、聊趋势,也聊前端还能走多远,走去哪。
背景
onShareAppMessage 是微信小程序中用于定义用户点击右上角分享时的行为的一个生命周期函数,它允许开发者自定义分享内容、路径和图片等信息。但是 vue 并没有提供一个专门的 onShareAppMessage 钩子函数来处理这个生命周期事件,开发者依然能够在 vue 组件中使用 onShareAppMessage 来定义分享行为,本篇文章会分析 uni-app 的编译时和运行时到底做了什么工作。
编译
你的页面写了如下代码
html
<template>
<view>测试</view>
</template>
<script setup>
import { onShareAppMessage } from '@dcloudio/uni-app'
onShareAppMessage(() => {
return {
}
})
</script>
会被编译为
js
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "index",
setup(__props) {
common_vendor.onShareAppMessage(() => {
return {};
});
return (_ctx, _cache) => {
return {};
};
}
};
_sfc_main.__runtimeHooks = 2;
wx.createPage(_sfc_main);
页面产物调用 wx.createPage(_sfc_main) 来创建小程序页面实例。_sfc_main 上的 __runtimeHooks 是编译器生成的一个标记,表示这个组件使用了运行时钩子(如 onShareAppMessage)。在运行时,uni-app 会检查这个标记,并根据需要注册相应的生命周期函数,以确保 onShareAppMessage 能够正确地被调用。
注册
js
wx.createPage = createPage
createPage(vuePageOptions) -> Component(parsePage(vuePageOptions, parseOptions))
parsePage 里拿到小程序页面的 methods 后,会注册页面钩子:
js
initHooks(methods, PAGE_INIT_HOOKS)
initUnknownHooks(methods, vueOptions)
initRuntimeHooks(methods, vueOptions.__runtimeHooks)
其中 onShareAppMessage 不在默认注册的 hook 列表中,需要开发者手动注册。所以它主要靠下面几种方式补注册:
initUnknownHooks:Options API 顶层直接写了onShareAppMessage。initRuntimeHooks:编译器生成了vueOptions.__runtimeHooks。
initRuntimeHooks
运行时钩子用 bitmask 标识:
js
const MINI_PROGRAM_PAGE_RUNTIME_HOOKS = {
onPageScroll: 1,
onShareAppMessage: 1 << 1,
onShareTimeline: 1 << 2,
// ...
}
所以 onShareAppMessage 的值是 2。
initRuntimeHooks 会检查页面的 __runtimeHooks 是否包含这个 bit:
js
function initRuntimeHooks(mpOptions, runtimeHooks) {
if (!runtimeHooks) return
Object.keys(MINI_PROGRAM_PAGE_RUNTIME_HOOKS).forEach((hook) => {
if (runtimeHooks & MINI_PROGRAM_PAGE_RUNTIME_HOOKS[hook]) {
initHook(mpOptions, hook, [])
}
})
}
如果 runtimeHooks & 2 成立,就会执行:
js
initHook(methods, 'onShareAppMessage', [])
initHook
initHook 只做一件事:给小程序 options/methods 补一个代理函数。
js
function initHook(mpOptions, hook, excludes) {
if (excludes.indexOf(hook) === -1 && !hasOwn(mpOptions, hook)) {
mpOptions[hook] = function(args) {
return this.$vm && this.$vm.$callHook(hook, args)
}
}
}
对于 onShareAppMessage,微信实际调用的是这个代理函数。
callHook
callHook 的逻辑其实很简单,主要是调用用户注册的 hook 数组:
js
function callHook(name, args) {
const hooks = this.$[name]
return hooks && invokeArrayFns(hooks, args)
}
这里的 this.$ 是 Vue internal instance,this.$['onShareAppMessage'] 是用户注册的 hook 数组。
用户写的 hook 会通过 injectHook 收集进去:
js
instance['onShareAppMessage'] = [wrappedHook1, wrappedHook2]
invokeArrayFns
uni-app 这里使用的 invokeArrayFns 会返回最后一个 hook 的返回值:
js
const invokeArrayFns = (fns, arg) => {
let ret
for (let i = 0; i < fns.length; i++) {
ret = fns[i](arg)
}
return ret
}
这对分享钩子很关键,因为微信要求 onShareAppMessage 返回分享配置:
js
return {
title: '标题',
path: '/pages/index/index'
}
uni-app 通过:
js
return this.$vm.$callHook('onShareAppMessage', args)
把用户 hook 的返回值原样传回微信
总结
下面是整个运行时的最短调用链
text
wx.createPage(_sfc_main)
-> createPage
-> parsePage
-> initRuntimeHooks(methods, vueOptions.__runtimeHooks)
-> initHook(methods, 'onShareAppMessage', [])
-> 微信调用 methods.onShareAppMessage(args)
-> this.$vm.$callHook('onShareAppMessage', args)
-> callHook
-> invokeArrayFns(this.$['onShareAppMessage'], args)
-> 返回分享配置给微信