Skip to content

1.React 中的 路由拦截策略与 vue 对比

1.App.js 里面根据 路径 和 是否有 token 等信息进行判断去哪个路由 2.router 文件夹下面的 index.js 进行鉴权判断去哪个路由和动态标题的设置 App.js > main.js > index.js > routeConfigLazy.js 下面是文件的包含利用关系: image.png|425

对应的 vue-router 拦截器配置:把两部分内容合到一起的

js
if (to.path !== "/404" && to.path !== "/checkLogin") {
	/*
	/#\/main\/ReportNav\/ReportManage:匹配哈希部分中包含#/main/ReportNav/ReportManage的URL。
	
	#\/main\/supervise:匹配哈希部分中包含#/main/supervise的URL。
	
	#\/main\/FeedbackDetails:匹配哈希部分中包含#/main/FeedbackDetails的URL。
*/

	if (
		search.nw &&
		/#\/main\/ReportNav\/ReportManage|#\/main\/supervise|#\/main\/FeedbackDetails/.test(
			hash
		)
	) {
		if (token) {
			next(); //直接向下走,这三个路径不用鉴权,登陆过了就行
		} else {
			if (GetQueryString().ssoToken) {
				//如果路径后面拼接了 ssoToken 的话:https://test-agent.myyixue.com/#/ssologin?ssoToken=1n6LO9wsn88tuP50E9bgnTn9Qoz1Fewy8E9ajnnRK27NxXssPN6auw0iE98p49j09ic1Zy8E96

				localStorage.setItem("needBackSso", 1);

				next({ path: "/ssologin" });
			} else {
				next({ path: "/login" });
			}
		}
	} else if (
		to.path === "/login" ||
		to.path === "/pt-login" ||
		to.path === "/agent-login" ||
		to.path === "/ssologin" ||
		to.fullPath.indexOf("/ImproveScoreCase/Preview") > -1
	) {
		if (token) {
			//必须是 cookie 中没有 token了才可以去登录页面

			//如果已经处于登录状态要进行拦截,转到首页(本身如果直接改是进不去登录的,如果在前面加个#还是可以进入登录,所以一定要拦截一下)

			//跳转到之前点击的那个侧边栏 url

			next({ path: this.$store.state.selectSideUrl });
		} else {
			next(); //没登录的话,可以去登录
		}
	} else {
		if (token) {
			//登陆了才有必要鉴权,否则必须先登录!

			let isEmpty = false;

			let CrmNavUrls = JSON.parse(
				localStorage.getItem("CRM_NAVFUNC_URLS") || "[]"
			);

			// 鉴权(防止直接通过路径跳转来访问没有权限的页面)
			isEmpty =
				CrmNavUrls.indexOf(to.path) >= 0 || to.path == "/main" //去 main 页面的话不应该拦截(前提是在已登录的情况下)
					? true
					: false;

			console.log(CrmNavUrls.indexOf(to.path) >= 0);

			console.log(to.path == "/main");

			if (!isEmpty) {
				//没有权限

				next({ path: "/main/noMenu" });
			} else {
				//有权限

				next(); //大多数正常的走这里
			}
		} else {
			//如果不是去登录的(任何 login 都不是)还没有 token

			next({ path: "/login" });
		}
	}
} else {
	next(); //404或者checkLogin走这里
}

区别的理解:react 将逻辑分摊到组件里面,看起来比较直观,逻辑性强,适合进行非常复杂的配置(可以拆分复杂度); 而 vue 把逻辑统一到配置文件,方便配置和修改。

2.ComputedMatch

