乐趣区

关于前端架构:探索微前端的场景极限

本文次要介绍总结了一些基于 qiankun 的微前端利用场景与实际

根底场景

与路由绑定的形式渲染微利用

通常状况下,咱们接触的最多的微前端的实际,是以 URL/ 路由 为维度来划分咱们的微利用,以 OneX 平台(蚂蚁金融云基于微前端架构打造的对立接入平台)为例:

接入这类平台的微利用,通常只须要提供本人的 entry html 地址,并为其调配一个路由规定即可。

这背地的实现则是基于 qiankun 的 registerMicroApps API,如:

import {registerMicroApps} from 'qiankun';

registerMicroApps([
  {
    name: 'app1', 
    container: '#container', 
    entry: '//micro-app.alipay.com/', 
    activeRule: '/app1' 
  }
])

路由与利用绑定的形式简略直观,是微前端中最为常见的应用场景,通常咱们会用这种形式将一堆独立域名拜访的 MPA 利用,整合成一个一体化的 SPA 利用。

但这类场景也有本人的局限性:

  1. 因为 URL/ 路由的 唯一性 / 排他性 的特点,这种形式只实用单实例场景需要
  2. 微利用的调度都是由路由零碎来主动解决的,尽管省事然而碰到更简单的需要,如同一个路由下,依据不同的用户权限展现不同的微利用这类个性化诉求,须要写一些中间层代码来曲线救国
  3. 利用挂载的容器节点等需提前准备好,不然碰到 动静 / 嵌套 路由这类状况,可能会因为路由 listener 监听触发的时序不确定,导致微利用无奈实现挂载的问题

以组件的形式应用微利用

qiankun 2.0 的公布带来一个全新的 API loadMicroApp,通过这个 API 咱们能够本人去管制一个微利用加载 / 卸载,这个形式也是 qiankun 2.0 的重磅个性:

import {loadMicroApp} from 'qiankun';

// do something

const container = document.createElement('div');
const microApp = loadMicroApp({name: 'app', container, entry: '//micro-app.alipay.com'});

// do something and then unmount app
microApp.unmout();

// do something and then remount app
microApp.mount();

开发者能够在脱离路由的限度下,以更自在的形式去渲染咱们的微利用。基于 loadMicroApp API,咱们只须要做一些简略的封装,即能够相似组件的开发体验,实现微利用的接入,以 React 为例:

第一步:封装一个 MicroApp 组件:

import {loadMicroApp} from 'qiankun';
import React from 'react';

export default class MicroApp extends React.Component {containerRef = React.createRef();
  microApp = null;

  componentDidMount() {const { name, entry, ...props} = this.props;
    this.microApp = loadMicroApp({ name, entry, container: this.containerRef.current, props},
    );
  }

  componentWillUnmount() {this.microApp.unmount();
  }

  componentDidUpdate() {const { name, entry, ...props} = this.props;
    this.microApp.update(props);
  }

  render() {return <div ref={this.containerRef}></div>;
  }
}

第二步:通过 MicroApp 组件引入微利用:

import MicroApp from './MicroApp';
import React from 'react';

class App extends React.Component {render() {
    return (
      {
        this.props.admin
          ? <MicroApp name="admin" entry="//localhost:8080/" level={10} />
          : <MicroApp name="guest" entry="//localhost:8081/" level={1} />
      }
    )
  }
}

如果你是 umi 利用,那只须要间接应用插件封装好的组件即可:

import {MicroApp} from 'umi';

function MyComponent() {
  return (
    <div>
      <MicroApp name="qiankun" age={1.5} stars={8700} />
    </div>
  )
}

这类形式实用于一些可共用的、带业务逻辑的服务型组件(相似于咱们以前常说的端对端组件):比方带聊天交互的客服机器人、带疏导性能的 intro 服务等。

如蚂蚁 sofa 产品控制台中的这个应用入门微利用:

右侧呼出的窗口即为一个独立开发、独立公布的微利用

通过组件的这种形式,咱们能够齐全自主的管制微利用的渲染,并与之做一些简单的交互。不论是在开发者的编码心智,还是用户的体验上,都跟应用一个一般的业务组件无异。

组件的形式非常灵活,简直解决了所有路由绑定形式渲染微利用的问题,但也有本人的一些局限:比方咱们会要求这类微利用必须是不领路由零碎的 widget 类型,不然也会呈现多实例时路由抵触的问题。

嵌套渲染场景

有一些更简单的场景中,咱们可能须要应用到「套娃」的形式集成咱们的若干微利用。

比方咱们要在利用 A 下集成利用 B 的商品列表页,而后在利用 B 的商品列表页呼出利用 C 的买家详情页,所有利用的唤起都以 弹层 / 抽屉 这种不刷新的交互来实现。

