场景描述
在应用开发中无论是出于工程组织效率还是开发体验的考虑,开发者都需要对项目进行模块间解耦,此时需要构建一套用于模块间组件跳转、数据通信的路由框架。
业界常见的实现方式是在编译期生成路由表。
1. 实现原理及流程
- 在编译期通过扫描并解析ets文件中的自定义注解来生成路由表和组件注册类
- Har中的rawfile文件在Hap编译时会打包在Hap中,通过这一机制来实现路由表的合并
- 自定义组件通过wrapBuilder封装来实现动态获取
- 通过NavDestination的Builder机制来获取wrapBuilder封装后的自定义组件
2. 使用ArkTS自定义装饰器来代替注解的定义
由于TS语言特性,当前只能使用自定义装饰器
使用@AppRouter装饰器来定义路由信息
// 定义空的装饰器
export function AppRouter(param:AppRouterParam) {
return Object;
}
export interface AppRouterParam{
uri:string;
}
自定义组件增加路由定义
@AppRouter({ uri: "app://login" })
@Component
export struct LoginView {
build(){
//...
}
}
3. 实现动态路由模块
定义路由表(该文件为自动生成的路由表)
{
"routerMap": [
{
"name": "app://login", /* uri定义 */
"pageModule": "loginModule", /* 模块名 */
"pageSourceFile": "src/main/ets/generated/RouterBuilder.ets", /* Builder文件 */
"registerFunction": "LoginViewRegister" /* 组件注册函数 */
}
]
}
应用启动时,在EntryAbility.onCreate中加载路由表
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
DynamicRouter.init({
libPrefix: "@app", mapPath: "routerMap"
}, this.context);
}
export class DynamicRouter {
// 路由初始化配置
static config: RouterConfig;
// 路由表
static routerMap: Map<string, RouterInfo> = new Map();
// 管理需要动态导入的模块,key是模块名,value是WrappedBuilder对象,动态调用创建页面的接口
static builderMap: Map<string, WrappedBuilder<Object[]>> = new Map();
// 路由栈
static navPathStack: NavPathStack = new NavPathStack();
// 通过数组实现自定义栈的管理
static routerStack: Array<RouterInfo> = new Array();
static referrer: string[] = [];
public static init(config: RouterConfig, context: Context) {
DynamicRouter.config = config;
DynamicRouter.routerStack.push(HOME_PAGE)
RouterLoader.load(config.mapPath, DynamicRouter.routerMap, context)
}
//...
}
路由表存放在src/main/resources/rawfile目录中,通过ResourceManager进行读取
export namespace RouterLoader {
export function load(dir: string, routerMap: Map<string, RouterInfo>, context: Context) {
const rm: resourceManager.ResourceManager = context.resourceManager;
try {
rm.getRawFileList(dir)
.then((value: Array<string>) => {
let decoder: util.TextDecoder = util.TextDecoder.create('utf-8', {
fatal: false, ignoreBOM: true
})
value.forEach(fileName => {
let fileBytes: Uint8Array = rm.getRawFileContentSync(`${dir}/${fileName}`)
let retStr = decoder.decodeWithStream(fileBytes)
let routerMapModel: RouterMapModel = JSON.parse(retStr) as RouterMapModel
loadRouterMap(routerMapModel, routerMap)
})
})
.catch((error: BusinessError) => {
//...
});
} catch (error) {
//...
}
}
}
根据URI跳转页面时,通过动态import并执行路由表中定义的registerFunction方法来实现动态注册组件
Button("跳转")
.onClick(()=>{
DynamicRouter.pushUri("app://settings")
})
export class DynamicRouter {
//...
public static pushUri(uri: string, param?: Object, onPop?: (data: PopInfo) => void): void {
if (!DynamicRouter.routerMap.has(uri)) {
return;
}
let routerInfo: RouterInfo = DynamicRouter.routerMap.get(uri)!;
if (!DynamicRouter.builderMap.has(uri)) {
// 动态加载模块
import(`${DynamicRouter.config.libPrefix}/${routerInfo.pageModule}`)
.then((module: ESObject) => {
module[routerInfo.registerFunction!](routerInfo) // 进行组件注册,实际执行了下文中的LoginViewRegister方法
DynamicRouter.n