React 组件的 Material UI 主题不在 Shadow DOM 的本地范围内

Posted

技术标签:

【中文标题】React 组件的 Material UI 主题不在 Shadow DOM 的本地范围内【英文标题】:React component's Material UI theme not scoped locally to Shadow DOM 【发布时间】:2019-01-20 18:56:21 【问题描述】:

简介

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

什么有效

使用 div 将根 React 组件注入页面可以正常工作,“Extension”作为 mountNode。

content.js(Chrome 内容脚本)

var app = $('<div id="Extension" style="position: fixed; display: block; bottom: 0px; left: 0px; width: 100vw; height: 48px; background: grey; z-index: 99999"></div>')
app.prependTo('body');
render(<App />, document.getElementById('Extension'))

app.js(主要反应组件)

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

(来源)

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

(生成)

.App-root-1 
  color: red;
  display: block;

问题

因为我在 chrome 中使用内容脚本,所以像 Facebook 这样带有通用 css 选择器的网站会尝试覆盖 Material UI 中的样式。 css 属性也有可能从 React 工具栏泄漏到主页面中。

半途而废

我当前的解决方案是使用react-shadow 来限定 React 组件周围的样式,并使其与页面的其余部分隔离。

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>
          <div id="Toolbox">
              <MuiThemeProvider theme=theme>
                  <ToolBar />
              </MuiThemeProvider>
          </div>    
      </ShadowDOM>
    )
  
;

App.propTypes = 
  classes: PropTypes.object,
;

export default withStyles(styles)(App)

当我这样做时,从“Material-UI”生成的主题不适用于工具栏,我只剩下默认的内联样式应用于“扩展”div(上面定义,content.js )。

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

此功能成功,主题应用使用:

<MuiThemeProvider theme=theme>

我可以确认createMuiTheme(options) & &lt;MuiThemeProvider/&gt; 正在工作,因为主题生成的样式表被添加为页面&lt;head&gt; 标记中的最后一个标记,如图所示:

&lt;head&gt; tag contains MUI generated styles

我需要做什么

因为我的 React 工具栏元素在 #shadow-root 内,它无法从 MUI 生成的类中接收样式,因为它们包含在主 DOM 树中的主 &lt;head&gt; 标记中。我相信当 &lt;MuiThemeProvider/&gt; 被渲染时,它会将其提供的样式表附加到 ma​​in DOM 树的顶部,NOT #shadow-root(它们需要在哪里,以便样式在本地应用)。见下图:

MUI Stylesheets in &lt;/head&gt;, but they need moved to #shadow-root

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

1.) 有没有办法让影子 DOM 具有 &lt;MuiThemeProvider/&gt; 范围,或者在类名前加上 :host 之类的前缀?

2.) 有没有办法锁定使用react-shadow 创建的#shadow-root,这样当&lt;MuiThemeProvider/&gt; 附加样式表时,它们会被强制附加到影子根目录?

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

感谢任何帮助。

【问题讨论】:

见How to really isolate stylesheets in the Google Chrome extension? @wOxxOm,感谢您的回复。大部分主题都提到了使用 iFrame 进行隔离,但我已经尝试过了。我有需要在框架边界之外显示的 UI 元素。不过,还讨论了其他主题,这可能会有所帮助。我一直在看material-ui's CSS in JS article。可以在#shadow-root 中为 JSS Provider 设置自定义 JSS 插入点。 --- Material UI - CSS in JS @BradenPreston 你找到解决方案了吗?我正在努力实现同样的目标。 @BradenPreston 兄弟,你发现如何解决这个问题了吗?我遇到了完全相同的问题... 【参考方案1】:

我在打包我的 Web 应用程序以与另一个框架集成时遇到了同样的问题,这需要将我的 React 应用程序导出为 Web 组件。在与react-jss 之类的库苦苦挣扎后没有运气,今天早上我终于通过@shawn-mclean 遇到了this Stack Overflow answer。我在用头撞桌子的时候看到了你的问题,想把对我有用的东西传授给我。

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

WCRoot.js

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 设置为渲染&lt;wc-component&gt;&lt;/wc-component&gt; 而不是&lt;App /&gt;

index.js

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

var app = $('<div id="Extension" style="position: fixed; display: block; bottom: 0px; left: 0px; width: 100vw; height: 48px; background: grey; z-index: 99999"></div>')
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);

【讨论】:

对于未来的读者,这无需创建自定义 WebComponent 即可工作 这适用于在 shadow DOM 子树中呈现的 MUI 组件,但那些使用门户(如 Dialog)的组件将无法设置样式。参考:github.com/mui-org/material-ui/issues/17473

以上是关于React 组件的 Material UI 主题不在 Shadow DOM 的本地范围内的主要内容,如果未能解决你的问题,请参考以下文章

如何在样式化组件中访问 Material-ui 的主题

更改自定义主题 Material-UI

React-Router 和 Material-UI:根据路由应用自定义主题

在样式化组件中使用 Material-ui 主题

Material UI 嵌套主题提供程序与Styles HOC 中断

酶不模拟 React Material-UI v1 上的更改事件 - 选择组件