随着时代的发展,大型应用越来越多,对于维护成本也会逐渐提高,如果一整个项目的代码都在一起的话,维护的局限性会特别强
这些年,我们把大部分的目光都在用户体验上面,如何把代码写的更好,性能更快,用户体验更流畅,在做这些事情的同时,随着日复一日的项目迭代,加上业务的复杂性,我们需要花在除了正常业务开发上的时间会越来越多
然后在这种条件下,渐渐的目光转向了如何面对当前市场越来约复杂的web应用上做整体的结构优化,子应用的切分也是要保证对用户而言,他是一个完整的应用,一个国外对微前端研究较深的web开发人员认为:
一种架构风格,其中独立交付的前端应用程序被组合成一个更大的整体
其实针对 微前端 每个人都有不同的看法,我没有按照一些特定的技术方法和实现原理来告诉大家 微前端就是什么 、什么就是微前端,只是以目前的这种技术方案和市场的需求来描述这么一种应用而生的技术而已
微前端 的好处和坏处都是针对目前的一些市场反馈来总结的,不同的产品需求,针对 微前端 的反馈也是各自不同的,下面我只是总结了我已知的一些优点和缺点,剩下的还需要在未来使用微前端 的路上大家一起探索
# 增量升级(影响范围小)
一个好的产品,会在每个阶段都会进行更新迭代,以满足更多用户的需求,保证吸引更多的用户,产品的完善,版本的迭代,给开发带来的主要工作就是代码量的增加,某一个功能点可能需要对一部分的代码进行不断的更新:
# 版本一
产品初始化,我们有一个选项卡功能,需要通过数据确定有哪些tab,对应的tab显示对应的内容,这个时期,最火的框架是 jquery ,没有react 和vue,实现的过程如下:
<div id="tab-container">
<div id="tab-btn"></div>
<div id="tab-content"></div>
</div>
$ajax({
url: '***',
success: function() {
// 这里就是获取到所有的按钮,绑定事件,点击相应的 tab 再次请求显示相应内容
// 不写太多逻辑,明白意思即可
}
})
这里实现了一个初始化项目开发,满足了该产品一开始的需求
# 版本二
产品维护了一段时间,用户数也在不断的上涨,发现目前的这个功能不满足用户的需求了,我们原先的头部tab 按钮保持不变,但是展示的内容变了,可能是一段文字,可能是一张图片,也有可能是一个媒体资源,而且页面不止有tab,还有很多的内容都是基于某一个状态来做动态更新的,为了保证内容的可扩展性,我们做了抽离,这个时候,有了react 和vue 框架:
// app.js
import React, { Component } from 'react'
impot TabHoc from 'TabHoc.js'
import Content1 from 'Content1.js'
import Content2 from 'Content2.js'
const HocContent1 = TabHoc(Content1, (props, method) => {
return method(props.status)
})
const HocContent2 = TabHoc(Content2, (props, method) => {
return method(props.status)
})
export default class App extends Component {
constructor() {
this.state = {
status: '1'
}
}
componentDidMount() {
}
render() {
<>
<HocContent1 status={this.state.status} />
<HocContent2 status={this.state.status} />
<... />
</>
}
}
// TabHoc.js
import React, { Component } from 'react'
import { Tabs } from 'antd';
const { TabPane } = Tabs;
export default function(Components, callback) {
return class extends Component {
constructor() {
this.updateData = this.updateData.bind(this)
this.updateState = (props = {
tabList: []
}) => callback(
props,
this.updateData
)
this.state = this.updateState()
}
componentDidMount() {
axios({
url: '***'
}).then(({data}) => {
this.updateState({
tableList: data
})
})
}
updateData(obj) {
this.setState(prev => Object.assign({}, prev, obj))
}
render() {
<Tabs defaultActiveKey={tabList[0].tab} onChange={callback}>
{tabList.map((item,index) => {
return (
<TabPane tab={item.tab} key={index}>
<Components {...this.props} data={...this.state} type={item.type} />
</TabPane>
)
})}
</Tabs>
}
}
}
Content1 和 Content2 的代码我没有写,后面的那个callback的方法也是大概一个意思,就是告诉大家不同的情况下,可能会有不同的展示形式,利用高阶组件(HOC)的概念对共享型数据和方法做了封装,保证代码的维护性的同时也提升了整体的性能,要比在jquery时期的渲染速度快很多,虽然功能变多了,但是由于dom的渲染是基于diff算法的,没有变化的dom 是不会进行重新渲染的
当然,版本的变化可能不止这么简单,可能更复杂,更需要不断的去重构、重构、在重构以此来优化代码,每一次的优化重构,都是会导致产品用户使用不流畅风险性的提升,没有谁敢保证某一次的更改不会影响到别的业务
这时候如果使用的是微前端方案的话,完全可以把这里独立出来,哪怕是重构,也不会影响到别的业务;如果重构的成本要比重做的成本还高(框架版本升级,性能优化等),那么重做的话基于微前端,也是一个非常安全的方案
基于微前端做增量升级最大的好处就是不论是对当前的产品进行任何 增、删、改、查 都是最方便的,风险低、效率高
# 支持多框架(灵活)
市面上的前端框架数不胜数,也各自有各自的优点,方便的框架不代表适用于我们现在的业务,如果单纯的考虑开发速度来说,vue 绝对是我认为目前市场上最方便、最快捷的框架了
但是,vue 是我们当前项目里面最好的选择吗?并不是。
上面我也说了,如果单纯是为了考虑开发效率,项目体积小,业务逻辑没有特别沉重,我还是建议使用vue的;但是如果项目比较复杂,业务逻辑依赖性也比较强的话,这个时候我就建议还是选择react 会更好一些;当然,如果你可能只是做一个活动页,宣传页之类的,前两者可能都不需要,毕竟可能只是几个div,几个事件就搞定的东西,没有必要在去搞一套框架,那样还的再去加载框架相关资源,没那个必要
多框架带来的好处很明显,当然凡事都是有两面性,合适的方式去实现合适的功能,但是整体产品来说,加载的资源就会被无限的放大:框架、构建配置、预处理语言编译器等,都是需要考虑的范围
毕竟,用户的体验才是最重要的,其次才是开发效率,但是多框架带来的上述所说的弊端也不是不可以解决,只是需要去衡量一定的方案而已
# 独立部署(安全)
后端有一个叫做server less 的概念,是前端可以借鉴的地方,也是微前端里面一个非常重要的概念
假设我们是一整个项目,没有做项目切片,项目做大以后,我们可能很多人要维护着一个项目,按照 git flow 的方式,基于 master 分支,拉了一个新的分支 dev1 ,开发过程当中,可能有多个版本,比如开发的一半的时候,我们需要有别人加入来开发新的需求,两者业务逻辑不影响,但是有公共的方法可能需要一起使用,如果我们基于 dev1 去拉一个 dev2 ,这样不合理,毕竟没有相同的需求,而且调试过程当中, dev1 的分支可能存在问题,会影响到我们接下来的开发,然而如果从 master 分支上拉一个来开发虽然看似没有问题的,但是这两版需求在合并的时候一旦出现冲突,那么这方面要耽误的时间成本就会很长了,一些复杂的逻辑,我们也不可能时时都在合并代码后做逻辑检测,对于前端或者测试来说,都是一项重复性,而又不愿意面对的问题

