样式已被Material-UI样式覆盖

时间:2020-02-12 00:55:07

标签: javascript reactjs material-ui react-component jss

序言

几天前,我问了similar question,虽然本质上是相关的,但我认为解决方案最终会有所不同,所以我在不同的话题中再次提问。

CodeSanbox Example(已更新为反映已接受的答案)

问题:

我希望通过className道具传递的所有外部样式都比我的自定义组件内部样式具有更高的特异性。这样,使用它的人就可以调整边距和填充。但是,我组件的默认内部样式会覆盖我的外部样式,我希望反之亦然。

You can see my external style is lower speceficity

详细信息:

我正在创建一个基于material-ui的自定义组件库。我想使自定义组件api与@material-ui类似,以便我们的开发人员可以更轻松地使用它们。我正在构建的每个组件都有其自己的内部样式,这些默认样式覆盖默认的material-ui样式,在这种情况下,它定义为类button。另外,像@material-ui一样,我接受颜色道具<TestButton color={'default'}/>。最后,如果需要,我希望可以使用外部样式覆盖自定义按钮。我正在使用clsx库来构建className字符串。

代码:

import React, { useState } from "react";
import { makeStyles } from "@material-ui/styles";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import { Button } from "@material-ui/core";
import clsx from "clsx";

const useAppStyles = makeStyles({
  gButton: { margin: "150px" }
});

export default function App() {
  const classes = useAppStyles();

  return (
    <div className={classes.example}>
      <div className={classes.separator}>
        <div>Buttons:</div>
        <TestButton
          className={classes.gButton}
          color={"default"}
        >
          Default
        </TestButton>
        <TestButton
          className={classes.gButton}
          color={"primary"}
        >
          Primary
        </TestButton>
    </div>
  );
}

function TestButton(props) {

  const classes = GrangeButtonStyles();
  let color = props.color === 'default' ? classes.default : classes.primary 

  const GrangeButtonStyles = makeStyles({
    button: {
     height: "45px",
     padding: "13px 30px 13px 30px",
     borderRadius: "5px",
     border: "none",
     margin: "15px",
    },
    default: {
     backgroundColor: "black",
     border: 'solid #2e7d32 1px',
     color: "white",
    },
    primary: {
     backgroundColor: 'white',
     color: 'black',
     fontFamily: 'Montserrat, sans-serif',
     border: 'solid black 1px',
    }
  });

  return (
    <Button
      className={clsx(classes.button, color, props.className)}
      variant="contained"
      disabled={props.disabled}
      disableElevation
    >
      {props.children}
    </Button>
  );
}

注意:

在这个问题和代码沙箱示例中,我大大简化了空间代码。请不要发表评论,因为这个例子,您认为我在做什么并没有意义。

2 个答案:

答案 0 :(得分:1)

来自https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity

当多个声明具有相同的特异性时,将在CSS中找到的最后一个声明应用于该元素。

因此,在您要在自定义组件(例如TestButton)和使用该组件的代码中定义CSS类的情况下,特异性取决于这些CSS类在{ {1}}元素。此顺序由index that is set when makeStyles is called确定,因此以后调用<head>定义的类将稍后出现在makeStyles元素中,因此具有更高的特异性。

在您的示例中有两个问题:

  1. <head>是在使用它的代码之后定义的,因此在定义样式的TestButton调用之后,这些样式旨在覆盖makeStyles中的样式。由于对TestButton的{​​{1}}调用首先发生,因此相应的CSS类将首先在makeStyles元素中。但是,在实际使用中,gButton(您的自定义组件)将在单独的文件中定义并导入。由于导入必须位于最顶部,因此在使用导入的组件对文件中的任何<head>调用之前,将执行导入文件的所有<{1}}调用。

  2. TestButton的{​​{1}}调用不是在顶层完成的。相反,它是在makeStyles函数内部完成的,这意味着它将在呈现makeStyles时而不是在导入makeStyles时执行。对TestButton的调用应始终在顶层,而不是嵌套在组件函数中。另一个较小的问题是从TestButton返回的变量的名称(在您的示例中为TestButton)。由于TestButton返回custom hook,因此您应始终使用以“ use”开头的名称(例如makeStyles)。这样可以确保eslint rules for hooks将其识别为钩子,并警告您任何钩子滥用。

相关答案和参考文献:

答案 1 :(得分:-1)

<TestButton className={classes.gButton} color={"default"}>

// should be

<TestButton classes={{button:classes.gButton}} color={"default"}>

// and

<TestSelect className={classes.gSelect}

// should be

<TestSelect className={{dropdown:classes.gSelect}}

^在处理material-ui的样式解决方案时,不要将“ className”传递给组件(仅将此道具放在DOM元素上!!!)

function TestButton(props) {
  const classes = GrangeButtonStyles();
  ...

// should be


function TestButton(props) {
  const classes = GrangeButtonStyles(props);
  ...


^这将导致 prop classes.button(看起来像jss-1233)与GrangeButtonStyles中出现的按钮类合并,因此类现在将看起来像这样:

{
  button: 'jss-7382 jss-1233' <- jss-1233 is the classname that got generated in the previous component
}

    <Button
      className={clsx(classes.button, color, props.className)}

// should be

    <Button
      classes={{root:classes.button)}

^ See material-ui docs for button

实际上不幸的是,material-ui将其引用转发给DOM元素,而无需检查className ,因为这允许人们将className放在material-ui组件上,并且“有点”有用。他们应该真正添加警告以改为使用classes 感谢您的评论者纠正了我的错误,我仍然喜欢传递classes而不是className的冗长,因为将两者混合会导致混乱!

编辑:

我还注意到您正在弄乱TestSelect中的样式-始终牢记这一点-不要将className作为Material-ui组件的道具,只能传递classes 。如果要将样式从父组件传递到子组件,则必须使用相同的键:

让我们尝试一个例子,我知道,这些东西很难理解,但最终它会“点击”:

const useParentStyles = makeStyles({
   childStyles: { ... some jss }
   childStyles2: { ... some jss }
});

const Parent = props => {
   const classes = useParentStyles(props);
   return <Child classes={{root:classes.childStyles,someOtherKey:classes.childStyles2}}/> <- object with a keys of "root" and "someOtherKey"
}

const useChildStyles = makeStyles({
  root: { ... some jss } <- root
  someOtherKey: { ... some jss } <- someOtherKey 
});

const Child = props => {
   const classes = useChildStyles(props); <- classes have been merged together
   return <div className={classes.root}>...</div>
}