关于javascript:React组件的Material UI主题不在本地范围内适用于Shadow DOM

React component's Material UI theme not scoped locally to Shadow DOM

介绍

我正在构建一个Chrome扩展程序,该扩展程序使用Content脚本呈现一个React组件。 React组件是一个工具栏,其中包含子工具,供用户在浏览页面时使用。我将Material UI用作工具栏及其所有其他子组件,弹出窗口等的组件库和样式解决方案。

什么有效

使用div," Extension"作为mountNode,将根React组件注入页面即可。

content.js(Chrome内容脚本)

1
2
3
var app = $('')
app.prependTo('body');
render(<App />, document.getElementById('Extension'))

app.js(主要React组件)

Material UI也能够生成新的样式对象,并将css样式应用于页面上的子组件。所以这一切都很好

(资源)

1
2
3
4
5
6
const styles = {
  root: {
    display:"block",
    color:"red",
  },
};

(生成)

1
2
3
4
.App-root-1 {
  color: red;
  display: block;
}

问题

由于我在chrome中使用内容脚本,因此具有常规css选择器的Facebook之类的网站会尝试覆盖Material UI中的样式。 CSS属性也有可能从React工具栏泄漏到主页中。

中途解决方案

我当前的解决方案是使用react-shadow围绕React组件设置样式范围,并将其与页面的其余部分隔离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React from"react";
import PropTypes from 'prop-types';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import { withStyles } from '@material-ui/core/styles';

import ShadowDOM from 'react-shadow';
import ToolBar from './ToolBar'

const theme = createMuiTheme({
  palette: {
    primary: {
      main: '#ef5350',
    },
    type: 'light'
  },
  status: {
    danger: 'orange',
  },
});

const styles = {
  root: {
    display:"block",
    color:"red",
  },
};

class App extends React.Component {

  constructor(props) {
      super(props);
 
      this.state = { open: false }
  }

  render () {
    const { classes } = this.props;

    return (
      <ShadowDOM>
         
              <MuiThemeProvider theme={theme}>
                  <ToolBar />
              </MuiThemeProvider>
             
      </ShadowDOM>
    )
  }
};

App.propTypes = {
  classes: PropTypes.object,
};

export default withStyles(styles)(App)

当我这样做时," Material-UI"生成的主题不适用于工具栏,剩下的是默认的行内样式应用于" Extension" div(上面定义为content.js)。

使用material-ui包中的createMuiTheme(options)生成主题:

此功能成功,并且使用了以下主题来应用主题:

1
<MuiThemeProvider theme={theme}>

我可以确认createMuiTheme(options)
之所以起作用,是因为主题的生成的样式表被添加为页面标记中的最后一个标记,如图所示:

标记包含MUI生成的样式

我需要发生什么

因为我的React工具栏元素位于#shadow-root内部,所以它无法从MUI生成的类中接收样式,因为它们包含在主DOM树的主标记中。我相信,呈现时,它会将其提供的样式表附加到主DOM树的顶部,而不是#shadow-root(需要在此处放置以便样式在本地应用)。参见下图:

中的MUI样式表,但需要将其移至#shadow-root

因此,我正在寻找一种解决方案,以将Material UI生成的样式表放置在#shadow-root下,以便它们在我的React工具栏组件上应用正确的样式。

1.)是否可以通过作用域访问影子DOM,或在类名前加上诸如:host之类的前缀?

2.)是否有办法锁定由react-shadow创建的#shadow-root,以便当附加样式表时,将其强制附加到影子根?

3.)我仍然对React,Javascript和创建Chrome扩展程序缺乏经验。也许我已经错过了一个非常简单的解决方案?

任何帮助表示赞赏。


我在打包Web应用程序以与另一个框架集成时遇到了同样的问题,这要求将React应用程序导出为Web组件。在像react-jss这样的库中苦苦挣扎了一段时间之后,没有运气,我终于在今天早上遇到了@ shawn-mclean的Stack Overflow答案。我把头撞在桌子上时看到了你的问题,想继续讲对我有用的东西。

总而言之,我首先必须创建Web组件

WCRoot.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react';
import ReactDOM from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';
import App from './App';

class WCRoot extends HTMLElement {

    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const mountPoint = document.createElement('span');
        // first we need to store a reference to an element INSIDE of the shadow root        
        const reactRoot = shadowRoot.appendChild(mountPoint);

        // now we use that saved reference to create a JSS configuration
        const jss = create({
            ...jssPreset(),
            insertionPoint: reactRoot
        });

        // finally we need to wrap our application in a StylesProvider
        ReactDOM.render(
            <StylesProvider jss={jss}>
                <App />
            </StylesProvider>,
            mountPoint);
    }
}
customElements.define('wc-root', WCRoot);

接下来,我将index.js设置为呈现而不是

index.js

1
2
3
4
5
6
7
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// eslint-disable-next-line
import WCRoot from './WCRoot';

ReactDOM.render(<wc-root></wc-root>, document.getElementById('root'));

让我感到惊讶的是,我们根本不需要修改MuiThemeProvider ...它只是获取了StylesProvider提供的现有JSS上下文。我相信您应该能够将content.js修改为如下所示:

content.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var app = $('')
app.prependTo('body');

var shadowRoot = this.attachShadow({ mode: 'open' });
var mountPoint = document.getElementById('Extension');
var reactRoot = shadowRoot.appendChild(mountPoint);

// see code blocks above for the import statements for `create` and `jssPreset`
var jss = create({
    ...jssPreset(),
    insertionPoint: reactRoot
});

render(
    <StylesProvider jss={jss}>
        <App />
    </StylesProvider>,
    mountPoint);

您可能需要查看ShadowDOM的include属性,该属性允许您在其中使用本地作用域的样式。

像这样使用(示例来自其github):

1
2
3
<ShadowDOM include={['css/core/calendar.css', props.theme]}>
     Calendar for {props.date}
</ShadowDOM>