hooks.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { uniAppHook, Global } from '../helpers/config';
  2. import {
  3. callAppHook, getPageVmOrMp, ruleToUniNavInfo, formatTo, formatFrom, getPages,
  4. } from './util';
  5. import appletsUniPushTo from './appletsNav';
  6. import { noop } from '../helpers/util';
  7. import { warn } from '../helpers/warn';
  8. /**
  9. *
  10. * @param {String} key
  11. * @param {Function} hook 需要执行及还原的生命周期函数
  12. */
  13. const toutiaoIndexHookCall = function (key, hook) {
  14. const { indexVue } = uniAppHook;
  15. const indeHooks = indexVue[key];
  16. indeHooks.splice(indeHooks.length - 1, 1, hook);
  17. };
  18. /**
  19. * 还原并执行所有 拦截下来的生命周期 app.vue 及 index 下的生命周期
  20. * @param {Boolean} callHome // 是否触发首页的生命周期
  21. *
  22. * this 为当前 page 对象
  23. */
  24. const callwaitHooks = function (callHome) {
  25. return new Promise(async (resolve) => {
  26. const variation = []; // 存储一下在uni-app上的变异生命钩子 奇葩的要死
  27. const {
  28. appVue, onLaunch, onShow, waitHooks, variationFuns, indexCallHooks,
  29. } = uniAppHook;
  30. const app = appVue.$options;
  31. await onLaunch.fun[onLaunch.fun.length - 1].call(appVue, onLaunch.args); // 确保只执行最后一个 并且强化异步操作
  32. onShow.fun[onShow.fun.length - 1].call(appVue, onShow.args); // onshow 不保证异步 直接确保执行最后一个
  33. if (callHome) { // 触发首页生命周期
  34. // eslint-disable-next-line
  35. for (const key in waitHooks) {
  36. if (indexCallHooks.includes(key)) { // 只有在被包含的情况下才执行
  37. callAppHook.call(this, waitHooks[key].fun);
  38. }
  39. }
  40. }
  41. if (onLaunch.isHijack) { // 还原 onLaunch生命钩子
  42. app.onLaunch.splice(app.onLaunch.length - 1, 1, onLaunch.fun[0]);
  43. }
  44. if (onShow.isHijack) { // 继续还原 onShow
  45. app.onShow.splice(app.onShow.length - 1, 1, onShow.fun[0]);
  46. }
  47. // eslint-disable-next-line
  48. for (const key in waitHooks) { // 还原 首页下的生命钩子
  49. const item = waitHooks[key];
  50. if (item.isHijack) {
  51. if (variationFuns.includes(key)) { // 变异方法
  52. variation.push({ key, fun: item.fun[0] });
  53. } else {
  54. toutiaoIndexHookCall(key, item.fun[0]);
  55. }
  56. }
  57. }
  58. resolve(variation);
  59. });
  60. };
  61. /**
  62. * 还原剩下的奇葩生命钩子
  63. * @param {Object} variation 当前uni-app中的一些变异方法 奇葩生命钩子
  64. */
  65. const callVariationHooks = function (variation) {
  66. for (let i = 0; i < variation.length; i += 1) {
  67. const { key, fun } = variation[i];
  68. toutiaoIndexHookCall(key, fun);
  69. }
  70. };
  71. /**
  72. * 主要是对app.vue下onLaunch和onShow生命周期进行劫持
  73. *
  74. * this 为当前 page 对象
  75. */
  76. export const proxyLaunchHook = function () {
  77. const {
  78. onLaunch,
  79. onShow,
  80. } = this.$options;
  81. uniAppHook.appVue = this; // 缓存 当前app.vue组件对象
  82. if (onLaunch.length > 1) { // 确保有写 onLaunch 可能有其他混入 那也办法
  83. uniAppHook.onLaunch.isHijack = true;
  84. uniAppHook.onLaunch.fun = onLaunch.splice(onLaunch.length - 1, 1, (arg) => {
  85. uniAppHook.onLaunch.args = arg;
  86. }); // 替换uni-app自带的生命周期
  87. }
  88. if (onShow.length > 0) {
  89. uniAppHook.onShow.isHijack = true;
  90. uniAppHook.onShow.fun = onShow.splice(onShow.length - 1, 1, (arg) => {
  91. uniAppHook.onShow.args = arg;
  92. if (uniAppHook.pageReady) { // 因为还有app切前台后台的操作
  93. callAppHook.call(this, uniAppHook.onShow.fun, arg);
  94. }
  95. }); // 替换替换 都替换
  96. }
  97. };
  98. /**
  99. * 触发全局beforeHooks 生命钩子
  100. * @param {Object} _from // from 参数
  101. * @param {Object} _to // to 参数
  102. *
  103. * this 为当前 Router 对象
  104. */
  105. const beforeHooks = function (_from, _to) {
  106. return new Promise(async (resolve) => {
  107. const beforeHooksFun = this.lifeCycle.beforeHooks[0];
  108. if (beforeHooksFun == null) {
  109. return resolve();
  110. }
  111. await beforeHooksFun.call(this, _to, _from, resolve);
  112. });
  113. };
  114. /**
  115. * 触发全局afterEachHooks 生命钩子
  116. * @param {Object} _from // from 参数
  117. * @param {Object} _to // to 参数
  118. *
  119. * this 为当前 Router 对象
  120. */
  121. const afterEachHooks = function (_from, _to) {
  122. const afterHooks = this.lifeCycle.afterHooks[0];
  123. if (afterHooks != null && afterHooks.constructor === Function) {
  124. afterHooks.call(this, _to, _from);
  125. }
  126. };
  127. /**
  128. * 触发全局 beforeEnter 生命钩子
  129. * @param {Object} finalRoute // 当前格式化后的路由参数
  130. * @param {Object} _from // from 参数
  131. * @param {Object} _to // to 参数
  132. *
  133. * this 为当前 Router 对象
  134. */
  135. const beforeEnterHooks = function (finalRoute, _from, _to) {
  136. return new Promise(async (resolve) => {
  137. const { beforeEnter } = finalRoute.route;
  138. if (beforeEnter == null || beforeEnter.constructor !== Function) { // 当前这个beforeEnter不存在 或者类型错误
  139. return resolve();
  140. }
  141. await beforeEnter.call(this, _to, _from, resolve);
  142. });
  143. };
  144. /**
  145. * v1.5.4+
  146. * beforeRouteLeave 生命周期
  147. * @param {Object} to 将要去的那个页面 to对象
  148. * @param {Object} from 从那个页面触发的 from对象
  149. * @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
  150. * this 为当前 Router 对象
  151. */
  152. const beforeRouteLeaveHooks = function (from, to, leaveHook) {
  153. return new Promise(async (resolve) => {
  154. if (leaveHook) { // 我们知道这个是来自页面beforeRouteLeave next到其他地方,所有不必再执行啦
  155. warn('beforeRouteLeave next到其他地方,无须再执行!');
  156. return resolve();
  157. }
  158. if (from.path == to.path) { // 进入首页的时候不触发
  159. return resolve();
  160. }
  161. const currentPage = getPages(-2); // 获取到全部的页面对象
  162. const callThis = getPageVmOrMp(currentPage); // 获取到页面的 $vm 对象 及 page页面的this对象
  163. const { beforeRouteLeave } = callThis.$options; // 查看当前是否有开发者声明
  164. if (beforeRouteLeave == null) {
  165. warn('当前页面下无 beforeRouteLeave 钩子声明,无须执行!');
  166. return resolve();
  167. }
  168. if (beforeRouteLeave != null && beforeRouteLeave.constructor !== Function) {
  169. warn('beforeRouteLeave 生命钩子声明错误,必须是一个函数!');
  170. return resolve();
  171. }
  172. await beforeRouteLeave.call(callThis, to, from, resolve); // 执行生命钩子
  173. });
  174. };
  175. /**
  176. * 核心方法 处理一系列的跳转配置
  177. * @param {Object} rule 当前跳转规则
  178. * @param {Object} fnType 跳转页面的类型方法
  179. * @param {Object} navCB:? 回调函数
  180. * @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
  181. * this 为当前 Router 对象
  182. *
  183. */
  184. export const appletsTransitionTo = async function (rule, fnType, navCB, leaveHook = false) {
  185. await this.lifeCycle.routerbeforeHooks[0].call(this); // 触发内部跳转前的生命周期
  186. const finalRoute = ruleToUniNavInfo(rule, this.CONFIG.routes); // 获得到最终的 route 对象
  187. const _from = formatFrom(this.CONFIG.routes); // 先根据跳转类型获取 from 数据
  188. const _to = formatTo(finalRoute); // 再根据跳转类型获取 to 数据
  189. try {
  190. const leaveResult = await beforeRouteLeaveHooks.call(this, _from, _to, leaveHook); // 执行页面中的 beforeRouteLeave 生命周期 v1.5.4+
  191. // eslint-disable-next-line
  192. await isNext.call(this, leaveResult, fnType, navCB,true); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
  193. const beforeResult = await beforeHooks.call(this, _from, _to); // 执行 beforeEach 生命周期
  194. // eslint-disable-next-line
  195. await isNext.call(this, beforeResult, fnType, navCB); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
  196. const enterResult = await beforeEnterHooks.call(this, finalRoute, _from, _to); // 接着执行 beforeEnter 生命周期
  197. // eslint-disable-next-line
  198. await isNext.call(this, enterResult, fnType, navCB); // 再次验证 如果生命钩子多的话应该写成递归或者循环
  199. } catch (e) {
  200. warn(e); // 打印开发者操作的日志
  201. return false;
  202. }
  203. if (navCB) {
  204. navCB.call(this, finalRoute, fnType); // 执行当前回调生命周期
  205. }
  206. afterEachHooks.call(this, _from, _to);
  207. await this.lifeCycle.routerAfterHooks[0].call(this); // 触发内部跳转前的生命周期
  208. };
  209. /**
  210. * 触发全局 返回事件
  211. * @param {Number} backLayer 需要返回的页面层级
  212. * @param {Function} next 正真的回调函数
  213. *
  214. * this 为当前 Router 对象
  215. */
  216. export const backCallHook = function (backLayer, next) {
  217. const pages = getPages(); // 获取到全部的页面对象
  218. const toPage = pages.reverse()[backLayer];
  219. if (toPage == null) { // 没有匹配到的时候
  220. return warn('亲爱的开发者,你确定页面栈中有这么多历史记录给你返回?');
  221. }
  222. const { query, page } = getPageVmOrMp(toPage, false);
  223. const beforeFntype = 'RouterBack';
  224. appletsTransitionTo.call(this, { path: page.route, query }, beforeFntype, (finalRoute, fnType) => {
  225. const toPath = finalRoute.uniRoute.url;
  226. if (`/${page.route}` == toPath || page.route == toPath) { // 直接调用返回api
  227. next();
  228. } else { // 有拦截到其他页面时
  229. if (fnType == beforeFntype) {
  230. return warn('调用返回api被拦截到其他页面需要指定合理的 ‘NAVTYPE’ ');
  231. }
  232. appletsUniPushTo(finalRoute, fnType);
  233. }
  234. });
  235. };
  236. /**
  237. * 主动触发导航守卫
  238. * @param {Object} Router 当前路由对象
  239. *
  240. */
  241. export const triggerLifeCycle = function (Router) {
  242. const topPage = getCurrentPages()[0];
  243. if (topPage == null) {
  244. return warn('打扰了,当前一个页面也没有 这不是官方的bug是什么??');
  245. }
  246. const { query, page } = getPageVmOrMp(topPage, false);
  247. appletsTransitionTo.call(Router, { path: page.route, query }, 'push', async (finalRoute, fnType) => {
  248. let variation = [];
  249. if (`/${page.route}` == finalRoute.route.path || page.route == finalRoute.route.path) { // 在首页不动的情况下
  250. uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
  251. await callwaitHooks.call(this, true);
  252. } else { // 需要跳转
  253. variation = await callwaitHooks.call(this, false); // 只触发app.vue中的生命周期
  254. await appletsUniPushTo(finalRoute, fnType);
  255. }
  256. uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
  257. callVariationHooks(variation);
  258. });
  259. };
  260. /**
  261. * 把指定页面的生命钩子函数保存并替换
  262. * this 为当前 page 对象
  263. */
  264. export const appletsProxyIndexHook = function (Router) {
  265. if (process.env.VUE_APP_PLATFORM == 'mp-toutiao') { // 头条小程序首页生命周期由我们手动触发,缓存this
  266. uniAppHook.toutiaoIndexThis = this;
  267. }
  268. const { needHooks, waitHooks } = uniAppHook;
  269. const options = this.$options;
  270. uniAppHook.indexVue = options;
  271. for (let i = 0; i < needHooks.length; i += 1) {
  272. const key = needHooks[i];
  273. if (options[key] != null) { // 只劫持开发者声明的生命周期
  274. const { length } = options[key];
  275. // eslint-disable-next-line
  276. const whObject= waitHooks[key]={};
  277. whObject.fun = options[key].splice(length - 1, 1, noop); // 把实际的页面生命钩子函数缓存起来,替换原有的生命钩子
  278. whObject.isHijack = true;
  279. }
  280. }
  281. triggerLifeCycle.call(this, Router); // 接着 主动我们触发导航守卫
  282. };
  283. /**
  284. * 验证当前 next() 管道函数是否支持下一步
  285. *
  286. * @param {Object} Intercept 拦截到的新路由规则
  287. * @param {Object} fnType 跳转页面的类型方法 原始的
  288. * @param {Object} navCB 回调函数 原始的
  289. * @param {Boolean} leaveHookCall:? 是否为 beforeRouteLeave 触发的next 做拦截判断
  290. * this 为当前 Router 对象
  291. *
  292. */
  293. const isNext = function (Intercept, fnType, navCB, leaveHookCall = false) {
  294. return new Promise((resolve, reject) => {
  295. if (Intercept == null) { // 什么也不做 直接执行下一个钩子
  296. return resolve();
  297. }
  298. if (Intercept === false) { // 路由中断 我们需要把防抖设置为false
  299. Global.LockStatus = false; // 解锁跳转状态
  300. return reject('路由终止');
  301. }
  302. if (Intercept.constructor === String) { // 说明 开发者直接传的path 并且没有指定 NAVTYPE 那么采用原来的navType
  303. reject('next到其他页面');
  304. return appletsTransitionTo.call(this, Intercept, fnType, navCB, leaveHookCall);
  305. }
  306. if (Intercept.constructor === Object) { // 有一系列的配置 包括页面切换动画什么的
  307. reject('next到其他页面');
  308. return appletsTransitionTo.call(this, Intercept, Intercept.NAVTYPE || fnType, navCB, leaveHookCall);
  309. }
  310. });
  311. };