如何在 React 自定义元素中为 MUI material v5 创建插入点以在阴影 dom 中安装样式
How to create insertion point to mount styles in shadow dom for MUI material v5 in React custom element
使用@material-ui/core V4(确切地说是4.12.3)我有一个使用webpack和babel成功创建的自定义元素。我曾经使用@material-ui/core makeStyles 来设计它的样式。现在我正在升级到 @mui/material v5 并希望使用来自 @mui/material 的内置组件,但它们不会在自定义元素中显示样式。
请注意,我需要这是一个自定义元素,因为它将集成到另一个托管应用程序中。
index.tsx 在 v4 之前
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
升级到@mui/material v5(准确地说是 v5.0.4),首先我尝试使用 StyledEngineProvider 来安装样式。然后我尝试使用@mui/styles jssPreset。无论哪种方式都行不通。我的意思是不起作用的是 AppComponent 引用的 DataContainer 有 @mui/material 组件,它们都在加载时没有任何样式(例如 Grid、Button、InputLabel、Select,等等).
首先尝试使用 StyledEngineProvider
import AppComponent from './App';
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
// can't use jss in mui v5
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
render(
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<AppComponent />
</ThemeProvider>
</StyledEngineProvider>,
mountPoint //I have also used reactRoot here instead and got same result
);
}
}
customElements.define('my-element', MyWebComponent);
第二次尝试@mui/styles jssPreset
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@mui/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.getElementById('jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
应用组件
import React from 'react';
import { Suspense } from 'react';
import DataContainer from './components/DataContainer';
class AppComponent extends React.Component<any> {
render() {
return (
<Suspense fallback='Loading...'>
<div className='AppComponent'>
<DataContainer />
</div>
</Suspense>
);
}
}
export default AppComponent;
数据容器
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
const Item = styled(Paper)(({ theme }) => ({
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
export default function FullWidthGrid() {
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={6} md={8}>
<Button variant="contained">xs=6 md=8</Button>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={8}>
<Item>xs=6 md=8</Item>
</Grid>
</Grid>
<div>
<FormControl sx={{ m: 1, minWidth: 180 }}>
<Select autoWidth>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Twenty</MenuItem>
<MenuItem value={21}>Twenty one</MenuItem>
<MenuItem value={22}>Twenty one and a half</MenuItem>
</Select>
</FormControl>
</div>
</Box>
);
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="manifest.json" />
<title>React Custom Element</title>
</head>
<body>
<my-element id="elem"> </my-element>
</body>
</html>
这是我看到的:
这就是我应该看到的,如本次 stackblitz 所示。 (请注意,不幸的是我无法创建带有自定义元素的 stackblitz)
https://stackblitz.com/edit/react-d8xtdu?file=index.js
以下是我的做法:
您需要创建 style
标签。这将是情感(material ui 5 样式解决方案)插入范围阴影 DOM 样式的切入点。
下一步是配置jss和情感缓存
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
最后要做的是将我们的树包装在提供者中
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
完整示例:
import React from 'react';
import Demo from './demo';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { StylesProvider, jssPreset } from '@mui/styles';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { create } from 'jss';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const emotionRoot = document.createElement('style');
const mountPoint = document.createElement('div');
shadowRoot.appendChild(emotionRoot);
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
}
}
if (!customElements.get('my-element')) {
customElements.define('my-element', MyWebComponent);
}
使用@material-ui/core V4(确切地说是4.12.3)我有一个使用webpack和babel成功创建的自定义元素。我曾经使用@material-ui/core makeStyles 来设计它的样式。现在我正在升级到 @mui/material v5 并希望使用来自 @mui/material 的内置组件,但它们不会在自定义元素中显示样式。 请注意,我需要这是一个自定义元素,因为它将集成到另一个托管应用程序中。
index.tsx 在 v4 之前
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
升级到@mui/material v5(准确地说是 v5.0.4),首先我尝试使用 StyledEngineProvider 来安装样式。然后我尝试使用@mui/styles jssPreset。无论哪种方式都行不通。我的意思是不起作用的是 AppComponent 引用的 DataContainer 有 @mui/material 组件,它们都在加载时没有任何样式(例如 Grid、Button、InputLabel、Select,等等).
首先尝试使用 StyledEngineProvider
import AppComponent from './App';
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
// can't use jss in mui v5
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('custom-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
render(
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<AppComponent />
</ThemeProvider>
</StyledEngineProvider>,
mountPoint //I have also used reactRoot here instead and got same result
);
}
}
customElements.define('my-element', MyWebComponent);
第二次尝试@mui/styles jssPreset
import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@mui/styles';
import { create } from 'jss';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.getElementById('jss-insertion-point');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
render(
<StylesProvider jss={jss}>
<AppComponent />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-element', MyWebComponent);
应用组件
import React from 'react';
import { Suspense } from 'react';
import DataContainer from './components/DataContainer';
class AppComponent extends React.Component<any> {
render() {
return (
<Suspense fallback='Loading...'>
<div className='AppComponent'>
<DataContainer />
</div>
</Suspense>
);
}
}
export default AppComponent;
数据容器
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
const Item = styled(Paper)(({ theme }) => ({
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
export default function FullWidthGrid() {
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={6} md={8}>
<Button variant="contained">xs=6 md=8</Button>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid item xs={6} md={8}>
<Item>xs=6 md=8</Item>
</Grid>
</Grid>
<div>
<FormControl sx={{ m: 1, minWidth: 180 }}>
<Select autoWidth>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Twenty</MenuItem>
<MenuItem value={21}>Twenty one</MenuItem>
<MenuItem value={22}>Twenty one and a half</MenuItem>
</Select>
</FormControl>
</div>
</Box>
);
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="manifest.json" />
<title>React Custom Element</title>
</head>
<body>
<my-element id="elem"> </my-element>
</body>
</html>
这是我看到的:
这就是我应该看到的,如本次 stackblitz 所示。 (请注意,不幸的是我无法创建带有自定义元素的 stackblitz) https://stackblitz.com/edit/react-d8xtdu?file=index.js
以下是我的做法:
您需要创建 style
标签。这将是情感(material ui 5 样式解决方案)插入范围阴影 DOM 样式的切入点。
下一步是配置jss和情感缓存
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
最后要做的是将我们的树包装在提供者中
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
完整示例:
import React from 'react';
import Demo from './demo';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { StylesProvider, jssPreset } from '@mui/styles';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { create } from 'jss';
import { render } from 'react-dom';
const theme = createTheme();
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const emotionRoot = document.createElement('style');
const mountPoint = document.createElement('div');
shadowRoot.appendChild(emotionRoot);
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot,
});
const cache = createCache({
key: 'css',
prepend: true,
container: emotionRoot,
});
render(
<StylesProvider jss={jss}>
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Demo />
</ThemeProvider>
</CacheProvider>
</StylesProvider>,
mountPoint
);
}
}
if (!customElements.get('my-element')) {
customElements.define('my-element', MyWebComponent);
}