在 Material ui 中将类作为 prop 传递时如何输入类

Set*_*ske 4 typescript reactjs material-ui react-proptypes

我发现自己在 Material ui 中编写了一些通用组件,其中样式实际上是在父组件或祖父组件中定义的。例如:

const GenericDescendant = (props: DescendantProps) => {
  const { classesFromAncestor, ...other } = props
  return <Button className={classesFromAncestor} {...props}>Text here</Button>
}
Run Code Online (Sandbox Code Playgroud)

从祖先那里,我创建了一个useStylesandclasses对象并将其传递下来:

const useDescendantStyles = makeStyles({
  selector: { 
    border: '1px solid green',
    // more customized styles
  }
})

const Ancestor = () => {
  const descendantClasses = useDescendantStyles()
  return <GenericDescendant classesFromAncestor={descendantClasses} />
}
Run Code Online (Sandbox Code Playgroud)

或者另一种方式,我可以用来withStyles包装后代中使用的组件:

const GenericDescendant = (props: DescendantProps) => {
  const { tooltipClassesFromAncestor, buttonClassesFromAncestor, ...other } = props
  const CustmoizedTooltip = withStyles(tooltipClassesFromAncestor)(Tooltip);
  const CustomButton = withStyles(buttonClassesFromAncestor)(Button)
  return (
    <CustmoizedTooltip>
      <CustomButton>Text here</CustomButton>
    </CustmoizedTooltip>
  )
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我不会调用useStylesAncestor,我只会像这样传递对象:

const Ancestor = () => {
  return <GenericDescendant classesFromAncestor={{
    selector: { 
      border: '1px solid green',
      // more customized styles
    }
  }} />
}
Run Code Online (Sandbox Code Playgroud)

我不想将整个组件包装GenericDescendant在 a 中withStyles,因为这样我就无法确定哪个组件正在采用从祖先传递的自定义样式。

这些情况略有不同,但我可以采用任一途径来允许自定义祖先的通用后代。但是,当将自定义样式作为道具传递时,我不知道如何classesFromAncestorDescendantProps. 它实际上可以采取任何形式,并且该形式直到运行时才会被知道。我不想将其输入为any. 定义作为 prop 传递的类类型的最佳方法是什么?

Lin*_*ste 5

我一直在玩这个,有很多可能的设置。您可以通过传递单个元素来设置material-ui组件的样式,也可以传递一个与该组件可用的不同选择器相对应的string className对象。classes大多数(但不是全部)Material 组件的主要选择器是root。AButton接受类、、root和许多其他类labeltext

当您调用makeStyles您创建的内容object时,键是选择器,基本上与您的输入相同的键,值是string生成的类的类名称。您可以将此类型导入为ClassNameMapfrom @material-ui/styles。它本质上是Record<string, string>,但有一个可选的泛型,可以让您限制键。

export type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;
Run Code Online (Sandbox Code Playgroud)

makeStyles您传递给or的第一个参数withStyles是样式属性的键控对象。它可以简化为,Record<string, CSSProperties>或者您可以从material-ui导入复杂类型,其中包括对函数的支持等。该类型Styles<Theme, Props, ClassKey>采用三个可选泛型,其中Theme是主题的类型,Props是组件自己的道具,也是ClassKey受支持的选择器。 Theme和主要包含在内,以便您可以将样式映射为或 的Props函数。themeprops

如果我们想同时向多个组件提供样式,一种简单的方法是为每个组件创建一个带有一个键的样式对象。你可以随意称呼它们。然后我们将每个样式作为 传递className给组件。

const MyStyled = withStyles({
  button: {
    border: "green",
    background: "yellow"
  },
  checkbox: {
    background: "red"
  }
})(({ classes }: { classes: ClassNameMap }) => (
  <Button className={classes.button}>
    <Checkbox className={classes.checkbox} />
    Text Here
  </Button>
));
Run Code Online (Sandbox Code Playgroud)

该设置允许任意数量的组件处于任意级别的嵌套。

我尝试了制作一个可以包裹外部组件的高阶组件。您可以使用它作为起点,并根据您的需要进行调整。

const withAncestralStyles = <Props extends { className?: string } = {}>(
  Component: ComponentType<Props>,
  style: Styles<DefaultTheme, Omit<Props, "children">, string>
) => {
  const useStyles = makeStyles(style);

  return (
    props: Omit<Props, "children"> & {
      children?: (styles: ClassNameMap) => ReactNode;
    }
  ) => {
    console.log(props);
    const classes = useStyles(props);
    const { children, className, ...rest } = props;
    return (
      <Component {...rest as Props} className={`${className} ${classes.root}`}>
        {children ? children(classes) : undefined}
      </Component>
    );
  };
};
Run Code Online (Sandbox Code Playgroud)

基本上,该类classes.root将自动应用于className父级,而所有样式都作为道具传递给子级(但不应用)。我在React.cloneElement工作中遇到了麻烦,所以我要求children组件的 是function接收classes对象的。

const StyledButton = withAncestralStyles(Button, {
  root: {
    border: "green",
    background: "yellow"
  },
  checkbox: {
    background: "red"
  }
});

const MyComponent = () => (
  <StyledButton onClick={() => alert('clicked')}>
    {(classes) => <><Checkbox className={classes.checkbox}/>Some Text</>}
  </StyledButton>
);
Run Code Online (Sandbox Code Playgroud)

代码沙箱

此方法无法在您的Tooltip示例中正常工作,因为Tooltip它是没有root选择器的组件之一,需要采用不同的样式来定位弹出窗口。