可以看到两个项目除了业务之外,几乎没什么交集了。
微前端策略
老项目作为主应用,通过 qiankun 去加载新项目(子应用)里的页面。
-
当没有需求时,在新项目(子应用)重写页面,重写完了之后,在老项目(主应用)中加载新项目的页面,下掉老项目的页面
-
当有需求时,也是在新项目(子应用)重写面面再做对应需求(向产品要多点时间),重写完了之后,在老项目(主应用)中加载新项目的页面
这样一来就可以避免 “我要一整个月都做重构” 的局面,而是可以做到一个页面一个页地慢慢迁移。最终等所有页面都在新项目写好之后,直接把老项目下掉,新项目就可以从幕后站出来了。相当于从重写的第一天开始,老项目就成替身了。
如果只看上面画的架构图,会觉得:啊,不就引入一个 qiankun 就完事了么?实际上还有很细节和问题需要注意的。
升级版架构
上图的架构有一个问题就是,当每次点击侧边栏的 MenuItem
时,都会加载一次微应用的子页面,也即:
微应用子页面之间的切换,其实就是在微应用里路由切换嘛,大可不需要通过重新加载一次微应用来做微应用子页面的切换。
所以,我想了一个办法:我在 <router-view>
旁边放了一个组件 Container
。进入主应用后,这个组件先直接把微应用整个都加载了。
当展示老页面时,把这个 Container
高度设为 0
,要展示新页面时,再把 Container
高度自动撑开。
// micro-app-container
这样一来,当进入老项目时,这个 Container
自动被 mounted 后就会地去加载子应用了。当在切换新页面时,本质上是在子应用里做路由切换,而不是从 A 应用切换到 B 应用了。
子应用的布局
由于新的项目(子应用)里的页面要供给老项目(主应用)来使用的,所以子应用也应该有两套布局:
第一套标准的管理后台布局,有 Sider
,Header
还有 Content
,另一套侧作为子应用时,只展示 Content
部分的布局。
// 单独运行时的布局
export const StandaloneLayout: FC = () => {
return (
)
}
// 作为子应用时的布局
export const MicroAppLayout = () => {
return (
)
}
单独运行时的布局
作为微应用时的布局
最后通过 window.__POWERED_BY_QIANKUN__
就可以切换不同的布局了。
import { StandaloneLayout, MicroAppLayout } from “./components/Layout”;
const Layout = window.POWERED_BY_QIANKUN ? MicroAppLayout : StandaloneLayout;
function App() {
return (
);
}
样式冲突
qiankun 是默认开启 JS 隔离(沙箱),关闭 CSS 样式隔离的。为什么这么做呢?因为 CSS 的隔离是不能无脑做去做的,下面来讲讲这方面的问题。
qiankun 一共提供了两种 CSS 隔离方法(沙箱):严格沙箱 以及 实验性沙箱。
严格沙箱
开启代码:
start({
sandbox: {
strictStyleIsolation: true,
}
})
严格沙箱主要通过 ShadowDOM
来实现 CSS 样式隔离,效果是当子应用被挂在到 ShadowDOM
上,主子应用的样式 完完全全 地被隔离,无法互相影响。你说:这不是很好么?No No No。
这种沙箱的优点也成为了它自己的缺点:除了样式的硬隔离,DOM 元素也直接硬隔离了,导致子应用的一些 Modal
、Popover
、Drawer
组件会因为找不到主应用的 body
而丢失,甚至跑到整个屏幕之外。
还记得我刚说主应用和子应用都用了 ant-design 么?ant-design 的 Modal
、Popover``Drawer
的实现方式就是要挂在到 document.body
上的,这么一隔离,它们一挂在整个元素起飞了。
实验性沙箱
开启代码:
start({
sandbox: {
experimentalStyleIsolation: true,
}
})
这种沙箱实现方式就是给子应用的样式加后缀标签,有点像 Vue 里的 scoped
,通过名字来做样式 “软隔离”,比如像这样:
其实这种方式已经很好地做了样式隔离,但是主应用里经常有人喜欢写 !important
来覆盖 ant-design 的组件原样式:
.ant-xxx {
color: white: !important;
}
而 !importnant
的优先级是最高的,如果微应用也用了这个 .ant-xxx
类,就很容易被主应用的样式影响了。所以在加载微应用时,还需要处理 ant-design 之间的样式冲突问题。
ant-design 样式冲突
ant-design 提供了一个非常好的类名前缀功能:用 prefixCls
来做样式隔离,我自然也用上了:
// 自定义前缀
const prefixCls = ‘cmsAnt’;
// 设置 Modal、Message、Notification rootPrefixCls
ConfigProvider.config({
prefixCls,
})
// 渲染
function render(props: any) {
const { container, state, commit, dispatch } = props;
const value = { state, commit, dispatch };
const root = (
<MicroAppContext.Provider value={value}>
</MicroAppContext.Provider>
);
ReactDOM.render(root, container
? container.querySelector(‘#root’)
: document.querySelector(‘#root’));
}
@ant-prefix: cmsAnt; // 引入来改变全局变量值
但是不知道为什么,在 less 文件中改了 ant-prefix
变量后,ant-design-pro 的样式还是老样子,有的组件样式改变了,有的没变化。
最后,我是通过 less-loader
的 modifyVars
在打包时来更新全局的 ant-prefix
less 变量才搞定的:
var webpackConfig = {
test: /.(less)$/,
use: [
…
{
loader: ‘less-loader’,
options: {
lessOptions: {
modifyVars: {
‘ant-prefix’: ‘cmsAnt’
},
sourceMap: true,
javascriptEnabled: true,
}
}
}
]
}
具体 Issue 看 Issue: ant-design 改了 prefixCls 后 ant-design-pro 不生效。
主子应用状态管理
老项目(主应用)用到了 vuex 全局状态管理,所以新项目页面(子应用)里有时需要更改主应用里的状态,这里我用了 qiankun 的 globalState 来处理。
首先在 Container 里创建了 globalActions
,再监听 vuex 状态变更,每次变更都通知子应用,同时把 vuex 的 commit
和 dispatch
函数传给子应用:
import {initGlobalState, registerMicroApps, start} from ‘qiankun’
const globalActions = initGlobalState({
state: {},
commit: null,
dispatch: null,
});
export default {
name: “Container”,
props: {
visible: {
type: Boolean,
defaultValue: false,
}
},
mounted() {
const { dispatch, commit, state } = this.$store;
registerMicroApps([
{
name: ‘microReactApp’,
entry: ‘//localhost:3000’,
container: ‘#micro-app-container’,
activeRule: ‘/#/micro-react-app’,
// 初始化时就传入主应用的状态和 commit, dispatch
props: {
state,
dispatch,
commit,
}
},
])
start()
// vuex 的 store 变更后再次传入主应用的状态和 commit, dispatch
this.$store.watch((state) => {
console.log(‘state’, state);
globalActions.setGlobalState({
state,
commit,
dispatch
});
})
},
}
子应用里接收主应用传来的 state
,commit
以及 dispatch
函数,同时新起一个 Context,把这些东西都放到 MicroAppContext
里。(Redux 因为不支持存放函数这种 nonserializable 的值,所以只能先存到 Context 里)
// 渲染
function render(props: any) {
const { container, state, commit, dispatch } = props;
const value = { state, commit, dispatch };
const root = (
<MicroAppContext.Provider value={value}>
</MicroAppContext.Provider>
);
ReactDOM.render(root, container
? container.querySelector(‘#root’)
: document.querySelector(‘#root’));
}
// mount 时监听 globalState,只要一改再次渲染 App
export async function mount(props: any) {
console.log(‘[micro-react-app] mount’, props);
props.onGlobalStateChange((state: any) => {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
资料过多,篇幅有限,需要文中全部资料可以点击这里获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。
板技术停滞不前!**
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-0y16WlhZ-1712572996988)]
[外链图片转存中…(img-Yvmy7Ons-1712572996989)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-dypGTlhT-1712572996989)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
[外链图片转存中…(img-WZWKiHi7-1712572996989)]
[外链图片转存中…(img-SjmKIUOD-1712572996989)]
资料过多,篇幅有限,需要文中全部资料可以点击这里获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。