computedMatch 是使用 Switch 包裹的子组件才有的值,Switch 的作用是从上到下开始渲染,只要匹配到一个,其他的就不匹配。 在 React Router 的 Switch 组件内的每个 Route 组件都会有一个 computedMatch 属性,无论其是否匹配当前 URL。 ComputedMatch 是由 Route 组件内部根据路由匹配逻辑计算得出的一个属性。它包含有关路由匹配的信息,如 path、url 和 isExact 等。 注意:ComputedMatch 属性只有在组件被成功匹配到当前 URL 时才会有值,否则为 null。 所以可以把 computedMatch.url 理解为就是当前的组件对应的路径。——> 可以说等价于 match. Path(但是很多时候我们不能直接获得到 history 对象,比如 Route 外面包裹了高阶组件 PrivateRoute,而 PrivateRoute 不能获取到 history 对象,所以只能用 computedMatch) 例子:使用 computedMatch.url 来做路由鉴权

js
let flag =
	CrmNavUrls.indexOf(rest.computedMatch.url) >= 0 ||
	rest.type === "notMenu" ||
	rest.computedMatch.url.indexOf("/main/SchoolArchives") >= 0;

Route 的路由包装器 ——> PrivateRoute 高阶组件

PrivateRoute 是一个高阶组件,它封装了 Route 组件并添加了一些额外的逻辑和处理。当你希望在渲染 Route 组件之前进行一些特定的检查或操作时,你可以使用 PrivateRoute 作为中间组件。 这样要传递给 Route 组件的内容都会经过 PrivateRoute 这个高阶组件 例子:

jsx
return (
	<PrivateRoute
		type={item.type || false}
		title={item.title}
		path={item.path}
		key={index}
		meta={item.meta}
		component={item.component}
	/>
);

function PrivateRoute({ component: Component, ...rest }) {
	//rest里面有computedMatch属性
	isEmpty =
		CrmNavUrls.indexOf(rest.computedMatch.url) >= 0 ||
		rest.type === "notMenu" ||
		rest.computedMatch.url.indexOf("/main/SchoolArchives") >= 0;
}

Location. Pathname 和 match. Path 对比:获取当前路由的路径信息

Location. Pathname 和 match. Path 是用于路由匹配的 React Router 属性,用于获取当前路由的路径信息。它们之间有一些区别:

  • Location. Pathname:实际的 url 属于 location 对象的属性。 返回当前 URL 的路径部分。 不考虑路由的匹配规则,仅返回 URL 中的路径部分。 例如,对于 URL /users/123/profile,location. Pathname 将返回 /users/123/profile。
  • Match. Path:定义的 url(包含占位符) 属于 match 对象的属性。 返回当前路由的模式(path),即路由定义时指定的路径。 考虑路由的匹配规则,与当前 URL 的路径部分进行比较。 用于在嵌套路由或动态路径中,获取当前路由定义的路径模式。 例如,对于路由定义 ,如果当前 URL 为 /users/123/profile,match. Path 将返回 /users/: id/profile。 综上所述,location. Pathname 返回当前 URL 的路径部分,而 match. Path 返回当前路由的路径模式(path)。它们用途不同,根据需要选择使用。一般来说,当需要获取当前 URL 的路径时,可以使用 location. Pathname;当需要获取当前路由定义的路径模式时,可以使用 match. Path。

Route 组件对于 history 对象(以及 location、match 对象)属性的获取:

在 React Router 中,Route 组件渲染的组件会自动获得 history 属性,不论该组件是否位于 Switch 组件中。 如果你在 Route 的 render 属性中返回的组件需要访问 history 对象,你可以将 history 对象作为参数传递给回调函数,并将其传递给渲染的组件。(否则可能不会拥有 history 对象)

jsx
<Route
	path="/my-route"
	render={(routeProps) => (
		<MyComponent {...routeProps} history={routeProps.history} />
	)}
/>

3.hash 模式的特点

在 React Router 的 hash 模式下,当路径发生变化时,路由的 path 属性会反映当前的 URL 路径。 在 hash 模式下,URL 中的路径会以 # 符号为前缀,例如 http://example.com/#/users。当路径发生变化时,React Router 会解析 URL 中的 hash 部分,并将其与定义的路由路径进行匹配。 hash 模式只会匹配#之后的作为路径(并且?参数之前的)image.png|425 所以说这样写的话: 注意:rest 本身就有一个被传进来的属性叫做 path image.png|425|425

