记录–有关uni-app如何实现路由拦截的知识分享

  • 记录–有关uni-app如何实现路由拦截的知识分享已关闭评论
  • 28 次浏览
  • A+
所属分类:Web前端
摘要

随着业务的需求,项目需要支持H5、各类小程序以及IOS和Android,这就需要涉及到跨端技术,不然每一端都开发一套,人力成本和维护成本太高了。团队的技术栈主要以Vue为主,最终的选型是以uni-app+uview2.0作为跨端技术栈。以前一直听别人吐槽uni-app怎么怎么不好,但是没什么概念,这一次需要为团队开发一个项目的基础框架和一些示例页面,主要是支持路由拦截、http请求多实例、请求数据加密以及登录功能封装,发现uni-app的生态不怎么健全,比如我们项目很需要的路由拦截,http请求拦截,这些都没有提供,对于跨端的兼容问题也挺多的。这篇文章聊聊的路由拦截的调研,以及最终的选择和实现。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--有关uni-app如何实现路由拦截的知识分享

前言

随着业务的需求,项目需要支持H5、各类小程序以及IOS和Android,这就需要涉及到跨端技术,不然每一端都开发一套,人力成本和维护成本太高了。团队的技术栈主要以Vue为主,最终的选型是以uni-app+uview2.0作为跨端技术栈。以前一直听别人吐槽uni-app怎么怎么不好,但是没什么概念,这一次需要为团队开发一个项目的基础框架和一些示例页面,主要是支持路由拦截http请求多实例请求数据加密以及登录功能封装,发现uni-app的生态不怎么健全,比如我们项目很需要的路由拦截,http请求拦截,这些都没有提供,对于跨端的兼容问题也挺多的。这篇文章聊聊的路由拦截的调研,以及最终的选择和实现。

实现路由拦截的方式

  • 使用uni-simple-router
  • 重写uni-app跳转方法
  • 对uni-app跳转方法做进一步的封装

使用uni-simple-router

uni-simple-router是为uni-app专门提供的路由管理器,使用方式跟vue-router的API一致,可以很方便的上手,Github 也有了六百多的start,它可以说是uni-app用来做路由管理很好的选择,但是我没有选择使用它,个人认为开发h5是可以的,但是如果做跨端,可能会有一些后患,接下来我们聊聊为什么不使用它的原因。

无法拦截switchTabnavigateBack

这个其实也不算是一个缺点,目前也没找到可以拦截这两个事件的路由插件,如果确实需要实现这两种跳转方式的拦截,也是可以实现的,可以使用下一种方式,对这两种方法进行暴力重写。

没有解决全部的跨端兼容问题

这个其实是我不选择它的主要原因,根据官方文档的说明,根据文档去配置和编写,基本上能解决所有端上的95%的问题,其他的5%的问题需要去查看编译到端的说明。代码还是严谨的,缺少1%都是不完美的,更何况是5%。这会导致在以后的使用过程中,可能因为兼容问题,导致自己没办法去解决,或者为了解决这个问题,需要花费大量的时间和精力,有可能得不偿失。

编译app时,不能用'nvue'作为启动页面

nvue 不能直接作为启动页面。因为在启动时 uni-app 会检测启动页面是否为原生渲染,原生渲染时不会执行路由跳转,插件无法正确捕捉页面挂载。这也是一个问题,我们可以尽量的去避免,但以后有未知的情况,可能我们的启动页必须就是以nvue来实现。

暴力重写uni-app跳转方法

这种方式虽然有点简单粗暴,但是效果挺好的,代码也很简短,Vue2.0对于数组的响应式监听也是采用这种方式。虽然实现了,但可能有些同学不知道怎么使用,直接把这段代码写在main.js就可以了,或者也可以在单独的文件里封装一个封装一个函数,然后在main.js引入,然后执行该方法。

