美文网首页
大前端通用路由设计原理篇(一)

大前端通用路由设计原理篇(一)

作者: 搬砖的作家 | 来源:发表于2021-03-15 17:42 被阅读0次

本文首发地址:开源实践网:大前端通用路由设计原理篇(一)

一、什么是路由?

在我们整个大前端(包含前端,安卓,iOS)的开发中,经常会遇到『路由』的概念。那么,到底什么是路由?简单来说,路由就是URL到函数的映射。
首先我们来学习三个单词(route,routes,router):
  route:首先它是个单数,译为路由,即我们可以理解为单个路由或者某一个路由;
  routes:它是个复数,表示多个的集合才能为复数;即我们可以理解为多个路由的集合,JS中表示多种不同状态的集合的形式只有数组和对象两种,事实上官方定义routes是一个数组;所以我们记住了,routes表示多个数组的集合;
  router:译为路由器,上面都是路由,这个是路由器,我们可以理解为一个容器包含上述两个或者说它是一个管理者,负责管理上述两个;举个常见的场景的例子:当用户在页面上点击按钮的时候,这个时候router就会去routes中去查找route,就是说路由器会去路由集合中找对应的路由;

二、路由的历史和各端路由简介

简单举例说明,假如我们有一台提供 Web 服务的服务器的网络地址是:10.0.0.1,而该 Web 服务又提供了三个可供用户访问的页面,其页面 URI 分别是:
http://10.0.0.1/
http://10.0.0.1/ about
http://10.0.0.1/ concat
那么其路径就分别是 /,/about,/concat。
当用户使用 http://10.0.0.1/ about 来访问该页面时,Web 服务会接收到这个请求,然后会解析 URL 中的路径 /about,在 Web 服务的程序中,该路径对应着相应的处理逻辑,程序会把请求交给路径所对应的处理逻辑,这样就完成了一次「路由分发」,这个分发就是通过「路由」来完成的。
以前路由都是后台做的,通过用户请求的url导航到具体的html页面,前端路由就是通过配置js文件,把这个工作拿到前端来做。
当时的服务端路由,就是是根据不同的 url 地址展示不同的内容或页面

2.1、前端的路由

随着前端页面越来越复杂,并且伴随着前端的工程化发展,前端SPA(web单页面应用)的使用,较为流行的前端框架将路由也搬到前端中,这里我重点介绍一下vue中的路由
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。
在Vue Router 中,单个route被定义为path和component的映射

const routes = [
 {path:'/page1',component:page1},
    {path:"/page2",component:page2}
]
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <div>
//router-link定义页面中点击触发部分  
      <router-link to="/page1">Page1</router-link>
      <router-link to="/page2">Page2</router-link>
    </div>
//router-view定义页面中显示部分
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'App'
}
</script>

如上图所示,用户输入url,经过Vue Router的路由解析出对应的视图加载(上面代码中component中指定)后渲染,用户在页面中点击<router-link>标签,又通过路由解析加载对应的页面形成一个功能上的闭环
Vue Router就是通过上图所示的流程,将前端的各个功能页面封装为单独的component(也就是视图),然后通过path来跳转指定的功能页面。

2.2、安卓中的路由

提到安卓中的路由,就不得不说大名鼎鼎,出身名门(阿里巴巴开源)的ARouter
ARouter的核心功能:
支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
支持多模块工程使用
支持添加多个拦截器,自定义拦截顺序
支持依赖注入,可单独作为依赖注入框架使用
...等等(略很多吊炸天的功能,具体参考文档https://github.com/alibaba/ARouter/blob/master/README_CN.md
相信安卓的大佬已经很熟悉,这边简单的描述一下流程
1.首先是router的声明

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

2.然后是 router调用

// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .withObject("key4", new Test("Jack", "Rose"))
            .navigation();

3.高级功能 通过依赖注入解耦:服务管理(一) 暴露服务

// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
    String sayHello(String name);
}

// 实现接口
@Route(path = "/yourservicegroupname/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
    return "hello, " + name;
    }
    @Override
    public void init(Context context) {
    }
}

4.高级功能 通过依赖注入解耦:服务管理(二) 发现服务

public class Test {
    @Autowired
    HelloService helloService;

    @Autowired(name = "/yourservicegroupname/hello")
    HelloService helloService2;
    HelloService helloService3;
    HelloService helloService4;

    public Test() {
    ARouter.getInstance().inject(this);
    }

    public void testService() {
    // 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
    // Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
    helloService.sayHello("Vergil");
    helloService2.sayHello("Vergil");
    }
}

综上,安卓中的Aouter是一个功能强大的路由,他通过注解和依赖注入的方式,提供了页面跳转,和调用服务的方法。

2.3 iOS中的路由

iOS中的路由,JLRoutes在Github上面Star最多,下面我就它为例,讲解iOS中的路。
1.JLRoutes是如何做到URL到方法的映射呢 ?
首先,JLRoutes会有一个全局的字典,key是URL,value是对应的block(ps:就是代码片段,类似匿名函数),当打开一个URL时,JLRoutes 就可以遍历这个全局的字典,通过 URL 来执行对应的 block。具体层次图如下

