微前端无效钩子调用



我有一个具有以下结构的微前端应用程序。我正试图将营销和公司安装到容器中,但不断获得无效的钩子调用。它们都使用react 18.2, react-dom 18.2和react-router-dom 6.3.0。营销本身在浏览器中加载良好,但当试图将其加载到容器中时,我得到一个无效的钩子调用警告。发生了什么事?我可以在容器(主机)和营销(远程)中的BrowserRouter中使用Router吗?

/packages
|  /company
|  |  /public
|  |  /src
|  /marketing
|  |  /public
|  |  /src
|  /container
|  |  /public
|  |  /src

容器:App.js

import React, { lazy, Suspense, useState, useEffect } from 'react';
import { Router, Route, Routes, Navigate } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
height: 100%;
min-height: 100vh;
}
html {
height: 100%;
}
`
import WebFont from 'webfontloader';
WebFont.load({
google: {
families: ['Barlow', 'Playfair Display', 'Overpass']
}
});
// deleted because we are now using lazy function and Suspense module
// import MarketingApp from './components/MarketingApp';
// import AuthApp from './components/AuthApp';
import Progress from './components/Navbar/Progress';
import Header from './components/Header';
const MarketingLazy = lazy(() => import('./components/MarketingApp'));
const CompaniesLazy = lazy(() => import('./components/CompanyApp'));
const history = createBrowserHistory();
export default function App() {
// useState is a HOOK to keep track of the application state IN A FUNCTION COMPONENT, in this case if the user isSignedIn
// State generally refers to data or properties that need to be tracking in an application
// hooks can only be called at the top level of a component; cannot be conditional; and can only be called inside React function commponents 
// useState accepts an initial state and returns two values: the current state, and a function that updates the state 
const [isSignedIn, setIsSignedIn] = useState(false);
// 
useEffect(() => {
if (isSignedIn) {
history.push('/companies/last')
}
}, [isSignedIn]);

return (
<Router history={history}>
<div>
<GlobalStyle />
<Header onSignOut={() => setIsSignedIn(false)} isSignedIn={isSignedIn} />
<Suspense fallback={<Progress />} >
<Routes>
...
<Route path="/" element={ <MarketingLazy /> } />
</Routes>
</Suspense>
</div>
</Router>
);
};

容器:MarketingApp.js从Marketing (remote)导入mount功能

import { mount } from 'marketingMfe/MarketingApp';
import React, { useRef, useEffect, useContext } from 'react';
import { useLocation, UNSAFE_NavigationContext } from 'react-router-dom';
export default () => {
const ref = useRef(null);
let location = useLocation();
const { navigator } = useContext(UNSAFE_NavigationContext);

useEffect(() => {
const { onParentNavigate } = mount(ref.current, {
initialPath: location.pathname,
onNavigate: ({ pathname: nextPathname }) => {        
const { pathname } = navigator.location;
if (pathname !== nextPathname) {
navigator.push(nextPathname);
}
},
});
const unlisten = navigator.listen(onParentNavigate);
return unlisten; // <-- cleanup listener on component unmount
}, []);
return <div ref={ref} />;
};

营销:App.js

import React from 'react';
import { Routes, Route, BrowserRouter as Router } from 'react-router-dom';
import Landing from './components/Landing';
import EarlyAccess from './components/LandingPages/EarlyAccess';
import WebFont from 'webfontloader';
if (process.env.NODE_ENV === 'development') {
WebFont.load({
google: {
families: ['Barlow', 'Playfair Display', 'Overpass']
}
});
}
export default ({ history }) => {
return (
<div>
<Router location={history.location} history={history}>
<Routes>
<Route exact path="/earlyaccess" element={ <EarlyAccess /> } />
<Route exact path="/learn" element={ <EarlyAccess /> } />
<Route path="/" element={ <Landing /> } />
</Routes>
</Router>
</div>
);
};

营销:bootstrap.js通过Container (host)运行时导出mount函数

import React from 'react';
import ReactDOM from 'react-dom/client';
import { createMemoryHistory, createBrowserHistory } from 'history';
import App from './App';
// Mount function to start up the app
// NOTE: if you update mount parameters then change the code for running the app in isolation below this definition
const mount = (el, { onNavigate, defaultHistory }) => {
// if given a default history object, assign it to history; otherwise use MemoryHistory object
const history = defaultHistory || createMemoryHistory();
if (onNavigate) {
history.listen(onNavigate); // event listener tied to the history object which listens to whenever navigation occur
}
const root = ReactDOM.createRoot(el);
root.render(
<React.StrictMode>
<App history={history} />
</React.StrictMode>
);
return{
onParentNavigate({ pathname: nextPathname }) {
console.log('Container-marketing just navigated.');
// console.log(location);
const { pathname } = history.location;
// avoid getting into an infinite loop
if (pathname !== nextPathname) {
history.push(nextPathname);
}
},
};
};
// If we are in development and in isolation,
// call mount immediately
if (process.env.NODE_ENV === 'development') {
const devRoot = document.querySelector('#_marketing-dev-root');
if (devRoot) {
mount(devRoot, { defaultHistory: createBrowserHistory() });
}
}
// We are running through container
// and we should export the mount function
export { mount };

**容器:webpack.dev.js**

const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');
const path = require('path');
const globals = require('../src/variables/global')
const port = globals.port
const devConfig = {
mode: 'development',
output: {
publicPath: `http://localhost:${port}/`,   // don't forget the slash at the end
},
devServer: {
port: port,
historyApiFallback: {
index: 'index.html',
},
},
plugins: [
new ModuleFederationPlugin({
name: 'companiesMod',
filename: 'remoteEntry.js',
exposes: {
'./CompaniesApp': './src/bootstrap',
},
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
resolve: {
// for dotenv config
fallback: {
"fs": false,
"os": false,
"path": false
},
extensions: ['', '.js', '.jsx', '.scss', '.eot', '.ttf', '.svg', '.woff'],
modules: ['node_modules', 'scripts', 'images', 'fonts'],
alias: {
mdbReactUiKit: 'mdb-react-ui-kit'
},
},
};
module.exports = merge(commonConfig, devConfig);

** Marketing (remote): webpack.dev.js **

const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');
const devConfig = {
mode: 'development',
output: {
publicPath: 'http://localhost:8081/'   // don't forget the slash at the end
},
devServer: {
port: 8081,
historyApiFallback: {
index: 'index.html',
},
},
plugins: [
new ModuleFederationPlugin({
name: 'marketingMod',
filename: 'remoteEntry.js',
exposes: {
'./MarketingApp': './src/bootstrap',
'./FooterApp': './src/components/footer/Footer',
},
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
module.exports = merge(commonConfig, devConfig);

容器:package.json

{
"name": "containerr",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
},
"repository": {
"type": "git",
"url": "https://github.com/theCosmicGame/mavata.git",
"directory": "packages/containerr"
},
"keywords": [],
"author": "",
"license": "ISC",
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@svgr/webpack": "^6.2.1",
"babel-loader": "^8.2.5",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"sass-loader": "^13.0.2",
"style-loader": "^3.3.1",
"url-loader": "^4.1.1",
"webfontloader": "^1.6.28",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.9.0",
"styled-components": "^5.3.5"
}
}

** Marketing: package.json**

{
"name": "marketing",
"version": "1.0.0",
"scripts": {
"start": "webpack serve --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
},
"repository": {
"type": "git",
"url": "https://github.com/theCosmicGame/mavata.git",
"directory": "packages/marketingg"
},
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.1",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"sass-loader": "^13.0.0",
"style-loader": "^2.0.0",
"webfontloader": "^1.6.28",
"webpack": "^5.4.0",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.2.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0"
},
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@mui/material": "^5.9.0",
"styled-components": "^5.3.5"
}
}

在我的webpack.dev.server中,我更改了

shared: packageJson.dependencies,

……

shared: {...packageJson.dependencies, ...packageJson.peerDependencies}

和警告消失

最新更新