const routeInterceptor = () => {     const methodToPatch = ["navigateTo", "redirectTo", "switchTab", "navigateBack"];     methodToPatch.map((type) => {       // 通过遍历的方式分别取出,uni.navigateTo、uni.redirectTo、uni.switchTab、uni.navigateBack       // 并且对相应的方法做重写       const original = uni[type];       uni[item] = function (options = {}) {         if (!token) {           // 判断是否存在token,不存在重定向到登录页           uni.navigateTo({             url: "/login",           });         } else {           return original.call(this, opt);         }       };     }); }  routeInterceptor()

这是一个最极简的方式,需要添加其他参数和判断逻辑,大家可以自行添加,这里只是抛砖引玉,给大家提供一个思路。

使用方式

handleDetail() {     uni.navigateTo({                 url: '/detail?id=11111111111'     })  }

对uni-app跳转方法做进一步的封装

这个是 uView提供的一种路由封装方式,对于路由传参做了进一步的封装,使用起来更加方便,但是不涉及到uni-app跳转方式的重写,所以也谈不上改了路由跳转的跨端兼容,所以还是具有uni-app一致的兼容性。但是官方文档没有说明提供了路由拦截,但这个还是我们特别需要的功能,去查看源码,发现还是提供了这个功能。现在还存在的一个问题是,这个功能是跟uView强耦合的,可能我们并不想使用uView,所以我们可以将这个功能独立抽离。

记录--有关uni-app如何实现路由拦截的知识分享

 目录结构

记录--有关uni-app如何实现路由拦截的知识分享

/router/index.js

这个文件主要提供路由拦截函数,具体的实现,可以大家可以根据自己的需求实现,最后向外暴露一个包含install方法的对象,使用的时候可以直接用Vue.use进行注册。

routeConfig这个参数是路由相关的配置,resolve 传递一个true或者false表示是否允许跳转。

routeConfig属性

参数名 类型 默认值 是否必填 说明
type String navigateTo false navigateToto对应uni.navigateToredirectredirectTo对应uni.redirectToswitchTabtab对应uni.switchTabreLaunch对应uni.reLaunchnavigateBackback对应uni.navigateBack
url String - false typenavigateToredirectToswitchTabreLaunch时为必填
delta Number 1 false typenavigateBack时用到,表示返回的页面数
params Object - false 传递的对象形式的参数,如{name: 'lisa', age: 18}
animationType String pop-in false 只在APP生效,详见窗口动画(opens new window)
animationDuration Number 300 false 动画持续时间,单位ms
import route from "./route"; // 配置白名单 const whiteList = ["/pages/login/index"]; const install = function (Vue, options) {   uni.$e = { route };   Vue.prototype.route = route;   uni.$e.routeIntercept = (routeConfig, resolve) => {     const path = routeConfig.url.split("?")[0];     if (!whiteList.includes(path) && !uni.getStorageSync("token")) {       uni.$e.route("/pages/login/index");       return;     }     resolve(true);   }; }; export default {   install, };

/router/route.js

这个文件,主要是对于uni-app跳转做了封装,主要做的还是传参部分,实现跟vue-router一致的传参方式,使用起来更加方便优雅,同时提供一个uni.$e.routeIntercept路由拦截方法。

/**  * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷  * 并且带有路由拦截功能  */  import { queryParams, deepClone, deepMerge, page } from "./utils"; class Router {   constructor() {     // 原始属性定义     this.config = {       type: "navigateTo",       url: "",       delta: 1, // navigateBack页面后退时,回退的层数       params: {}, // 传递的参数       animationType: "pop-in", // 窗口动画,只在APP有效       animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效       intercept: false, // 是否需要拦截     };     // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文     // 这里在构造函数中进行this绑定     this.route = this.route.bind(this);   }    // 判断url前面是否有"/",如果没有则加上,否则无法跳转   addRootPath(url) {     return url[0] === "/" ? url : `/${url}`;   }    // 整合路由参数   mixinParam(url, params) {     url = url && this.addRootPath(url);      // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"     // 如果有url中有get参数,转换后无需带上"?"     let query = "";     if (/.*/.*?.*=.*/.test(url)) {       // object对象转为get类型的参数       query = queryParams(params, false);       // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开       return (url += `&${query}`);     }     // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号     query = queryParams(params);     return (url += query);   }    // 对外的方法名称   async route(options = {}, params = {}) {     // 合并用户的配置和内部的默认配置     let mergeConfig = {};      if (typeof options === "string") {       // 如果options为字符串,则为route(url, params)的形式       mergeConfig.url = this.mixinParam(options, params);       mergeConfig.type = "navigateTo";     } else {       mergeConfig = deepClone(options, this.config);       // 否则正常使用mergeConfig中的url和params进行拼接       mergeConfig.url = this.mixinParam(options.url, options.params);     }      // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题     if (mergeConfig.url === page()) return;      if (params.intercept) {       this.config.intercept = params.intercept;     }     // params参数也带给拦截器     mergeConfig.params = params;     // 合并内外部参数     mergeConfig = deepMerge(this.config, mergeConfig);     // 判断用户是否定义了拦截器     if (typeof uni.$e.routeIntercept === "function") {       // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转       const isNext = await new Promise((resolve, reject) => {         uni.$e.routeIntercept(mergeConfig, resolve);       });       // 如果isNext为true,则执行路由跳转       isNext && this.openPage(mergeConfig);     } else {       this.openPage(mergeConfig);     }   }    // 执行路由跳转   openPage(config) {     // 解构参数     const { url, type, delta, animationType, animationDuration } = config;     if (config.type == "navigateTo" || config.type == "to") {       uni.navigateTo({         url,         animationType,         animationDuration,       });     }     if (config.type == "redirectTo" || config.type == "redirect") {       uni.redirectTo({         url,       });     }     if (config.type == "switchTab" || config.type == "tab") {       uni.switchTab({         url,       });     }     if (config.type == "reLaunch" || config.type == "launch") {       uni.reLaunch({         url,       });     }     if (config.type == "navigateBack" || config.type == "back") {       uni.navigateBack({         delta,       });     }   } }  export default new Router().route;

/router/uitls.js

这个文件主要是为路由封装提供一些工具函数

/**  * @description 对象转url参数  * @param {object} data,对象  * @param {Boolean} isPrefix,是否自动加上"?"  * @param {string} arrayFormat 规则 indices|brackets|repeat|comma  */ export const queryParams = (   data = {},   isPrefix = true,   arrayFormat = "brackets" ) => {   const prefix = isPrefix ? "?" : "";   const _result = [];   if (["indices", "brackets", "repeat", "comma"].indexOf(arrayFormat) == -1)     arrayFormat = "brackets";   for (const key in data) {     const value = data[key];     // 去掉为空的参数     if (["", undefined, null].indexOf(value) >= 0) {       continue;     }     // 如果值为数组,另行处理     if (value.constructor === Array) {       // e.g. {ids: [1, 2, 3]}       switch (arrayFormat) {         case "indices":           // 结果: ids[0]=1&ids[1]=2&ids[2]=3           for (let i = 0; i < value.length; i++) {             _result.push(`${key}[${i}]=${value[i]}`);           }           break;         case "brackets":           // 结果: ids[]=1&ids[]=2&ids[]=3           value.forEach((_value) => {             _result.push(`${key}[]=${_value}`);           });           break;         case "repeat":           // 结果: ids=1&ids=2&ids=3           value.forEach((_value) => {             _result.push(`${key}=${_value}`);           });           break;         case "comma":           // 结果: ids=1,2,3           let commaStr = "";           value.forEach((_value) => {             commaStr += (commaStr ? "," : "") + _value;           });           _result.push(`${key}=${commaStr}`);           break;         default:           value.forEach((_value) => {             _result.push(`${key}[]=${_value}`);           });       }     } else {       _result.push(`${key}=${value}`);     }   }   return _result.length ? prefix + _result.join("&") : ""; };  /**  * 是否数组  */ function isArray(value) {   if (typeof Array.isArray === "function") {     return Array.isArray(value);   }   return Object.prototype.toString.call(value) === "[object Array]"; }  /**  * @description 深度克隆  * @param {object} obj 需要深度克隆的对象  * @returns {*} 克隆后的对象或者原值(不是对象)  */ export const deepClone = (obj) => {   // 对常见的“非”值,直接返回原来值   if ([null, undefined, NaN, false].includes(obj)) return obj;   if (typeof obj !== "object" && typeof obj !== "function") {     // 原始类型直接返回     return obj;   }   const o = isArray(obj) ? [] : {};   for (const i in obj) {     if (obj.hasOwnProperty(i)) {       o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];     }   }   return o; };  /**  * @description JS对象深度合并  * @param {object} target 需要拷贝的对象  * @param {object} source 拷贝的来源对象  * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)  */ export const deepMerge = (target = {}, source = {}) => {   target = deepClone(target);   if (typeof target !== "object" || typeof source !== "object") return false;   for (const prop in source) {     if (!source.hasOwnProperty(prop)) continue;     if (prop in target) {       if (typeof target[prop] !== "object") {         target[prop] = source[prop];       } else if (typeof source[prop] !== "object") {         target[prop] = source[prop];       } else if (target[prop].concat && source[prop].concat) {         target[prop] = target[prop].concat(source[prop]);       } else {         target[prop] = deepMerge(target[prop], source[prop]);       }     } else {       target[prop] = source[prop];     }   }   return target; };  /**  * @description 获取当前页面路径  */ export const page = () => {   const pages = getCurrentPages();   // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组   return `/${pages[pages.length - 1]?.route ?? ""}`; };

路由配置

在main.js引入

import router from "./router";  Vue.use(router);

使用方式

更全的使用方式可以查看 uView路由跳转文档

全局使用

uni.$e.route('/pages/info/index');

vue文件中使用

this.route('/pages/info/index');

拦截switchTab、navigateBack

现在的方式还是没办法支持拦截switchTab、navigateBack,所以需要借助第二种方式,重写这两种方法,具体实现,完善 /router/index.js

// /router/index.js  import route from "./route"; // 配置白名单 const whiteList = ["/pages/login/index"];  const handleOverwirteRoute = () => {   // 重写switchTab、navigateBack   const methodToPatch = ["switchTab", "navigateBack"];   methodToPatch.map((type) => {     // 通过遍历的方式分别取出,uni.switchTab、uni.navigateBack     // 并且对相应的方法做重写     const original = uni[type];     uni[type] = function (options = {}) {       const { url: path } = options;       if (!whiteList.includes(path) && !uni.getStorageSync("token")) {         // 判断是否存在token,不存在重定向到登录页         uni.$e.route("/pages/login/index");       } else {         return original.call(this, options);       }     };   }); };  const install = function (Vue, options) {   uni.$e = { route };   Vue.prototype.route = route;   // 重写uni方法   handleOverwirteRoute();   // 路由拦截器   uni.$e.routeIntercept = (routeConfig, resolve) => {     const path = routeConfig.url.split("?")[0];     if (!whiteList.includes(path) && !uni.getStorageSync("token")) {       uni.$e.route("/pages/login/index");       return;     }     resolve(true);   }; }; export default {   install, };

补充

在系统第一进入的时候,是不会触发拦截事件的,需要在App.js的onLanch去做进一步的实现。

onLaunch: function () {     if (!uni.getStorageSync("token")) {       uni.navigateTo({ url: "/pages/login/index" });     } },

记录--有关uni-app如何实现路由拦截的知识分享

本文转载于:

https://juejin.cn/post/7119274924149047327

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--有关uni-app如何实现路由拦截的知识分享