hooks.js 15 KB

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