按照上图的方法,我们是可以规避掉这种问题的,我们的 dev1 和 dev2 都是独立的服务,维护各自的代码,可以完全避免掉因为某一个需要出现的问题而导致整个项目无法进行使用,线上项目出现类似的问题,是一件很恐怖的事情
仔细看的同学这个时候可能会问,那么独立的服务代表我们要做独立的代码分布,虽然可以通过 copy 实现功能的使用,但是这样打包的时候加载当前资源就会出现多余的内容呀?
当然,这样肯定是会出现的,我们做微前端的目的就是为了能保证用户的体验和稳定性、开发人员的维护成本和开发成本,这样的代码出现是不利于我们维护的,下面,就会讲解怎么解决类似的问题
# 共享组件库(便捷)
上述情况中就存在一个问题,代码重复性,其实平时我们有用到类似的方法,或者说见过类似的一些功能就可以解决这种问题:按需加载。
通过按需加载的方式,把公共的一些功能或者组件通过打包的方式,来实现局部加载,这样就可以解决上面所说的问题,例如 antd 中,我们 import { Button } from 'antd' , 在通过 babel-plugin-import 插件,基于 AST 把代码独立打包出来使用
antd 为我们提供了 ui组件, lodash 为我们提供了数据处理的方法,但是肯定是不难满足我们的业务需求的,但是可以借鉴此类方法
babel-plugin-import 的实现原理和 AST 相关的我就不在该文当中做详细解释了,百度一下都有
# 团队自治(沟通成本小)
一个完整的产品团队,实现产品需求只是其中的一部分,在需求的的路上,还有很多需要我们解决的问题,团队协作就是一个非常重要的点,而这个点可以直接的影响到我们产品的实用性及发展速度
假设现在我们有 a 和 b 两个功能需要开发,产品和开发都是一个团队,当业务需求特别多的时候,尤其是敏捷开发的阶段,人员的调整、需求的评审,各方各面的原因都可能导致项目有存在不难按时实现需求的情况,而且这种情况不只是靠加班解决的
一个产品不应该是按照技术团队去做划分的,而是应该按照产品的功能去做划分,在微前端的方案中,不论是 a功能 还是 b功能,他们都是一个独立的团队,a团队的产品和开发去做 a功能;b团队也是如此,这样可以把当前的产品线给无限的向上发展,保证了开发的速度,也保证了因为功能沟通而浪费的时间成本