会导致下面的访问路径不能被匹配: image.png|425

从而会走向这里: image.png|425

准确的说是,不同的服务(ip 端口号不同),那么就会有不同的本地存储和 cookie

5.类式封装的 axios

这种封装方式适合于 react 中:

js
import axios from "axios";
import { getBaseUrl, getPhpUrl } from "./config";
import { CRM_TOKEN, PT_SCHOOL_LOGIN } from "./keys";
import { getCookie, delCookie } from "./cookie";
import { message } from "antd";
import qs from "qs";
import { clearLocalStorage } from "./util";

axios.withCredentials = true;
axios.defaults.baseURL = getBaseUrl();
axios.defaults.headers.post["Content-Type"] = "application/json; charset=UTF-8";
axios.interceptors.request.use(
	//拦截器
	function (config) {
		let token = getCookie(CRM_TOKEN);
		console.log(window.location.href.indexOf("/ssologin"));
		console.log(window.location.href);
		if (
			config.url.indexOf("/user/login") === -1 &&
			config.url.indexOf("user/resetPassword") === -1 &&
			window.location.href.indexOf("login") === -1 &&
			!token
		) {
			loginOut();
		}
		if (
			(config.url.indexOf("/user/login") === -1 &&
				config.url.indexOf("user/resetPassword") === -1 &&
				window.location.href.indexOf("/ssologin") === -1) ||
			config.url.indexOf("/user/loginOut")
		) {
			console.log(token);
			config.headers.common["Authorization"] = `${token}`;
		}
		return Promise.resolve(config);
	},
	function (error) {
		return Promise.reject(error);
	}
);

axios.interceptors.response.use(
	function (response) {
		// 获取token接口不校验,直接返回
		if (response.config.url.indexOf("token") !== -1) {
			return Promise.resolve(response.data);
		}
		// 登录失效
		if (
			response.data &&
			(response.data.code === -1 || response.data.code === 1000)
		) {
			loginOut();
		} else if (response.data.code === -2) {
			// ssologin
			goBackSso();
		}
		let retData;
		if (response.data.code === -1) {
			retData = {
				...response.data,
				message: "当前登录状态失效 请重新登录",
			};
		} else if (response.data.code === 500) {
			retData = {
				...response.data,
				message: "服务器开小差了,请稍后重试!",
			};
		} else {
			retData = {
				...response.data,
			};
		}
		return Promise.resolve(retData);
	},
	function (error) {
		// 获取token接口不校验,直接返回
		if (
			error.response &&
			error.response.data &&
			(error.response.data.code === -1 ||
				error.response.data.code === 1000)
		) {
			loginOut();
		}
		message.warning("服务器开小差了,请稍后重试!");
		return Promise.reject(error);
	}
);

//真正地封装
class CRMAxios {
	constructor(options = {}) {
		this.options = options;
	}
	request(options) {
		return axios.request(options);
	}
	get(url, options = {}) {
		return this.request({
			url: url,
			params: {
				...options,
			},
		});
	}
	phpGet(url, options = {}) {
		return this.request({
			url: getPhpUrl() + url,
			params: {
				...options,
			},
		});
	}
	post(url, data, options = {}) {
		if (data instanceof Object) {
			data = JSON.stringify(data);
		}
		return this.request({
			method: "post",
			url,
			data,
			...options,
		});
	}
	phpPost(url, data, options = {}) {
		if (data instanceof Object) {
			data = JSON.stringify(data);
		}
		return this.request({
			method: "post",
			url: getPhpUrl() + url,
			data,
			...options,
		});
	}
}
const instanceAxios = new CRMAxios();
export const request = instanceAxios.request.bind(instanceAxios);
export const get = instanceAxios.get.bind(instanceAxios); //重新绑定给instanceAxios实例
export const post = instanceAxios.post.bind(instanceAxios);
export const phpPost = instanceAxios.phpPost.bind(instanceAxios);
export const phpGet = instanceAxios.phpGet.bind(instanceAxios);