背景

最近我们公司有个”巨石“项目想要改造一下,但是由于项目开始时间久远,经历过无数人的手,乱七八糟的,用的技术栈都很旧了,都没有更新,antd 的版本都是 3.X 的,之前也没有人去升级,继续开发感觉就是在 ”堆垃圾“ 一样,要重构又要去了解很多以前的业务逻辑,产品经理都换了好几个,之前的业务逻辑现在的产品也不是很清楚,不太好重构。所以就萌生了要不用微前端的方法,把新的模块给做到一个新的子应用里,当然也只是在尝试阶段。了解了一些微前端的解决方案以后,内部还是觉得用 qiankunjs 这个解决方案。这里就不比较解决方案之间的优缺点了,就是在做这个微前端的过程中,感觉雷好多,今天就总结一下父子应用间的通信的实现。我还有尝试过 vite,但是配置起来也挺麻烦的,最后还是放弃了。用个 demo 来记录一下这个摸索的过程
技术栈:主应用(Vue3 )+ 子应用(React18) + 子应用(Vue2

代码实现

  • 主应用注册 registerApp.js

这里的 props 非常关键,一定要记得传,如果忘记了,你可以得一层一层往上排了

import { registerMicroApps, start } from 'qiankun'
import actions from './shared'

registerMicroApps([
  {
    name: 'reactApp',
    entry: 'http://localhost:7100/',
    container: '#container',
    activeRule: '/react',
    // 这个 props 很关键,是子应用能不能访问到全局状态的点
    props: { actions },
  },
  {
    name: 'vueApp',
    entry: 'http://localhost:7200/',
    container: '#container',
    activeRule: '/vue',
    props: { actions },
  },
])

start({
  sandbox: {
    // experimentalStyleIsolation: true,
    strictStyleIsolation: true,
  },
})
  • 状态管理 shared/index.js

这里就简单的使用了一个对象作为状态管理,项目上可以使用 vuex 或者 redux 等状态管理工具来出来,我想用最简单的方式来表达,不想牵扯太多其他的技术栈,代码仅为记录摸索 qiankunjs 的过程

import { initGlobalState } from 'qiankun'

const initialState = {
  count: 0,
}
const actions = initGlobalState(initialState)
// 例子里只有 count 一个key,如果对象比较复杂建议使用状态管理工具进行管理
// 这代码看着很呆
actions.getCount = () => initialState.count

actions.onGlobalStateChange(state => {
  // 这里得修改 initialState 我是有点不愿意的
  // 但是没有用状态管理,应该只能这么操作了
  initialState.count = state.count
})

export default actions
  • 子应用(React18index.js

这个 props 要在他的生命周期函数里才有,要记得传,如果忘记传了就啥也拿不到了

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

function render(props) {
  const { container } = props
  const eleDom = container
    ? container.querySelector('#root')
    : document.getElementById('root')
  const root = ReactDOM.createRoot(eleDom)
  // 这里也很重要,一定要通过 props 传给子应用,不然应用里啥也拿不到
  root.render(<App data={{ ...props }} />)
}

export async function bootstrap() {}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  render(props)
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount({ container }) {
  // 这里的 id 得看好了,写错了就老报错,很烦的
  const eleDom = container
    ? container.querySelector('#root')
    : document.getElementById('root')
  // react18的组件卸载方法和以前的有点不一样,如果使用18的小伙伴得注意一下咯
  // ReactDOM.unmountComponentAtNode(eleDom);
  const root = ReactDOM.createRoot(eleDom)
  root.unmount()
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({})
}
  • 修改全局状态 App.js

props 获取到的 count 值是不会触发页面的更新的,所以最好还是重新存到子应用中的 state 中吧

import { useState } from 'react'
import logo from './logo.svg'
import './App.css'

function App({ data }) {
  // 这样进来的时候,还能保持 count 的指,切换应用也可以保持
  const [count, setCount] = useState(data.actions.getCount())
  
  // 监听状态的变化,重新存起来
  // 如果想在页面上显示,还是重新存起来好
  data.onGlobalStateChange((state) => {
    setCount(state.count)
  })

  // 修改状态
  const addCountHandler = () => {
    data.setGlobalState({ count: data.actions.getCount() + 1 })
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <button onClick={addCountHandler}>add count</button>
        <h2>{count}</h2>
        {/* 如果你想这么做,对不起,页面可不会更新 */}
        {/* <h2>{data.actions.getCount()}</h2> */}
      </header>
    </div>
  )
}

export default App

总结

我也在网上找了很多资料,qiankunjs 的文档总觉得有点简陋

微服务调用前端端口怎么做 前端微服务通信_解决方案


就这样!!!好像说了怎么做,又好像没说,有一些细节还是得自己实践过才知道。如果有想入手的小伙伴,可以自己动手尝试一下,如果你不是很熟悉的话,不要用 vite,很打击信心,很会干扰你学习的进度。