JLRoutes 层次解析图
具体理解:
  1. routeControllersMap 是全局的单例可变字典
  2. 这个字典的 key 值对应一个标识,源码中称之为 scheme,为了不混淆,咱们就叫其为 JLRoutes 对象标识。这个标识对应的value 值为 routesController(JLRoutes类的对象:JLRoutes *routesController)。
  3. JLRoutes的对象(routesController)有很多属性,常用的有两个属性:
  4. NSString *scheme:也就是上面所说的 JLRoutes对象标识,也就是说,此 value 值记录了自己的 key 值。
    NSMutableArray *routes:此数组中存放了JLRRouteDefinition 对象。
  5. JLRRouteDefinition 对象为最终的具体模型,也就是说你注册的跳转逻辑的所有信息,都存在于这个模型中,包括要实施操作的handlerBlock(执行操作的block代码块)、scheme(JLRoutes对象标识)、pattern(模式)、priority(优先级)。

2.JLRoutes的注册

// 全局JLRoutes注册
[[JLRoutes globalRoutes] addRoute:@"取url内容值的标识" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
    return YES;    // 一旦匹配,立即返回 YES
}];
// 自定义命名空间注册
[[JLRoutes routesForScheme:@"第一模块的标识"] addRoute:@"取url内容值的标识" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
    return YES; // 一旦匹配,立即返回 YES
}];
// 定义优先级注册
[[JLRoutes globalRoutes] addRoute:@"取url内容值的标识" priority:1 handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
    return YES; // 一旦匹配,立即返回 YES
}];

3.JLRoutes的调用

- (BOOL)application:(UIApplication *)app openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    NSLog(@"options: %@", options);
    NSLog(@"Calling Application Bundle ID: %@", [options objectForKey:@"UIApplicationOpenURLOptionsSourceApplicationKey"]);
    NSLog(@"URL scheme: %@", [url scheme]);
    NSLog(@"URL  host : %@", [url host]);
    NSLog(@"URL  query: %@", [url query]);
    
    // 从浏览器打开时候会自动全部转成小写,而从应用内调用的话大小写不会变化
    // 为了方便判断所以统一转成小写来判断
    
    NSString *urlSchemeStr = [[url scheme] lowercaseString]; // url scheme 转换为小写的字符串
    NSLog(@"urlSchemeStr: %@",urlSchemeStr);
    if ([urlSchemeStr isEqualToString:@"jlrouteschemeone"]) {
        // 要和 info.plist 的 URL types 里面的一致
        return [[JLRoutes routesForScheme:@"JLRouteSchemeOne"]routeURL:url];
    } else if ([urlSchemeStr isEqualToString:@"jlrouteschemetwo"]) {
        // 要和 info.plist 的 URL types 里面的一致
        return [[JLRoutes routesForScheme:@"JLRouteSchemeTwo"]routeURL:url];
    }
    return YES;
}

4.JLRoutes的跳转

- (void)clickBtn {
    NSString *customURL = @"JLRouteSchemeOne://OneDetailViewController";
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
}
- (void)clickBtn {
    NSString *customURL = @"JLRouteSchemeTwo://TwoDetailViewController";
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
}

到这里,我们已经已经将前端,安卓,iOS上路由实现的具体的原理和对应具体的代码做了一些简单的讲解。可以看到,在前端和安卓平台上,对应的路由组件功能比强大,而在iOS这边,路由功能只剩下了URL到block的映射,所以我这边会从零开始实现一个iOS端的路由组件,来讲解我这次的主题。

三、我们需要路由解决什么问题?

1.页面之间的跳转问题

随着App越做越大,里面的页面越来越多,并且在天朝,有些超级app甚至包含了自己都不能列举完的功能页面,所以但我们在进行页面跳转的时候,会有以下比较难处理的问题。

2 页面之间的耦合问题

在iOS中,点击按钮跳转Push到另外一个界面,或者点击一个cell Present一个新的ViewController。我们的做法,一般都是新建一个VC,然后Push / Present到下一个VC。但我们新建一个ViewController的时候,我们就需要引入该对象,当我们的页面跳转比较复杂的时候,就会出现上图中A,B,C, D互相耦合的情况。那我们如何做到通过一个路由中心,跳转到任意app界面呢?

// ViewControllerA代码
#improt "ViewControllerB"    // 这里对ViewControllerB进行了依赖
...
- (void)btnClick:(UIButton)btn {
    ViewControllerB *bVc = [[ViewControllerB  alloc] init];
    [self.navigationController pushViewController:targetViewController animated:YES];
}
3 页面入口不收敛的问题

当我们使用上面代码跳转方式,整个app中就会存在大量的类似的页面跳转代码,并且业务还会别出心裁的各种风骚的页面跳转,使用不同的方法初始化vc,设置vc不同的属性,当我们改动页面的逻辑时,我们恰巧忘记兼容某个方法,并且恰巧qa没有测到,就这样一个新鲜的bug就这样巧无声息的跑到了线上,然后结果大家懂的都懂,无需我多言。那么,我们如何做到统一页面的跳转逻辑,做到每个页面有一个统一的入口呢?

