在本节中,我们将通过一个实际案例,展示如何在项目中应用微前端架构和Qiankun。该案例将涵盖从项目初始化到子应用集成、通信、样式隔离以及性能优化的各个方面。
案例概述
假设我们有一个电商平台,包含多个子应用,例如:产品列表、产品详情、购物车和用户管理。为了提升开发效率和维护性,我们决定采用微前端架构,将每个模块拆分为独立的子应用。
准备工作
- 环境搭建
确保已安装Node.js和npm。使用Vite创建React 18+TypeScript项目:
perl
npm create vite@latest my-ecommerce-platform --template react-ts
cd my-ecommerce-platform
npm install
-
安装Ant Design和Redux Toolkit
npm install antd @reduxjs/toolkit react-redux
创建主应用
- 配置React Router
在src/main.tsx
中设置React Router和全局状态管理:
javascript
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';
import './index.css';
import 'antd/dist/reset.css';
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container!);
root.render(
<React.StrictMode>
<Provider store={store}>
<Router>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</Router>
</Provider>
</React.StrictMode>
);
-
配置Redux 在
src/store.ts
中配置Redux Toolkit的store:// src/store.ts import { configureStore } from '@reduxjs/toolkit'; import userReducer from './slices/userSlice';
export const store = configureStore({ reducer: { user: userReducer, }, });
export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;
-
创建
userSlice
文件
在src/slices/userSlice.ts
中定义用户状态管理:
ini
// src/slices/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
name: string;
email: string;
}
const initialState: UserState = {
name: '',
email: '',
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser(state, action: PayloadAction<UserState>) {
state.name = action.payload.name;
state.email = action.payload.email;
},
clearUser(state) {
state.name = '';
state.email = '';
},
},
});
export const { setUser, clearUser } = userSlice.actions;
export default userSlice.reducer;
- 整合 Redux Store
在src/store.ts
中将 userSlice
整合到 Redux store 中:
typescript
// src/store.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
- 使用 Redux State 和 Actions
在你的组件中,你可以通过 Redux 的 useSelector
和 useDispatch
钩子来使用状态和派发动作。
javascript
// src/components/UserProfile.tsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store';
import { setUser, clearUser } from '../slices/userSlice';
const UserProfile = () => {
const user = useSelector((state: RootState) => state.user);
const dispatch = useDispatch();
const updateUser = () => {
dispatch(setUser({ name: 'John Doe', email: 'john.doe@example.com' }));
};
const clearUserInfo = () => {
dispatch(clearUser());
};
return (
<div>
<h1>User Profile</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<button onClick={updateUser}>Update User</button>
<button onClick={clearUserInfo}>Clear User</button>
</div>
);
};
export default UserProfile;
- 将 UserProfile 组件添加到主应用
最后,将 UserProfile
组件添加到主应用中,以便能够查看和操作用户状态。
javascript
// src/App.tsx
import React, { useEffect } from 'react';
import { Link, Route, Routes } from 'react-router-dom';
import { registerMicroApps, start } from 'qiankun';
import Home from './pages/Home';
import Product from './pages/Product';
import Cart from './pages/Cart';
import User from './pages/User';
import UserProfile from './components/UserProfile';
const App = () => {
useEffect(() => {
registerMicroApps([
{
name: 'productApp',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/product',
},
{
name: 'cartApp',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: '/cart',
},
{
name: 'userApp',
entry: '//localhost:7103',
container: '#subapp-container',
activeRule: '/user',
},
]);
start();
}, []);
return (
<div>
<nav>
<ul>
<li><Link to="/home">Home</Link></li>
<li><Link to="/product">Product</Link></li>
<li><Link to="/cart">Cart</Link></li>
<li><Link to="/user">User</Link></li>
</ul>
</nav>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/product" element={<Product />} />
<Route path="/cart" element={<Cart />} />
<Route path="/user" element={<User />} />
<Route path="/user-profile" element={<UserProfile />} />
<Route path="/subapp/*" element={<div id="subapp-container"></div>} />
</Routes>
</div>
);
};
export default App;
通过这些步骤,您已经成功创建并配置了主应用中的 userSlice
文件,并展示了如何在主应用中使用Redux状态管理和用户状态相关的操作。
-
配置主应用组件和路由 在
src/App.tsx
中配置主应用的导航栏和路由:// src/App.tsx import React, { useEffect } from 'react'; import { Link, Route, Routes } from 'react-router-dom'; import { registerMicroApps, start } from 'qiankun'; import Home from './pages/Home'; import Product from './pages/Product'; import Cart from './pages/Cart'; import User from './pages/User';
const App = () => { useEffect(() => { registerMicroApps([ { name: 'productApp', entry: '//localhost:7101', container: '#subapp-container', activeRule: '/product', }, { name: 'cartApp', entry: '//localhost:7102', container: '#subapp-container', activeRule: '/cart', }, { name: 'userApp', entry: '//localhost:7103', container: '#subapp-container', activeRule: '/user', }, ]); start(); }, []);
return (
- Home
- Product
- Cart
- User
<Route path="/home" element={} /> <Route path="/product" element={} /> <Route path="/cart" element={} /> <Route path="/user" element={} /> <Route path="/subapp/*" element={
} />
); };export default App;
-
创建页面组件
创建简单的页面组件:
javascript
// src/pages/Home.tsx
const Home = () => (
<div>
<h1>Home Page</h1>
</div>
);
export default Home;
// src/pages/Product.tsx
const Product = () => (
<div>
<h1>Product Page</h1>
</div>
);
export default Product;
// src/pages/Cart.tsx
const Cart = () => (
<div>
<h1>Cart Page</h1>
</div>
);
export default Cart;
// src/pages/User.tsx
const User = () => (
<div>
<h1>User Page</h1>
</div>
);
export default User;
创建子应用
每个子应用也是一个Vite+React 18项目,可以通过Vite的插件或手动编写脚本实现资源的导入和导出,以及生命周期的挂载。
1. 创建并初始化子应用
创建并初始化一个新的Vite项目作为子应用:
sql
npm create vite@latest product-app --template react-ts
cd product-app
npm install
2. 配置子应用的生命周期函数
在src/main.tsx
中配置子应用的生命周期函数:
javascript
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
function render(props: any) {
const { container } = props;
const root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('React app bootstraped');
}
export async function mount(props: any) {
render(props);
}
export async function unmount(props: any) {
const { container } = props;
const root = container ? container.querySelector('#root') : document.querySelector('#root');
ReactDOM.unmountComponentAtNode(root);
}
3. 配置Vite支持跨域加载
在vite.config.ts
中配置CORS支持,以便主应用可以加载子应用的资源:
php
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 7101,
cors: true,
},
});
4. 添加基本组件
创建一个简单的子应用页面:
javascript
// src/App.tsx
const App = () => (
<div>
<h1>Sub Application - Product</h1>
</div>
);
export default App;
通过以上步骤,您将完成主应用和子应用的配置,并实现它们的集成。这些步骤确保了主应用能够正确加载和管理子应用,并通过Qiankun实现微前端架构的基本功能。
- 在主应用中加载子应用
主应用需要使用qiankun的API来注册子应用。
npm install qiankun
然后在主应用中设置子应用:
php
// main.tsx 或者合适的地方注册子应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'productApp',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/product',
},
// 注册更多子应用...
]);
start();
子应用之间通信
在微前端架构中,子应用之间的通信是一个关键问题。通常我们可以通过以下几种方法实现子应用之间的通信:
-
全局状态管理(如 Redux)
-
自定义事件
-
发布-订阅模式
以下是这几种方法的详细实现示例。
方法一:全局状态管理(如 Redux)
我们可以使用 Redux 等全局状态管理库,将状态存储在主应用中,子应用可以通过与主应用共享状态来实现通信。
主应用的配置
-
在主应用中配置 Redux
// src/store.ts import { configureStore } from '@reduxjs/toolkit'; import userReducer from './slices/userSlice';
export const store = configureStore({ reducer: { user: userReducer, }, });
export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;
-
在主应用中提供 Redux Store
// src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { Provider } from 'react-redux'; import App from './App'; import { store } from './store'; import './index.css'; import 'antd/dist/reset.css';
const container = document.getElementById('root'); const root = ReactDOM.createRoot(container!);
root.render( <React.StrictMode> <Route path="/*" element={} /> </React.StrictMode> );
子应用的配置
-
在子应用中使用共享的 Redux Store
// src/main.tsx(子应用) import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { Provider } from 'react-redux'; import { store } from './store';
function render(props: any) { const { container } = props; const root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root')); root.render( <React.StrictMode> <Provider store={props.store || store}> </React.StrictMode> ); }
if (!window.POWERED_BY_QIANKUN) { render({}); }
export async function bootstrap() { console.log('React app bootstraped'); }
export async function mount(props: any) { render(props); }
export async function unmount(props: any) { const { container } = props; const root = container ? container.querySelector('#root') : document.querySelector('#root'); ReactDOM.unmountComponentAtNode(root); }
-
在子应用中使用共享状态
// src/components/SharedComponent.tsx(子应用) import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../store'; import { setUser, clearUser } from '../slices/userSlice';
const SharedComponent = () => { const user = useSelector((state: RootState) => state.user); const dispatch = useDispatch();
const updateUser = () => { dispatch(setUser({ name: 'Jane Doe', email: 'jane.doe@example.com' })); };
return (
Name: {user.name}Email: {user.email}
Update User
); };export default SharedComponent;
方法二:自定义事件
可以使用自定义事件在子应用之间进行通信。主应用可以作为中介,监听子应用发出的事件并转发给其他子应用。
在主应用中派发事件
javascript
// src/App.tsx
import React, { useEffect } from 'react';
import { Link, Route, Routes } from 'react-router-dom';
import { registerMicroApps, start } from 'qiankun';
import Home from './pages/Home';
import Product from './pages/Product';
import Cart from './pages/Cart';
import User from './pages/User';
const App = () => {
useEffect(() => {
registerMicroApps([
{
name: 'productApp',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/product',
},
{
name: 'cartApp',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: '/cart',
},
{
name: 'userApp',
entry: '//localhost:7103',
container: '#subapp-container',
activeRule: '/user',
},
]);
start();
const handleCustomEvent = (event: CustomEvent) => {
console.log('Received custom event:', event.detail);
// 转发事件到其他子应用
window.dispatchEvent(new CustomEvent('forwarded-event', { detail: event.detail }));
};
window.addEventListener('custom-event', handleCustomEvent);
return () => {
window.removeEventListener('custom-event', handleCustomEvent);
};
}, []);
return (
<div>
<nav>
<ul>
<li><Link to="/home">Home</Link></li>
<li><Link to="/product">Product</Link></li>
<li><Link to="/cart">Cart</Link></li>
<li><Link to="/user">User</Link></li>
</ul>
</nav>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/product" element={<Product />} />
<Route path="/cart" element={<Cart />} />
<Route path="/user" element={<User />} />
<Route path="/subapp/*" element={<div id="subapp-container"></div>} />
</Routes>
</div>
);
};
export default App;
在子应用中发出和监听事件
javascript
// src/App.tsx(子应用)
import React, { useEffect } from 'react';
const App = () => {
useEffect(() => {
const handleForwardedEvent = (event: CustomEvent) => {
console.log('Received forwarded event:', event.detail);
};
window.addEventListener('forwarded-event', handleForwardedEvent);
return () => {
window.removeEventListener('forwarded-event', handleForwardedEvent);
};
}, []);
const sendEvent = () => {
const event = new CustomEvent('custom-event', { detail: { message: 'Hello from product app' } });
window.dispatchEvent(event);
};
return (
<div>
<h1>Product App</h1>
<button onClick={sendEvent}>Send Event</button>
</div>
);
};
export default App;
方法三:发布-订阅模式
可以使用轻量级的发布-订阅库(如PubSubJS)来实现子应用之间的通信。
安装 PubSubJS
npm install pubsub-js
在主应用中初始化 PubSubJS
主应用中不需要特殊的配置,只需要在子应用中订阅和发布消息。
在子应用中发布和订阅消息
javascript
// src/App.tsx(子应用)
import React, { useEffect } from 'react';
import PubSub from 'pubsub-js';
const App = () => {
useEffect(() => {
const token = PubSub.subscribe('TOPIC', (msg, data) => {
console.log('Received message:', data);
});
return () => {
PubSub.unsubscribe(token);
};
}, []);
const sendMessage = () => {
PubSub.publish('TOPIC', { message: 'Hello from product app' });
};
return (
<div>
<h1>Product App</h1>
<button onClick={sendMessage}>Send Message</button>
</div>
);
};
export default App;
通过以上几种方法,可以在微前端架构中实现子应用之间的通信,选择适合项目需求的方式进行实现即可。
Ant Design UI组件使用
在子应用中,可以按需引入主应用中全局注册的Ant Design组件。
性能优化
懒加载
React支持路由级别的懒加载,可以为每个子应用路由配置懒加载。
预加载
Vite支持预加载设置,也可以手动配置webpack来优化资源的加载。
安全性
在微前端应用中,需要关注跨域资源共享(CORS)策略,确保安全通信。配置Nginx或者相应的Web服务器支持所需的CORS策略。
部署子应用
使用Vite的build
命令将应用构建为静态资源,然后可以将子应用部署到相应的静态资源服务器或者使用Docker容器化部署。
通过本节的实战案例演示,我们探索了如何使用React 18、TypeScript、Vite、Redux Toolkit和Ant Design构建一个基于微前端架构的电商平台。我们了解了如何设置主应用和子应用、实现子应用间的通信和状态共享,以及应用的性能优化和部署。希望这个案例能够帮助你深入理解和掌握微前端架构下的应用开发和管理。