我如何将 Material-UI 托管样式应用于非 Material-ui、非反应元素?

Posted

技术标签:

【中文标题】我如何将 Material-UI 托管样式应用于非 Material-ui、非反应元素?【英文标题】:How would I apply Material-UI managed styles to non-material-ui, non-react elements? 【发布时间】:2020-03-31 02:27:03 【问题描述】:

我有一个应用程序,我在其中使用 Material UI 及其主题提供程序(使用 JSS)。

我现在正在合并fullcalendar-react,它并不是一个真正成熟的 React 库——它只是一个围绕原始完整日历代码的 React 组件包装器。

也就是说,我无法访问诸如渲染道具之类的东西来控制它如何设置元素的样式。

但是,它确实让您可以直接访问 DOM 元素,通过在渲染它们时调用的回调(例如,eventRender method)。

Here's a basic demo sandbox.

现在我想做的是让完整日历组件(例如,按钮)与我的应用程序的其余部分具有相同的外观和感觉。

这样做的一种方法是,我可以通过查看它使用的类名并相应地实现样式来手动覆盖所有样式。

或者 - I could implement a Bootstrap theme - as suggested in their documentation.

但这两种解决方案的问题在于:

    工作量很大 我会遇到同步问题,如果我更改了我的 MUI 主题并忘记更新日历主题,它们看起来会有所不同。

我想做的是:

神奇地将 MUI 主题转换为 Bootstrap 主题。 或者在 MUI 类名和日历类名之间创建一个映射,例如:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

我不介意必须按摩选择器等以使其工作(例如 - MUI 按钮有两个内部跨度,而完整日历只有一个)。这主要是关于我何时更改主题 - 不想在两个地方更改它。

使用类似 Sass 的 @extend 语法将是我的想法。我可以很容易地用 Sass 创建完整的日历 CSS - 但是 Sass 将如何访问 MuiTheme?

也许我可以采取相反的方法 - 告诉 MUI '嘿,这里的这些类名应该像这些 MUI 类一样设置样式'。

关于我将如何解决这个问题的任何具体建议?

【问题讨论】:

【参考方案1】:

这是我的建议(显然,这不是直截了当的)。从 MUI 主题中获取样式并使用 react-helmet 基于它生成 style 标签。为了很好地完成它,我创建了一个执行地图的“包装器”组件。我只实现了主要规则,但它可以扩展到所有其他规则。

这样,您在主题中所做的任何更改也会影响映射的选择器。

import React from "react";
import  Helmet  from "react-helmet";

export function MuiAdapter( theme ) 
  if (!theme.palette) 
    return <></>;
  
  return (
    <Helmet>
      <style type="text/css">`
          .fc-button-primary 
            background: $theme.palette.primary.main
          
          /* more styles go here */
      `</style>
    </Helmet>
  );

以及适配器的使用

<MuiAdapter theme=theme />

工作演示:https://codesandbox.io/s/reverent-mccarthy-3o856

【讨论】:

我喜欢这种想法。这个解决方案的问题是你基本上仍然会自己实现所有样式(即悬停颜色、填充、边框半径等)。例如,如果您决定按钮文本应加下划线,则需要记住将 text-decoration 属性添加到头盔。 我明白你的要求。我尝试采用不同的方法并操作 MUI style 标签并根据地图添加它们 .fc- 选择器,但它们在基本级别有冲突(例如 displaypadding 等)所以我不'即使您可以选择在 scss 中迁移它,也看不到它是如何工作的。例如:codesandbox.io/s/charming-sound-3xmj9 哈哈——现在我喜欢这个。稍后我会给它一个更好的外观。【参考方案2】:

您可以通过ref's 创建 MUI 类名和日历类名之间的映射。这可能不是某些人所说的“最佳实践”......但它是一个解决方案:)。 请注意,我将您的组件从功能组件更新为类组件,但您可以使用功能组件中的挂钩来完成此操作。

添加引用

ref 添加到您要设置为参考的MUI 元素中,在您的情况下为Button

<Button
  color="primary"
  variant="contained"
  ref=x => 
    this.primaryBtn = x;
  
>

还有一个 ref 到一个包装 div 您要映射到的组件周围。您不能将其直接添加到组件中,因为这样我们就无法访问children

<div
  ref=x => 
    this.fullCal = x;
  
>
  <FullCalendar
    ...
  />
</div>

地图类

componentDidMount() 添加您需要的任何逻辑来定位正确的DOM 节点(对于您的情况,我添加了typematchingClass 的逻辑)。然后在所有 FullCalendar DOM 节点上运行该逻辑并替换任何匹配的 classList

componentDidMount() 
  this.updatePrimaryBtns();


updatePrimaryBtns = () => 
  const children = Array.from(this.fullCal.children);
  // Options
  const type = "BUTTON";
  const matchingClass = "fc-button-primary";

  this.mapClassToElem(children, type, matchingClass);
;

mapClassToElem = (arr, type, matchingClass) => 
  arr.forEach(elem => 
    const  tagName, classList  = elem;
    // Check for match
    if (tagName === type && Array.from(classList).includes(matchingClass)) 
      elem.classList = this.primaryBtn.classList.value;
    

    // Run on any children
    const next = elem.children;
    if (next.length > 0) 
      this.mapClassToElem(Array.from(next), type, matchingClass);
    
  );
;

这可能有点笨拙,但它满足您在更新 Material UI 时的未来证明要求。它还允许您在将 classList 传递给元素时更改它,这有明显的好处。

注意事项

如果“映射到”组件 (FullCalendar) 更新了您定位的元素上的类(例如,它是否将 .is-selected 添加到当前按钮)或在安装后添加新按钮,那么您必须弄清楚一种跟踪相关更改并重新运行逻辑的方法。

我还应该提到(显然)更改类可能会产生意想不到的后果,例如破坏 UI,您必须弄清楚如何修复它们。

这是工作沙箱:https://codesandbox.io/s/determined-frog-3loyf

【讨论】:

这行得通。请注意,您不必转换为类组件 - 您可以使用挂钩来执行此操作。 你是对的,这可以通过功能组件中的钩子来完成。我已经更新了答案以反映这一点。 很好,非常务实的方法。但实际上并不可行,因为您总是需要参考。

以上是关于我如何将 Material-UI 托管样式应用于非 Material-ui、非反应元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何始终在有焦点的 Material-UI Button 组件上应用 focusVisible 样式?

如何在 material-ui 样式/JSS 中选择 id?

我如何设置作为prop传递的material-ui图标的样式

如何将 Nextjs + styled-components 与 material-ui 集成

Material-UI Typescript 如何在组件类中从 makeStyle() 创建样式实例

带有 Material-UI 的 SVG 图标:在哪里可以找到,如何设置样式?