在有了下面两个场景的实践经验后,咱们很容易得出这样的组合逻辑:

在利用 A 中通过调用 loadMicroApp(B) 的形式唤起微利用 B,而后在微利用 B 中通过 loadMicroApp(C) 的形式唤起微利用 C,通过这样的调用链路即可很完满的实现产品上的诉求。

然而现实情况往往没有那么简略,后面提到过,若想要 loadMicroApp API 能合乎预期的运行,咱们须要确保被加载的微利用是不含本人的路由零碎,否则会呈现多个利用间路由零碎相互 抢占 / 抵触 的状况。

而现实情况是,咱们大部分须要复用的页面 / 组件,都会是某个站点的部分路由页,很少有人会专门起一个仓库,用来专门把这个页面抽取成一个微利用,比方下面提到的买家详情页。

这种场景下,咱们其实只须要确保微利用的路由零碎不会烦扰到全局的 URL 零碎即可。侥幸的是 react-router 的 memory history 模式很好的解决了这一问题。如果你是一个 umi 利用,只须要间接应用咱们封装好的组件即可实现 memory history 的运行时切换:

import {MicroAppWithMemoHistory} from 'umi';

<Drawer>
  <MicroAppWithMemoHistory name="buyer" url='/buyers/123' />
</Drawer>

交互成果能够参考:

图中 app1/app2 均是子利用,然而在 app1 中能够再通过抽屉呼出 app2,同时浏览器地址栏也不会被 app2 的路由烦扰。

对于嵌套渲染相干的具体介绍,能够看这篇《基于微前端的大型中台我的项目交融计划》

极限渲染场景

如果你感觉嵌套微利用就是咱们场景的天花板了,那未免有点小看大众们的想象力了。

在咱们外部的一个设计工程化平台里,咱们通过 qiankun 胜利的把一个微利用的 20+ 路由页同时渲染到了一个 url 下:

上图中右侧列表中每一个 demo 都是通过 qiankun 渲染的一个独立微利用实例,而这外面每一个微利用实例,理论对应是同一个 react 利用的不同路由页。

不同于后面几个场景,将同一个利用的不同页面,同时渲染到主利用的不同 UI 容器中这个需要下,有几个比拟非凡的问题须要去思考:

  1. 是否须要非凡的微利用生产方式
  2. 多路由零碎共存带来的 抵触 / 抢占 问题
  3. 不同微利用间的款式隔离
  4. 如何优化渲染性能:既然每一个微利用实例理论对应的是同一个利用,那咱们如何尽可能多的复用一些运行时或者沙箱,从而升高这么多微利用同时渲染代理的运行时开销

篇幅思考,针对这类场景优化的技术细节不在这里介绍了,前面会独自写一篇来介绍

解决了这些问题后,咱们只须要在咱们的我的项目中这么去应用就能够了:

// MicroAppWithMemoHistory 是基于 memory history 封装的微利用加载器
import {MicroAppWithMemoHistory} from 'umi';

function Home {
  return (
    <div>
      {/* 将同一个利用的不同路由页同时渲染进去 */}
      <MicroAppWithMemoHistory name="demo" url="/demo1" />
      <MicroAppWithMemoHistory name="demo" url="/demo2" />
      <MicroAppWithMemoHistory name="demo" url="/demo3" />
      <MicroAppWithMemoHistory name="demo" url="/demo4" />
      <MicroAppWithMemoHistory name="demo" url="/demo5" />
      <MicroAppWithMemoHistory name="demo" url="/demo6" />
    </div>
  )
}

更多的设想空间

工程上的设想空间

微前端架构除了其带来的巨石利用解构、技术栈无关等工程能力外,也为咱们对一些已有的工程问题带来了新的解题思路,比方:

npm 包散发业务组件背地的工程问题

在以前,咱们常常通过公布 npm 包的形式复用 / 共享咱们的业务组件,但这种形式存在几个显著的问题:

  1. npm 包的更新下发须要依赖产品重新部署才会失效
  2. 工夫一长就容易呈现依赖产品版本割裂导致的体验不统一
  3. 无奈灰度
  4. 技术栈耦合

说白了就是 npm 包这种动态的共享形式,丢失了动静下发代码的能力,导致了其过慢的工程响应速度,这在当初云服务风行的时代就会显得分外刺眼。而微前端这种纯动静的服务依赖形式,恰好能不便的解决下面的问题:被依赖的微利用更新后的产物,在产品刷新后即可动静的获取到,如果咱们在微利用加载器中再辅以灰度逻辑,那么动静更新带来的变更危险也能失去无效的管制。

