微服务乾坤使用
作者:互联网
1、安装乾坤
npm i qiankun -S
2、修改主应用
- 主应用分为路由形式加载和手动加载俩种形式
- 路由形式加载,在启动文件中增加启动代码,适合基于大面积替换子应用的场景,例如切换首页应用直接替换当前应用。
registerMicroApps([ { name: 'stream-media', // app name registered entry: '//localhost:9001', // 加载应用地址 container: '#stream-media', // 加载应用绑定的domi d activeRule: '/test/stream-media', // 加载应用的路由 } ]); start();
- 手动加载,适合在其他路由中,动态创建一小块页面
import React, {useEffect, useRef} from 'react'; import {loadMicroApp} from 'qiankun'; import {withRouter} from "react-router-dom"; import GlobalShared from "@/share"; const StreamMedia: React.FC<any> = (props: any) => { const divRef: any = useRef<HTMLDivElement>(null); useEffect(() => { const videoStreamApp = loadMicroApp({ name: 'video-stream', // app name registered // 部署地址 entry: process.env.REACT_APP_VIDEO_STREAM as string, container: '#video-stream', props: Object.assign({ token: sessionStorage.logintoken, height: divRef.current.offsetHeight, // margin-bottom GlobalShared }, props.location?.state) }, { // sandbox: {strictStyleIsolation: true} // 强制隔离 }); return () => { /** * 将微应用卸载掉 */ videoStreamApp.unmount(); } }, []); return ( <div ref={divRef} id="video-stream" style={{position: 'relative', height: '100%'}}></div> ) }; export default withRouter(StreamMedia);
3、修改微应用
- config-overrides.js中修改主要有俩出,一处是指定输出,一处是允许跨域
/** * 微服务参数处理 * @type {string} */ config.output.library = `stream-media`; // 注意,这个是主应用注册的子应用名称 config.output.libraryTarget = "umd"; config.output.publicPath = 'http://localhost:9001/'; // 注意,这个是你子应用的启动地址 devServer: overrideDevServer( // dev server plugin (config) => { /** * 配置允许跨域 * @type {{"Access-Control-Allow-Origin": string}} */ config.headers = {"Access-Control-Allow-Origin": "*",}; return config; } )
- 启动文件index中需要暴露3个函数
export async function bootstrap() { console.log('[react16] react app bootstraped'); } export async function mount(props: any) { console.log('[react16] props from main framework', props); startUp(props); } export async function unmount(props: any) { const {container} = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); }
- 根路径下新增public-path.js用于修改publicPath路径
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
4、开发环境处理微应用js、json等文件的加载跨域问题
- 微应用需要处理路径
在微应用中,将暴露的参数提取到.env文件中管理
#注意,这个是主应用注册的子应用名称
REACT_APP_QIANKUN_library= video-stream
# 支持的导入形式
REACT_APP_QIANKUN_libraryTarget= umd
# 注意,这个是你子应用的启动地址
REACT_APP_QIANKUN_publicPath= http://localhost:9004/
加载js时,如果是给作为微应用启动时,需要处理下路径
// @ts-ignore 如果是作为微应用启动 则修改路径 if (window.__POWERED_BY_QIANKUN__) { GlobalStaticFile = GlobalStaticFile.map((url: string) => { // @ts-ignore return Path.resolve(process.env.REACT_APP_QIANKUN_publicPath, url); }); }
加载json文件时,需要增加前缀,建议使用子应用名称,统一管理,方便主应用处理跨域
如果使用fetch报错 请删除headers的'content-type': 'application/json'属性
Access to fetch at 'http://localhost:9004/bin/layout.json' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
/** * 获取资源路径 * @param url */ const getPathName = (url: string) => { // @ts-ignore if (!window.__POWERED_BY_QIANKUN__) { return Path.resolve(window.location.pathname, url); } // return url; return Path.resolve(process.env.REACT_APP_QIANKUN_library as string, url); };
- 主应用需要处理跨域
setupProxy中处理跨域 /** * 处理微应用跨域问题 */ app.use( createProxyMiddleware('/video-stream', { target: 'http://localhost:9004', // changeOrigin: true, // needed for virtual hosted sites ws: true, // proxy websockets secure: false, // 验证SSL证书。应用于https pathRewrite: { '^/video-stream': '' } }) );
5、处理样式隔离
增加前缀,'@ant-prefix': process.env.REACT_APP_QIANKUN_library,
addLessLoader({ lessOptions: { javascriptEnabled: true, localIdentName: '[local]--[hash:base64:5]', modifyVars: Object.assign({}, getThemeVariables({ // dark: true, // 开启暗黑模式 compact: false, // 开启紧凑模式 }), { '@ant-prefix': process.env.REACT_APP_QIANKUN_library, }), } }),
ant message(创建方式)无法销毁及样式不可用问题处理
思路:样式不可用问题,将样式独立下载,独立维护,且增加前缀
/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ /* stylelint-disable no-duplicate-selectors */ /* stylelint-disable */ /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ .video-stream-ant-message { ... } AntdMessage.config({ prefixCls: `${process.env.REACT_APP_QIANKUN_library}-ant-message`, }); // 销毁问题:手动销毁 AntdMessage[fnKey]({ content: _msg, key: key, className: key, duration: 3, }).then((t: any) => { /** * 微组件销毁时处理 ant组件实现有bug * 直接使用key不能销毁 */ AntdMessage.destroy(key); const msg: any = document.getElementsByClassName(`${key}`); if (msg && msg[0] && msg[0].parentNode) { msg[0].parentNode.removeChild(msg[0]) } });
6、父子通信问题
- 使用props传递给微应用(直接传递,绑定到客户端sessionStorage localStorage)
- 使用Actions方式通信
// qiankun提供的api 使用观察者模式处理数据流动 import { initGlobalState, MicroAppStateActions } from "qiankun"; const initialState = {}; const actions: MicroAppStateActions = initGlobalState(initialState); export default actions; //赋值 actions.setGlobalState({ token }); // 注册观察者监听变化 actions.onGlobalStateChange((state, prevState) => { // state: 变更后的状态; prevState: 变更前的状态 console.log("主应用:改变前的值为 ", prevState.token); console.log("主应用:登录状态发生改变,改变后的值为 ", state.token); }); // 子应用在index中需要接受actions // 通信方式也是通过setGlobalState // TODO 通信业务复杂时,该种方案维护较为困难,数据流转不好跟踪
- 使用shared方式传递给微应用
使用redux来管理状态,并把管理状态的类作为工具传递给微应用,来达到数据共享和交互的目的
// src/share/Store.ts
/** * 暴露的类型 */ export enum SharedType { TOKEN = 'SET_TOKEN' } /** * 绑定的数据 后续可继续添加 */ type SharedState = { token: string, } /** * 动作支持 */ type SharedAction = { type: string, value: any, } const reducer = (state: SharedState, action: SharedAction): SharedState => { switch (action.type) { case SharedType.TOKEN: return Object.assign({}, state, {token: action.value}); default: return state; } }; const SharedStore = createStore<SharedState, SharedAction, any, any>(reducer as any, {} as any); export default SharedStore;
//主应用 src/share/index.ts
import SharedStore, {SharedType} from './Store'; class Shared { /** * 获取token */ public getToken = (): string => { return SharedStore.getState()[SharedType.TOKEN]; } /** * 设置token * @param token */ public setToken = (token: string): void => { SharedStore.dispatch({ type: SharedType.TOKEN, value: token }); } } const GlobalShared = new Shared(); export default GlobalShared; // login是通过GlobalShared. setToken 去赋值 将GlobalShared整个传递给微应用
//微应用/Shared/index.ts
import GLOBALCONFIG from "@share/HttpClient/GLOBALCONFIG"; class Shared { /** * 获取token */ public getToken(): string { return localStorage.getItem(GLOBALCONFIG.USERlocalStorage) || ''; } /** * 设置token * @param token */ public setToken(token: string): void { localStorage.setItem('autoLogin', token); } } class SharedModlue { static shared = new Shared(); /** * 父应用传递进来的shared 进行覆盖 * @param shared */ static overloadShared(shared: any) { SharedModlue.shared = shared; } /** * 获取shared 里面有token */ static getShared() { return SharedModlue.shared } } export default SharedModlue;
// 在index render函数中覆盖
if (GlobalShared) SharedModlue.overloadShared(GlobalShared);
7、部署后静态资源请求必须支持跨域,nginx配置
location /video-stream { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; if ($request_method = 'OPTIONS') { return 204; } try_files $uri /video-stream/index.html; index /video-stream/index.html; }
标签:服务,string,stream,QIANKUN,token,应用,使用,return,乾坤 来源: https://blog.csdn.net/ligaoming_123/article/details/117230984