irpas技术客

微应用搭建实践总结(qiankun + React)_平平的踩坑日记_qiankun react

大大的周 3451

qiankun 是一个基于 single-spa 的微前端实现库,是目前主流微前端解决方案之一。本文是对使用 qiankun+React 搭建微应用demo的一个实践以及踩坑记录。

搭建基座

使用 React 脚手架搭建项目,作为基座

npx create-react-app qiankun-base

安装 qiankun

npm i qiankun -S # or yarn add qiankun

配置index.js 注册子应用

import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import App from './App' import { registerMicroApps, start } from 'qiankun' import { BrowserRouter } from 'react-router-dom' //注册子应用 registerMicroApps([ { name: 'ReactApp', entry: '//localhost:4000', //显示子应用容器的id container: '#container', activeRule: '/react-app' } ]) //启动qiankun start() const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <React.StrictMode> // 后续会加上 react-router-dom ,所以此处要记得加上BrowserRouter < BrowserRouter > <App /> </ BrowserRouter > </React.StrictMode> )

使用 antd 来页面布局

安装 antd

npm i antd

在 index.js 中引入 antd 样式

import 'antd/dist/antd.css'

安装 react-router-dom

npm i react-router-dom

App.js页面布局,添加子应用容器

import { DesktopOutlined, FileOutlined, PieChartOutlined, TeamOutlined, UserOutlined, } from '@ant-design/icons' import { Breadcrumb, Layout, Menu } from 'antd' import React, { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import "./App.css" const { Header, Content, Footer, Sider } = Layout function getItem (label, key, icon, children) { return { key, icon, children, label, } } const items = [ getItem(<Link to="/react-app">react-app</Link>, '1', <PieChartOutlined />), getItem(<Link to="/react-app/userList">userList</Link>, '2', <PieChartOutlined />), ] const App = () => { const [collapsed, setCollapsed] = useState(false) const [user, setUser] = useState() useEffect(() => { mitter.addListener('user', msg => { setUser(msg) }) }) return ( <Layout style={{ minHeight: '100vh', }} > <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}> <div className="logo"> 当前用户:{user}</div> <Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items}/> </Sider> <Layout className="site-layout"> <Header className="site-layout-background" style={{ padding: 0, }} /> <Content style={{ margin: '0 16px', }} > {/* 子应用容器 */} <div id="container" className="site-layout-background" style={{ minHeight: 360 }}></div> </Content> <Footer style={{ textAlign: 'center', }} > Ant Design ?2018 Created by Ant UED </Footer> </Layout> </Layout> ) } export default App 搭建子应用

子应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的子应用主要使用的前端框架有 Vue、React、Angular,本文只尝试了 React 项目作为子应用。

使用 React 脚手架搭建项目,作为子应用

npx create-react-app qiankun-micro-base

安装 react-router-dom

npm i react-router-dom

在 src 目录下新建 public-path.js 配置资源公共路径,在 index.js 中导入(否则按需加载或外部加载的资源在主应用中加载时,会以主应用为基础路径)

if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } /** * 坑 * 直接放index.js import 下面:微应用中静态资源(图片)不显示 * 直接放index.js 最上方 运行报错 */

坑点

webpack_public_path 不添加 eslint 全局注释,会报错将代码直接放在 index.js 中,无论在哪个位置都不生效

index.js 中导出三个生命周期

import React from 'react' import "./public-path" import ReactDOM from 'react-dom/client' import './index.css' import App from './App' import reportWebVitals from './reportWebVitals' import { BrowserRouter } from 'react-router-dom' import "antd/dist/antd.css" //设置 history 模式路由的 base //<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/react-app' : '/'}> function render (props) { const { container } = props const dom = container ? container.querySelector('#root') : document.getElementById("root") const root = ReactDOM.createRoot(dom) root.render( <React.StrictMode> <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/react-app' : '/'}> <App /> </BrowserRouter> </React.StrictMode> ) } //为了避免根 id #root 与其他的 DOM 冲突,需要限制查找范围 if (!window.__POWERED_BY_QIANKUN__) { render({}) } export async function bootstrap () { console.log('[react16] react app bootstraped') } export async function mount (props) { console.log('[react16] props from main framework', props) render(props) } export async function unmount (props) { const { container } = props ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')) }

修改端口号,与主应用中注册的保持一致

在 node_modules 下找到 react-scripts 目录修改 scripts 目录下的 start.js 中将端口号 3000,改为 4000 即可

主应用与子应用之间通信

主应用配置

安装 events

npm i events --save

src 目录下添加 connector.js,在 index.js 中引入

import mitter from "./event" /** * qiankun 主应用与微应用通信 */ import { initGlobalState } from "qiankun" const state = { user: '', msg: 'hello' } //初始化通信池 const action = initGlobalState(state) //监听通信池变化 action.onGlobalStateChange((state, pre) => { console.log('-----通信池变化响应-----', state, pre) mitter.emit('user', state.user) })

在 App.js 中使用微应用传值

import mitter from "./event" const App = () => { const [user, setUser] = useState() useEffect(() => { mitter.addListener('user', msg => { setUser(msg) }) }) return ( //... <div className="logo"> 当前用户:{user}</div> //... ) } export default App

子应用配置 在 src 目录下添加 action.js

/** * 微应用通信 */ function emptyAction () { // 提示当前使用的是空 Action console.warn("Current execute action is empty!") } class Action { //默认值为空的action actions = { onGlobalStateChange: emptyAction, setGlobalState: emptyAction } setAction (actions) { this.actions = actions } onGlobalStateChange () { return this.actions.onGlobalStateChange(...arguments) } setGlobalState () { return this.actions.setGlobalState(...arguments) } } const actions = new Action() export default actions

在 index.js 中引入 action.js ,完成初始化

import actions from './action' //... export async function mount (props) { console.log('[react16] props from main framework', props) actions.setAction(props) render(props) } //...

在 userList.js 中点击事件绑定方法

import { Space, Table, Tag } from 'antd' import React from 'react' import actions from '../action' const columns = [ { title: 'Name', dataIndex: 'name', key: 'name', render: (text) => <a>{text}</a>, }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Address', dataIndex: 'address', key: 'address', }, { title: 'Tags', key: 'tags', dataIndex: 'tags', render: (_, { tags }) => ( <> {tags.map((tag) => { let color = tag.length > 5 ? 'geekblue' : 'green' if (tag === 'loser') { color = 'volcano' } return ( <Tag color={color} key={tag}> {tag.toUpperCase()} </Tag> ) })} </> ), }, { title: 'Action', key: 'action', render: (_, record) => ( <Space size="middle"> <a>Invite {record.name}</a> <a>Delete</a> </Space> ), }, ] const data = [ { key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', tags: ['loser'], }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ] const UserList = () => ( <Table columns={columns} dataSource={data} onRow={(record) => { return { onClick: (event) => { actions.setGlobalState({ user: record.name, }) }, } }} /> ) export default UserList


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #qiankun #React #是一个基于 #singlespa #本文是对使用 #qiankunReact