在比较复杂的前端应用中,微前端的应用场景也会相对比较复杂一点。比如tab页或者,同一个布局下的多视图切换。这里,我们还是以我们的工程为例。布局如下

我们的需求
- 主应用跳转子应用
- 子应用跳转主应用
- 子应用跳转子应用
wujie现有的功能
根据官网的资料,wujie本身提供了路由同步和路由跳转的功能
由文档可知
只有无界实例在初次实例化的时候才会从url上读回路由信息,一旦实例化完成后续只会单向的将子应用路由同步到主应用url上
子应用在初始化的时候,才会去同步主应用路由上的信息,而初始化之后,主应用的路由信息,就对子应用没有影响了,这个时候,如果要改变子应用的路由,只能通过wujie的通信系统来改变活跃应用内的路由
解决方案
这里,我需要考虑子应用保活与不保活两种情况 ,以WujieReact为例
主应用跳转子应用
主应用跳转子应用,一个是主应用的路由,一个是设置子应用的路由,这里主要考虑子应用激活和未激活两种情况,如果是已激活,则无法控制子应用的路由,只能打开子应用,如果是未激活的情况,可以直接按照wujie的路由同步规则,打开子应用的对应路由页面。这里需同时通过路由同步和系统通信两种方式来兼容。
子应用跳转主应用
子应用跳转主应用的时候,子应用是没法控制主应用的路由的,这个时候可以借助wujie的props ,在主应用里设置一个回调函数,来控制主应用的路由。这里相对简单,主应的路由不需要同步。
子应用跳转子应用
子应用跳转子应用,应该是几个场景里最复杂的。例如子应用A 要跳转子应用B的页面,子应用A,先要把相关跳转的目标信息通过wujie的props中的回调函数发给主应用,主应用,再根据相关信息,跳转到子应用B ,并通过wujie系统通信控制子应用B的页面路由。
代码实现
主应用
这里我们使用保活模式,并开启路由同步
const degrade = window.localStorage.getItem("degrade") === "true" || !window.Proxy || !window.CustomElementRegistry;
const WuJieView = () => {
const {state} = useLocation();
const navigate = useNavigate()
const {identifier} = useParams()
const [activeApp, setActiveApp] = useState<any>()
const [apps, setApps] = useState<{ key: string, name: string, url: string, route?: string }[]>([])
const [appMiss, setAppMiss] = useState(false)
const handleAddApp = (app: any) => {
const appSnap = _.find(apps, (item) => {
return item.name === app.identifier
})
if (appSnap) {
appSnap.key = `${appSnap.name}-${randomString.generate(7)}`
setApps(_.cloneDeep(apps))
} else {
setApps(_.concat(apps, {
key: `${app.identifier}-${randomString.generate(7)}`,
name: app.identifier,
url: app.url,
route: app.route
}))
}
setActiveApp({name: app.identifier, url: app.url})
}
/**
* 回调
*/
const handleNavigate = ({app, url, route}: { app: string, url: string, route?: string }) => {
navigate(url, {state: {route}})
if (app) {
WuJieReact.bus.$emit(`${app}:routeChange`, {route}) //通过wujie系统通信通知子应用变更路由
}
}
useEffect(() => {
if (state && state.url) {
handleAddApp(state)
} else {
if (identifier) {
appstoreApi.queryAppByIdentifier(identifier).then((app: any) => {
handleAddApp(app)
}).catch(async (ex) => {
message.error(ex.message)
})
}
}
}, [state, identifier]);
return <div style={{width: '100%', height: '100%'}}>
{apps.map((appItem) => <div key={appItem.name}
style={{
width: '100%',
height: '100%',
display: (_.isEqual(appItem.name, activeApp.name) ? 'block' : 'none')
}}>
{_.isEqual(appItem.name, activeApp.name) && <WuJieReact
height={"100%"}
width={"100%"}
name={appItem.name}
url={appItem.url}
sync={true}
alive={true}
degrade={degrade}
props={{'route': appItem?.route, 'navigate': handleNavigate, "DubheToken": getTenantUserToken()}} //此处通过navigate设置回调
activated={() => {
}}
afterMount={(args) => {
}}
afterUnmount={(args) => {
}}
/>}
</div>)}
{appMiss && <div>应用不存在</div>}
</div>
}
export default WuJieView
如果是非保活模式,则需要在afterUnmount中调用销毁方法,否则再次打开的时候会失败
<WuJieReact
height={"100%"}
width={"100%"}
name={appItem.name}
url={appItem.url}
sync={true}
alive={false}
degrade={degrade}
props={{'route': appItem?.route, 'navigate': handleNavigate, "DubheToken": getTenantUserToken()}}
activated={() => {}
}
afterMount={(args) => { }}
afterUnmount={(args)=>{
WuJieReact.destroyApp(args.name) //销毁应用
}}
/>
在wujie 1.0.20版本下会有如下报错
sandbox.ts:372 Uncaught TypeError: Cannot read properties of null (reading '$clear')

这是因为,在afterUnmount 调用 destroyApp 的时候,函数中的 bus对象为null。 已经提了pr,不过不确定是否合并,如果合并了应该没这样的问题了。 https://github.com/Tencent/wujie/pull/742 如果没合并,或者没解决,可以自己拉源码修改
子应用代码
const wujieProps: any = _.get(window, ['$wujie', 'props'])
/**
* 监听路由变话通知
*/
useEffect(() => {
const wujie: any = _.get(window, '$wujie')
wujie?.bus?.$on(`${name}:routeChange`, ({route}: { route: string }) => {
router?.navigate(route)
})
}, []);
/**
* 初始化的时候,如果主应用带着route过来,则进行跳转
*/
useEffect(() => {
if (wujieProps?.route) {
router?.navigate(wujieProps?.route)
}
}, [wujieProps?.route]);
跳转方式代码
export const navigateTo = ({app, url, route, navigate}: {
app?: string,
url: string,
route?: string,
navigate?: NavigateFunction
}) => {
if (_.get(window, '__POWERED_BY_WUJIE__')) { //如果是wujie子应用,则调用主应用上的回调函数
const props = _.get(window, ['$wujie', 'props'])
if (!props?.navigate) {
console.warn("[useDubheNavigate] missing navigate property")
}
props?.navigate?.({
url: url,
app,
route: route
})
} else { //如果不是wujie子应用,则通过传入的navigate对象进行跳转
navigate?.(url, {state: {route: url}})
}
}
/**
* 拼接地址,根据实际情况处理
*/
export const hrefGenerate = ({to, app, hashRouter, appHashRouter}: {
to?: string
app?: string
hashRouter?: boolean
appHashRouter?: boolean
}) => {
if (app) {
let appHref = _.replace(`${appHashRouter ? '/#/' : '/'}${to}`, '//', '/')
return _.replace(`${hashRouter ? '/#/' : '/'}/App/${app}`, '//', '/') + `?${app}=${encodeURIComponent(appHref)}`
} else {
return _.replace(`${hashRouter ? '/#/' : '/'}${to}`, '//', '/')
}
}
综上
总之,基于wujie的微前端路由跳转,要根据wujie的功能特性,结合自己的业务进行综合处理,以期达到自己适用的解决方案。