如何使用 cssinjs/jss 在 shadow root 中挂载样式

How to mount styles inside shadow root using cssinjs/jss

我正在尝试在阴影 dom 中使用 https://material-ui.com/ 组件,并且需要一种方法将这些样式注入阴影 dom 中。默认情况下 material-ui,它在后台使用 jss 在页面头部注入样式。

这可能吗?谁能举个例子?

这就是我的 Web 组件的样子,它是一个呈现包含 material-ui 样式的 React 应用程序的 Web 组件。

import * as React from 'react';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

import { App } from '@myApp/core';

class MyWebComponent extends HTMLElement {
  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const mountPoint = document.createElement('span');
    const reactRoot = shadowRoot.appendChild(mountPoint);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    render(
      <StylesProvider jss={jss}>
        <App />
      </StylesProvider>,
      mountPoint
    );
  }
}
customElements.define('my-web-commponent', MyWebComponent);

将 jss 上的 insertionPoint 设置为影子根内的实际反应根将告诉 jss 将这些样式插入影子根内。

使用https://github.com/Wildhoney/ReactShadow创建影子dom(你也可以像前面的回答那样手动完成),我创建了一个封装逻辑的小WrapperComponent。

import root from 'react-shadow';
import {jssPreset, StylesProvider} from "@material-ui/styles";
import {create} from 'jss';
import React, {useState} from "react"

const WrappedJssComponent = ({children}) => {
  const [jss, setJss] = useState(null);
  
  function setRefAndCreateJss(headRef) {
    if (headRef && !jss) {
      const createdJssWithRef = create({...jssPreset(), insertionPoint: headRef})
      setJss(createdJssWithRef)
    }
  }
  
  return (
    <root.div>
      <head>
        <style ref={setRefAndCreateJss}></style>
      </head>
      {jss &&
      <StylesProvider jss={jss}>
        {children}
      </StylesProvider>
      }
    
    </root.div>
  )
}

export default WrappedJssComponent

然后你只需要包装你的应用程序,或者你想要隐藏在里面的应用程序部分 <WrappedJssComponenent><YourComponent></YourComponent></WrappedJssComponenent>

小心,某些 material-UI 组件将无法正常工作(我遇到了一些问题

  • ClickAwayListener,可能是因为它使用了父 dom,老实说,没有调查更多。
  • Popper,以及将尝试使用 document.body 作为容器的所有内容将无法访问影子节点中定义的 jss。你应该给阴影 dom 内的一个元素作为容器。

在 MUI 中使用 @material-ui/core/CssBaseline 时,也会使用 emotion 样式。为了同时支持旧版 jssemotion,您可以使用 CacheProvider 扩展上面接受的答案,如下所示:

import ReactDOM from 'react-dom/client'
import App from './App'
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

class ReportComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    const mountPoint = document.createElement('div');
    const emotionPoint = this.shadowRoot!.appendChild(document.createElement('div'));
    const emotionCache = createCache({
      key: 'report-component',
      container: emotionPoint
    });

    const reactRoot = this.shadowRoot!.appendChild(mountPoint);
    const root = ReactDOM.createRoot(reactRoot);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    root.render(
      <StylesProvider jss={jss}>
        <CacheProvider value={emotionCache}>
          <App />
        </CacheProvider>
      </StylesProvider>
    );
  }
}
customElements.define('report-component', ReportComponent);