前端抽象化,打破框架枷锁:统一路由的设计
只要你是在写前端页面,那么好的路由导航无非就两种
- 编程式导航
- 约定式导航
一种是你来控制导航方向和所有页面的导航路径,另一种是根据目录结构来自动生成导航.
而编程式导航,也许我们也应该抽象出来,统一下,让我们切换vue、react亦或者其他东西都不必再需要多余成本,让路由中需要加入一些业务逻辑变得方便,让路由的权限控制变得简单,让路由的历史记录管理变得可控。
所以,便有了这个设计
我在设计这个架构时,最核心的理念是抽象与解耦。我希望路由导航的逻辑能够独立于具体的框架存在,这样一套代码就能适配各种环境,既减少重复开发,又方便未来的扩展。同时使用中间件,让路由导航的逻辑更加模块化,可插拔,可控。
这里面都有什么?
核心模块
导航器(INavigator)
导航器是这个设计的导航口,通过这个去调用跳转,这并没有破坏我们使用vue-router或者react-router的习惯路由解析器(IRouteResolver)
路由解析器从路由信息中解析出来我原始的路由配置对象。对于页面的导航配置相关细则,就在这里面了。中间件与导航管道(NavigationPipeline)
提示
这个部分是唯一特殊一点的部分,管道只是为了让中间件更好的用起来,所以这里我完善了一部分实现,你可以有你自己的设计,只要让中间件的思想和逻辑存在且可用
我借鉴了管道模式,且是阻塞式的管道模式。在导航前,用中间件去处理每个路由需要处理的事情,每个中间件只负责一件事,比如路由守卫检查权限、历史管理记录路径等。这样的模块化设计可以随时插拔不同逻辑处理,想加个新需求?插个中间件就搞定,维护起来也顺手。
- 历史管理器(IHistoryManager)
主动对历史记录进行管理和追踪,可控的方式去进行历史管理。这里推荐用 History API去实现,达到和浏览器同步历史记录栈。 - 适配器(AbstractRouterAdapter)
适配器,就是用不同框架来对接的接口。我定义了一个抽象类 AbstractRouterAdapter,里面封装了通用的接口和方法。只要为特定框架实现一个适配器,我的整个路由系统就能无缝接入,无论是 React、Vue 还是 Angular,你可以不必再重写路由的各种逻辑,直接复用
宏观的整体依赖关系
这里我严格遵循了低层策略依赖高层策略,一定要单向,这样才能保证系统的稳定性和可维护性。
涉及的设计模式
- 适配器模式和工厂模式让我实现了跨框架复用和实例创建的灵活性。
- 管道模式和观察者模式增强了导航过程的模块化和可控性。
- 策略模式则让导航行为更加动态和多样化。
对于设计模式,善用可以增加可读性和可扩展性,否则就是破坏代码简单性。
设计的TS抽象源码
提示
这里是一个设计的抽象模型,你可以根据这个模型去实现你的路由导航系统。无视框架,甚至无视语言,只要你能实现这个模型,你就可以在任何地方使用这个路由系统。
其他平台不是ts语言怎么办?AI会出手,助你转译
/**
* @description: 路由携带数据
* @return {*}
*/
export interface NavigationOptions {
params?: Record<string, string>;
query?: Record<string, string>;
hash?: string;
}
/**
* @description: 跳转类型
* @return {*}
*/
export enum NavigationType {
push = 'push',
replace = 'replace',
back = 'back',
forward = 'forward',
}
/**
* @description: 路由守卫,返回一个真正要跳转的路由
* @return {*} 返回一个新的跳转路径,或者什么也不返回,按原计划跳转
*/
export interface RouteGuard {
(context: NavigationContext): NavigationInstruction|void | Promise<NavigationInstruction|void>;
}
/**
* @description: 路由进入前的钩子
* @return {*}
*/
export interface RouterBefore {
(context: NavigationContext): void | Promise<void>;
}
/**
* @description: 路由离开前的钩子
* @return {*}
*/
export interface RouterLeave {
(context: NavigationContext): void | Promise<void>;
}
/**
* @description: 路由上下文
* @return {*}
*/
export class NavigationContext {
constructor(
public instruction: NavigationInstruction,
public prevInstruction: NavigationInstruction|null,
public nextInstruction: NavigationInstruction|null,
public currentRoute: ResolvedRoute,
) {}
}
/**
* @description: 路由指令
* @return {*}
*/
export class NavigationInstruction {
constructor(
public type: NavigationType,
public target: string,
public options: NavigationOptions,
public timestamp: number
) {}
}
/**
* @description: 路由解析
* @return {*}
*/
export class ResolvedRoute {
constructor(
public path: string,
public component: any,
public guards: RouteGuard[] = [],
public enterHook: RouterBefore[] = [],
public leaveHook: RouterLeave[] = [],
public children: ResolvedRoute[] = [],
public metadata: Map<string, any> = new Map()
) {}
}
import {
NavigationContext,
NavigationInstruction,
NavigationOptions,
NavigationType,
ResolvedRoute,
} from "./router_domain";
/**
* @description:导航器,使用有两种方式进行编程式路由导航
* @return {*}
*/
export interface INavigator {
push(target: string, options: NavigationOptions): Promise<void>;
replace(target: string, options: NavigationOptions): Promise<void>;
back(): Promise<void>;
forward(): Promise<void>;
}
/**
* @description: 路由解析器,用于解析路由实际对象
* @return {*}
*/
export interface IRouteResolver {
resolve(path: string): Promise<ResolvedRoute>;
}
/**
* @description: 中间件逻辑,用于处理路由跳转前的随时可插拔处理
* @return {*}
*/
export interface IMiddleware {
process(context: NavigationContext, next: () => Promise<void>): Promise<void>;
}
/**
* @design: 建议实现为单例模式
* @description: 历史管理器,用于管理路由历史记录,用于可监控式路由路径
* @return {*}
*/
export interface IHistoryManager {
push(entry: NavigationInstruction): void;
replace(entry: NavigationInstruction): void;
back(): void;
forward(): void;
getAllHistory(): NavigationInstruction[];
clear(): void;
}
/**
* @description: 路由数据,用于存储全部路由信息
* @return {*}
*/
export interface IRoute {
routes: ResolvedRoute[];
routeMap: Map<string, ResolvedRoute>;
createRouteMap(): void;
addRoute(route: ResolvedRoute): void;
}
import { IHistoryManager, IMiddleware } from "./router_core";
import { NavigationContext } from "./router_domain";
/**
* @description: 管道设计,当前这个是阻塞式的管道设计,依次处理中间件
* @return {*}
*/
export class NavigationPipeline {
middlewares: IMiddleware[] = [];
errorHandler?: (error: Error, context: NavigationContext) => void;
async pipe(context: NavigationContext): Promise<void> {
let index = 0;
const next = async () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
await middleware.process(context, next);
}
};
await next();
}
addMiddleware(middleware: IMiddleware): void {
this.middlewares.push(middleware);
}
onError(handler: (error: Error, context: NavigationContext) => void): void {
this.errorHandler = handler;
}
}
/**
* @description: 路由守卫中间件
* @return {*}
*/
export class GuardMiddleware implements IMiddleware {
async process(
context: NavigationContext,
next: () => Promise<void>
): Promise<void> {
const route = context.currentRoute;
for (const guard of route.guards) {
const result = await guard(context);
// 若得到了新的跳转路径,则跳转到新的路径
if (result) {
context.instruction = result;
break;
}
}
await next();
}
}
export class HistoryMiddleware implements IMiddleware {
histroyManager: IHistoryManager;
constructor(histroyManager: IHistoryManager) {
this.histroyManager = histroyManager;
}
async process(
context: NavigationContext,
next: () => Promise<void>
): Promise<void> {
const instruction = context.instruction;
const type = instruction.type;
this.histroyManager[type](instruction);
next();
}
}
import { INavigator, IRouteResolver, IHistoryManager, IRoute } from "./router_core"
import { NavigationPipeline } from "./router_pipeline"
export abstract class AbstractRouterAdapter {
navigator: INavigator
resolver: IRouteResolver
history: IHistoryManager
route:IRoute
pipeline: NavigationPipeline
initialize(route:IRoute,pipeline:NavigationPipeline): void{
this.route = route
this.pipeline = pipeline
this.navigator = this.createNavigator()
this.resolver = this.createResolver()
this.history = this.createHistory()
}
abstract createNavigator(): INavigator
abstract createResolver(): IRouteResolver
abstract createHistory(): IHistoryManager
abstract errorHandler():void
}
图像也许会帮你更好的理解
流程图
细节的类图
以下uml图,可以帮你快速的理解我这里的依赖关系,他是单向的,高层策略和低层策略是很明显的。
你可以右键下面这个图,在新的标签页中打开,这样可以放大和拖动的查看
上面uml类图的源码,还是有一些价值的
@startuml 高级路由抽象设计
left to right direction
' 领域模型
package "Domain Models" {
interface "NavigationOptions" {
params?: Map<string, string> :路由参数,比如 /user/:id
query?: Map<string, string> :查询参数,比如 ?id=1
hash?: string :比如锚点
}
enum "NavigationType" {
push
replace
back
forward
}
interface RouteGuard {
(context: NavigationContext): NavigationInstruction| void | Promise<NavigationInstruction|void>;
}
interface RouterBefore {
(context: NavigationContext): void | Promise<void>;
}
interface RouterLeave {
(context: NavigationContext): void | Promise<void>;
}
class "NavigationContext" {
+instruction: NavigationInstruction :导航指令
+prevInstruction: NavigationInstruction
+nextInstruction: NavigationInstruction
+currentRoute: ResolvedRoute
}
class "NavigationInstruction" {
+type: NavigationType
+target: string
+options: NavigationOptions
+timestamp: number
}
class "ResolvedRoute" {
+path: string
+name?: string
+component: any
+guards?: RouteGuard[] :路由守卫,用于拦截导航
+enterHook?: RouterBefore[] :完成一些预处理任务
+leaveHook?: RouterLeave[] :完成一些后处理任务
+children?: ResolvedRoute[] :子路由
+metadata: Map<string, any>
}
}
' 核心逻辑抽象
package "Core Abstractions" {
' 导航器,使用有两种方式进行编程式路由导航
interface "INavigator" {
+push(target: string, options: NavigationOptions): Promise<void>
+replace(target: string, options: NavigationOptions): Promise<void>
+back(): Promise<void>
+forward(): Promise<void>
}
' 路由解析器,用于解析路由路径
interface "IRouteResolver" {
+resolve(path: string): Promise<ResolvedRoute>
}
interface "IMiddleware" {
' 执行此中间件逻辑
+process(context: NavigationContext, next: () => Promise<void>): Promise<void>
}
interface "IHistoryManager" {
+push(entry: NavigationInstruction): void
+replace(entry: NavigationInstruction): void
+back(): void
+forward(): void
+getAllHistory(): NavigationInstruction[]
+clear(): void
}
interface "IRoute" {
routes: ResolvedRoute[]
routeMap: Map<string, ResolvedRoute>
+createRouteMap(): void: 根据routes创建路由映射表
+addRoute(route: ResolvedRoute): void
}
}
package "Pipeline" {
class "NavigationPipeline" {
-middlewares: IMiddleware[]
-errorHandler?: (error: Error, context: NavigationContext) => void
+pipe(context: NavigationContext): Promise<void>
+addMiddleware(middleware: IMiddleware): void
+onError(handler: (error: Error, context: NavigationContext) => void): void
}
' 路由守卫中间件
class "GuardMiddleware" {
+process(context: NavigationContext, next: () => Promise<void>): Promise<void>
}
'
class "HistoryMiddleware" {
+process(context: NavigationContext, next: () => Promise<void>): Promise<void>
}
}
package "Framework Adapters" {
abstract class "AbstractRouterAdapter" {
+navigator: INavigator
#resolver: IRouteResolver
+history: IHistoryManager
+route:IRoute
#pipeline: NavigationPipeline
+initialize(route:IRoute,pipeline:NavigationPipeline): void
+{abstract} createNavigator(): INavigator
+{abstract} createResolver(): IRouteResolver
+{abstract} createHistory(): IHistoryManager
+{abstract} errorHandler():void
}
}
' 关系定义
NavigationPipeline o-- IMiddleware
AbstractRouterAdapter o-- INavigator
AbstractRouterAdapter o-- IRouteResolver
AbstractRouterAdapter o-- NavigationPipeline
AbstractRouterAdapter o-- IHistoryManager
GuardMiddleware ..|> IMiddleware
HistoryMiddleware ..|> IMiddleware
NavigationContext --* NavigationInstruction
NavigationContext --* ResolvedRoute
ResolvedRoute o-- RouteGuard
ResolvedRoute o-- RouterBefore
ResolvedRoute o-- RouterLeave
ResolvedRoute o-- ResolvedRoute
NavigationInstruction --> NavigationType
NavigationPipeline --> NavigationContext
IRouteResolver --> ResolvedRoute
INavigator --> NavigationType
INavigator --> NavigationOptions
IHistoryManager o-- NavigationInstruction
IRoute o-- ResolvedRoute
note right of NavigationPipeline
导航管道
处理所有导航相关的中间件
end note
note right of IMiddleware
中间件接口
定义了导航过程中的处理单元
end note
note right of AbstractRouterAdapter
路由适配器抽象基类
可以采用适配器模式来实现不同框架的路由适配
也可以采用工厂模式来创建不同框架的路由适配器
end note
@enduml