這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言
隨著業務的需求,專案需要支持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是可以的,但是如果做跨端,可能會有一些后患,接下來我們聊聊為什么不使用它的原因,
無法攔截switchTab、navigateBack
這個其實也不算是一個缺點,目前也沒找到可以攔截這兩個事件的路由插件,如果確實需要實作這兩種跳轉方式的攔截,也是可以實作的,可以使用下一種方式,對這兩種方法進行暴力重寫,
沒有解決全部的跨端兼容問題
這個其實是我不選擇它的主要原因,根據官方檔案的說明,根據檔案去配置和撰寫,基本上能解決所有端上的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,所以我們可以將這個功能獨立抽離,

目錄結構

/router/index.js
這個檔案主要提供路由攔截函式,具體的實作,可以大家可以根據自己的需求實作,最后向外暴露一個包含install方法的物件,使用的時候可以直接用Vue.use進行注冊,
routeConfig這個引數是路由相關的配置,resolve 傳遞一個true或者false表示是否允許跳轉,
routeConfig屬性
| 引數名 | 型別 | 默認值 | 是否必填 | 說明 |
|---|---|---|---|---|
| type | String | navigateTo | false | navigateTo或to對應uni.navigateTo,redirect或redirectTo對應uni.redirectTo,switchTab或tab對應uni.switchTab,reLaunch對應uni.reLaunch,navigateBack或back對應uni.navigateBack |
| url | String | - | false | type為navigateTo,redirectTo,switchTab,reLaunch時為必填 |
| delta | Number | 1 | false | type為navigateBack時用到,表示回傳的頁面數 |
| 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 = https://www.cnblogs.com/smileZAZ/archive/2022/10/10/{},
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 = https://www.cnblogs.com/smileZAZ/archive/2022/10/10/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" });
}
},

本文轉載于:
https://juejin.cn/post/7119274924149047327
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/513085.html
標籤:其他