# 微前端的缺点
凡事都有两面性,没有任何一个技术方案是完美的,在解决某些问题的情况下,总会有不同的弊端出现,我们要做的就是在不同的方案面前,去衡量哪一种方案是解决我们当前产品最好的办法
# 有效的负载变大
微前端 的概念,使我们的每一个功能,成为了一个独立的 应用 ,这样会导致我们的依赖项出现大量的重复,对于用户来说,是需要做多次无意义的资源下载,这样其实是不友好的,但是针对该问题,目前有一种方案就是重复的依赖构建独立的资源包进行下载,这样好像是可以避免掉重复性资源的加载问题,但随之而来的两个不可避免的问题就出现了:
- 在
a页面可能不需要b页面的相应资源,但是c页面需要,我们又不能针对这种依赖关系做详细的依赖切分,这样其实对于我们整体的工作而言,是一个很重的任务量,每一个项目都不止一个依赖包,我们不可能花很多的时间去细分每一个依赖包针对当前功能的依赖关系,这是一个非常沉重的工作量 a页面可能使用的是react 16.7之前的版本,ui框架antd使用的也是3.x的,这里面是没有hooks的;b页面使用的是react 16.7之后的版本,这时候支持hooks,但是antd还没有做更新,只能只用3.x版本;c页面使用的也是react 16.7以后的版本,但是antd更新4.x,它支持hooks;这里可能会有人问,为什么不直接使用react 16.7版本,antd 4.x不就行了,何必这么复杂?我们的需求不是一下子完成的,框架也是需要不断的优化实现不管开发效率还是用户体验方面的提升的,版本的控制就是我们需要面对的最大的一个问题,我们不能在同一个公共依赖包里面去做所有版本的兼容问题,这是个不现实的事情
如此来看,好像独立的资源加载好似是最好的办法,可以避免这样的问题,但是有效的负载变大存在的问题还是没有解决,其实独立的资源加载并不是错的,但是它也不是对的,只是我们一直在这个对和错之间,寻找一个边界,其实对和错是不难区分的,难区分的就是它们之间的这个边界问题,如果区分对错的边界,需要耗费我们区分对错更多的成本,那么其实当前的状态,可能就是最好的
当然不是鼓励大家不去更精细化的区分对和错的更深层次的边界,一个中小型企业,时间成本很重要,这种边界的探测对于公司的发展来说起不到什么比较有价值的作用,反而会耽误整体的发展;反之如果是一个专门研究这方面技术的,技术协会或者大型公司的技术团体,我还是很鼓励大家做更深层次的研究的,毕竟物尽其用,人尽其才
# 管理的复杂性
这一块其实内容就很好说明了,管理的复杂性指的很多方面,业务方面、和开发方面等:
- 业务方面的划分,虽然说做到了更多的细节的把控,人员的目光和方向会更精确一点,但是带来的问题就是,业务的广泛性带来的成本也会很高,老人对业务扩展的创新性是否真的能直达用户内心、新人对业务方向的熟悉性是否能符合整个产品的发展方向,这些都是在不断的业务细分的场景下会出现的问题,有的时候细不一定是好,虽然说这样可以考虑到很多的点,但是很容易因为业务的细分导致大家的目光只盯在一个点上,这样其实对于一整个产品的发展并不友好,因为有可能产品和开发讨论了很久的一个对与错的细节问题,在用户看来,并不重要,尤其是对于业务在快速增长阶段的产品来说,这更是一个噩耗
- 开发方面的划分,就是一个很明显的、繁琐的一个过程,微前端带给我们的好处上面已经说的很清楚了,但是根据每一个微应用去做划分的同时,带来的就是一个需要合理规划维护方式的技术难题:
- 代码库的数量增加,是否有更好的办法去维护每一个版本的更新(CI是一个目前来说可以解决该问题毕竟)
- 每一个微应用,可能都是一个单独的仓库,那么一整个团队应该如何管理这些微应用,是否需要一定的规范性去约束每一个项目,还是天马行空的发挥各自的想象
- 假设有相同的功能点,我们有一套独立的微应用组件,那么这个开发工作应该如何划分,对于组件 api 的支持应该如何去制定
# 结束语
其实对于 微前端 的优点和缺点还有很多,包括细节的一些点,在我们以后不断工作中去尝试,还会发现很多的问题,对于 微前端 的介绍,我也很少用那种很官方,很专业的术语去解释它到底是什么样子的,我更喜欢通过这种大白话、举例的方式去给大家讲解到底 微前端 对于我们日常工作来说,到底起到了什么作用、项目当中该不该用、存在的问题我们当前的项目是否真的可以不考虑
前面对于 微前端 发展及概念的介绍差不多就到此为止了,有什么疏忽的点还希望大家评论补充
下面的重心就会放在基于 single-spa 如何去搭建微应用并部署到容器中的一整套实现方案,并包括其中的 api 介绍,single-spa 官方的 api 我和一些身边的朋友都感觉可读性还是很差的,所以我准备自己好好梳理一下,方便大家阅读
← 学习微前端前的知识储备 微前端的工具介绍 →