4 3D-Touch功能或者点击推送消息,要求外部跳转到App内部一个很深层次的一个界面

比如微信的3D-Touch可以直接跳转到“我的二维码”。“我的二维码”界面在我的里面的第三级界面。或者再极端一点,产品需求给了更加变态的需求,要求跳转到App内部第十层的界面,怎么处理?

5 统一安卓和IOS甚至整个大前端的跳转逻辑

项目里面某些模块会混合ReactNative,Weex,H5界面,这些界面还会调用Native的界面,以及Native的组件。那么,如何能统一Web端和Native端请求资源的方式?当我们在手机上浏览h5页面,如何做到每个h5页面都能跳转到app内部相同的功能页面?

6 如果App出现bug了,如何不用JSPatch,就能做到简单的热修复功能?

比如公司App上线突然遇到了紧急bug,能否把页面动态降级成H5,ReactNative,Weex?或者是直接换成一个本地的错误界面?

7 如何解决app组件化后,同层功能组件页面跳转的问题

app组件化,会将app分拆成各个功能模块,比如美团中的外面和电影模块,但是从层级上说,外面和电影模块属于同一层的功能模块,不存在谁依赖谁,所以外卖和电影如果模块化以后,都不能调用对方,但是如果产品提了从外面跳转到电影卖票界面,如何优雅的解决?使用rumtime跳转?

四、使用路由的缺点?如何避免?

缺点1 路由解决了依赖问题,却没有编译检查优势

原因: 路由的最核心的功能点之一就是实现模块之间的依赖解耦,势必是会以track(注:iOS中使用runtime)的方式跳过编译的检查,但是这样就不会有编译上的检查,如果这个调用模块不存在,势必会有异常。
解决思路: 在app的debug模式下,对路由调研的所有方法进行检查,如果没有该模块,使用断言尽早反馈给调用方

缺点2 需要将路由模块进行注册,以便路由识别调用

原因: 路由调用对应的组件,肯定路由需要知道组件,不然无法调用,所以需要注册
解决思路: 在实现上,使用方便使用的宏,或者app启动自动注入的方式,让组件的注册流程对业务透明

缺点3 需要集成路由组件,对业务有侵入

原因: 因为路由组件涉及到页面跳转和方法调用,所以不可避免的需要业务的支持或者改造。
解决思路: 学习java注解的方式,使用宏定义注册方法,尽可能地减少业务的接入成本

五、总结,我们的目标是

在native(包含安卓,iOS)和前端(h5)上实现一套通用的路由逻辑,能够实现跟平台无关的页面调用,将整个大前端的页面彻底统一起来,实现无缝调用

1.实现url和页面的绑定,通过一个url能够调起三端的相同业务
2.实现一个NA端的路由,解决组件化中功能业务层的互相调用问题
3.基于url和页面的绑定,能够做到由服务端动态下发url,支持动态打开页面

相关文章

  • 大前端通用路由设计原理篇(一)

    本文首发地址:开源实践网:大前端通用路由设计原理篇(一)[https://www.redskt.com/pract...

  • 前端路由原理和React Router

    前端路由原理 前端三大框架 Angular、React、Vue ,它们的路由解决方案 angular/router...

  • 设计数据库及通用视图实现api接口

    一、设计数据库 二、通用类视图 三、路由配置 四、postman接口测试

  • 第三十一节:Vue路由:前端路由vs后端路由的了解

    1. 认识前端路由和后端路由 前端路由相对于后端路由而言的, 在理解前端路由之前先对于路由有一个基本的了解 路由:...

  • 前端通用组件设计

    调用组件库的API相信很多人都会用,但是如何封装一个高复用的组件并不是每个人都能做到,而这也是检验一个前端开发人员...

  • hash 和history 的原理和区别

    目前单页应用(SPA)越来越成为前端主流,单页应用一大特点就是使用前端路由,由前端来直接控制路由跳转逻辑,而不再由...

  • react-router-dom

    一、什么是前端路由 在我看来,前端路由和网络上的路由器功能很像。前端路由也是进行分发操作,只不过其分发的是页面跳转...

  • 前端微服务化解决方案5 - 路由分发

    路由分发式微前端 从应用分发路由到路由分发应用 用这句话来解释,微前端的路由,再合适不过来. 路由分发式微前端,即...

  • vue - 路由模式

    1 路由的基本概念与原理 路由的本质就是对应关系; 在开发中, 路由分为前端路由和后端路由. 1.1 前端路由 概...

  • vue基础-路由(重要)

    前端路由的概念与原理 什么是前端路由 Hash 地址与组件之间的映射关系,前端路由可以将hash地址和组件关联起来...

网友评论

      本文标题:大前端通用路由设计原理篇(一)

      本文链接:https://www.haomeiwen.com/subject/xbnmnktx.html