我发现自己在 Material UI 中编写了一些通用组件,其中样式实际上是在父组件或祖父组件中定义的。例如:
const GenericDescendant = (props: DescendantProps) => {
const { classesFromAncestor, ...other } = props
return <Button className={classesFromAncestor} {...props}>Text here</Button>
}
我从 Ancestor 那里创建了一个 useStyles
和 classes
对象并将其向下传递:
const useDescendantStyles = makeStyles({
selector: {
border: '1px solid green',
// more customized styles
}
})
const Ancestor = () => {
const descendantClasses = useDescendantStyles()
return <GenericDescendant classesFromAncestor={descendantClasses} />
}
或者另一种方式,我可以使用 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>
)
}
在这种情况下,我不会在 Ancestor 中调用 useStyles
,我会像这样传递对象:
const Ancestor = () => {
return <GenericDescendant classesFromAncestor={{
selector: {
border: '1px solid green',
// more customized styles
}
}} />
}
我不想将整个 GenericDescendant
包装在 withStyles
中,因为这样我就无法确定哪个组件正在采用从祖先。
情况略有不同,但我可以选择任何一种方式来允许自定义祖先的通用后代。但是,当将自定义样式作为 Prop 传递时,我不知道如何在 DescendantProps
中正确键入 classesFromAncestor
Prop 。它实际上可以采用任何形式,并且直到运行时才知道该形式。我不想将其键入 any
。定义作为 prop 传递的类类型的最佳方法是什么?
最佳答案
我一直在玩这个,有很多可能的设置。您可以通过传递单个 string
来设置 material-ui 组件的样式。 className
你也可以传递 classes
的对象对应于该组件可用的不同选择器。大多数(但不是全部) Material 组件的主要选择器是 root
. Button
接受类(class) root
, label
, text
, 和 many others .
当您调用 makeStyles
时你创建的是一个 object
其中键是选择器,与您的输入键基本相同,值是 string
生成类的类名。您可以将此类型导入为 ClassNameMap
来自 @material-ui/styles
.它本质上是 Record<string, string>
,但有一个可选的泛型,可以让您限制键。
export type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;
您传递给 makeStyles
的第一个参数或 withStyles
是样式属性的键控对象。可以简化为Record<string, CSSProperties>
或者您可以从 material-ui 导入复杂类型,其中包括对功能等的支持。类型 Styles<Theme, Props, ClassKey>
采用三个可选泛型,其中 Theme
是你的主题类型,Props
是组件自己的 Prop ,ClassKey
再次是受支持的选择器。 Theme
和 Props
主要包含在内,以便您可以将样式映射为 theme
的函数或 props
.
如果我们想同时为多个组件提供样式,一种简单的方法是为每个组件创建一个带有一个键的样式对象。你可以随便称呼他们。然后我们将这些样式中的每一个作为 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>
));
该设置允许在任何级别的嵌套中使用任意数量的组件。
我尝试制作一个可以包裹外部组件的高阶组件。您可以以此为起点并根据您的需要进行调整。
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>
);
};
};
基本上,类 classes.root
将自动应用于 className
parent 的所有样式都作为 Prop 传递给 child (但未应用)。我在制作 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>
);
此方法不适用于您的 Tooltip
例如因为 Tooltip
是没有 root
的组件之一选择器,需要不同的样式以定位弹出窗口。
关于reactjs - 在 Material ui 中将它们作为 Prop 传递时如何键入类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65996439/