新的 UI 共享模式

在以前,如果咱们心愿复用一个站点的部分 UI,简直只有这样一条门路:从业务零碎中抽取出一个组件 -> 公布 npm 包 -> 调用方应用 npm 包。

且不说后面提到的 npm 本身的问题,单单是从一个已有的业务零碎中抽取出一个 UI 组件这个事件,都可可能咱们喝一壶的了。咱们不仅要在物理层面,将这部分代码抽取成若干个独自的文件,同时还要思考如何跟已有的零碎做上下文解耦,这类工作呈现在一个越是年代久远的我的项目上,施行起来就越是艰难,做过这类事件的同学应该都会深有体会。

不同于组件库的研发流程,微前端的场景下,大部分时候咱们不会为了去复用一个 UI,而去专门写一个微利用进去。通常咱们冀望的是,从一个已有零碎中,间接选取咱们须要复用的局部,嵌入到咱们本人的容器里进行渲染。

基于下面提到过的微利用多实例的渲染计划,咱们能够思考将须要复用的组件,以路由 URL 作为 ID 的形式导出。比方咱们有这样一个 A 利用有一个这样的页面:

function OnePage() {
  return (
    <div>
      <SearchForm/>
      <UserList/>
    </div>
  )
}

咱们有另外一个利用,心愿独自复用 A 利用的用户列表局部的交互跟 UI,那咱们只须要多加一条路由规定:

<Switch>
  ...
  <Route path="/userList" memory={true}>
    <UserList/>
  </Route>
</Switch>

依赖方只须要配合 memory history 并指定 url 为 /userList 即可实现渲染(参考下面嵌套渲染章节)。

站点即配置

当咱们将所有能够共享的服务单元变成一个个独立的微利用之后,咱们便能够通过配置的形式形容咱们的站点了,相似:

{
  layout: 'admin-pro',
  apps: [{ name: 'abc', props: {} },
    {name: 'bcd', props: {} },
    {name: 'cde', props: {} },
  ]
}

其中 layout 能够是一个带根底交互框架、用户鉴权等公共能力的微利用,也能够是一组微利用的汇合(相似 babel 中的 preset 插件),而 apps 则是一组须要在以后站点渲染进去的业务微利用。

这种形式十分实用于,当咱们要在一个业务域下开发多个细分服务站点时,通过这种配置的形式,配合一个运行时的微利用编排引擎,疾速的生成一系列视觉统一、交互对立的业务站点进去。

产品上的设想空间

在有了下面那些场景背地的技术撑持后,在产品上,咱们就曾经多出很多设想空间了。

但咱们还能够想的更极限一点:

比方咱们晓得微信有一个公众号浮窗的性能,包含安卓零碎常见的小窗模式,解决的就是空间独占、以及跨空间时的交互问题。

那咱们在中后盾也能够参考相似的设计,将不同空间的关联性操作以这种非独占的状态聚合到一起,从而升高流程上的断层感,晋升产品体验。

demo.jpg

(这里本有一张蚂蚁外部零碎的交互示意图,因波及窃密信息无奈公开,有趣味的同学能够抉择退出咱们后做进一步的交换分享????)

写在最初

微前端提供的这些渐进式更新、动静组合、服务拓展的能力,置信大家通过咱们介绍的这些常见场景,以及一些极致条件下的解决方案中能窥其一二。

但须要强调的是,任何技术架构都不可能是银弹,咱们不用对微前端过于神化 / 劣化。本篇仅心愿在分享了咱们在微前端畛域的一系列摸索之后,能给其余开发者带来一些新的抉择和启发,从而为其工程及产品上带来更多的可能性。

最初的最初,诚招天下英雄

如果您对微前端感兴趣,请发简历到 youzhi.lk@antfin.com,咱们十分冀望有机会能与您共事,摸索出微前端下更多的场景可能性。

如果您对微前端没什么趣味,不要紧,只须要您对 antd、AntV、dva、umi、eggjs、ahooks 中任一类别的畛域感兴趣,您也能够发简历到 youzhi.lk@antfin.com 来与咱们共商小事。

团队介绍

  • 大部门:蚂蚁团体体验技术部
  • 部门主管:玉伯
  • 部门开源作品:antd、AntV、dva、umi、eggjs、qiankun、ahooks 等
  • 部门商业化产品:语雀
  • 直属部门:平台前端技术部 – 部门主管:偏右(antd 负责人),部门介绍:https://www.yuque.com/afx/abo…
  • 要求:根本没要求,p5~p8 咱们都须要,只有您想来试试就能够投简历到 youzhi.lk@antfin.com,HC 十分多!!
  • base 地:杭州、上海、成都 三地任选
退出移动版