在 material ui 中将它们作为道具传递时如何键入 类
How to type classes when passing them as a prop in material ui
我发现自己在 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
中,因为这样我就无法确定哪个组件正在采用从祖先传递的自定义样式。
情况略有不同,但我可以选择任何一种方式来允许自定义祖先的通用后代。但是,当将自定义样式作为道具传递时,我不知道如何在 DescendantProps
中正确键入 classesFromAncestor
道具。它实际上可以采用任何形式,并且直到运行时才知道该形式。我不想将其键入 any
。定义作为 prop 传递的 类 类型的最佳方法是什么?
如评论中所述,如果您要传递 makeStyles
的结果,则它是 Record<string, string>
。但是,如果您要传递 CSS-in-JS 对象,那么您可以这样做:
import { ListItemClassKey } from '@material-ui/core/ListItem';
type RecursiveCSSProperties = {
[key: string]: React.CSSProperties | RecursiveCSSProperties;
}
type MuiComponentClasses<T extends string> = {
[key in T]?: React.CSSProperties | RecursiveCSSProperties;
};
const myListItemClasses: MuiComponentClasses<ListItemClassKey> = {
secondaryAction: {},
divider: {
margin: 56,
top: 0,
"& .myClass": {
background: 'red',
textAlign: 'center'
}
}
}
输出结构应说明为什么需要递归 React.CSSProperties
以获得 CSS 的所有可能嵌套,这些嵌套可以使用 CSS-in-JSS 库进行。第二种类型 MuiComponentClasses
是可选的,但允许您从相关组件中提取键列表,这样您就可以只在 class 对象的顶层定位正确的键。
我一直在玩这个,有很多可能的设置。您可以通过传递单个 string
className
来设置 material-ui 组件的样式,您还可以传递对应于可用的不同选择器的 classes
对象对于那个组件。大多数(但不是全部)Material 组件的主要选择器是 root
。 Button
接受 classes root
、label
、text
和 many others.
当您调用 makeStyles
时,您创建的是一个 object
,其中的键是选择器,与您输入的键基本相同,值是 string
class 生成的 classes 的名称。您可以将此类型作为 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
是组件自己的属性,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>
);
};
};
基本上,class classes.root
将自动应用于父项的 className
,同时所有样式都作为道具传递给子项(但未应用)。我在 React.cloneElement
工作时遇到了问题,所以我要求 ui 组件的 children
是接收 classes
对象的 function
。
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
选择器的组件之一,需要采用不同的样式来定位弹出框。
我发现自己在 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
中,因为这样我就无法确定哪个组件正在采用从祖先传递的自定义样式。
情况略有不同,但我可以选择任何一种方式来允许自定义祖先的通用后代。但是,当将自定义样式作为道具传递时,我不知道如何在 DescendantProps
中正确键入 classesFromAncestor
道具。它实际上可以采用任何形式,并且直到运行时才知道该形式。我不想将其键入 any
。定义作为 prop 传递的 类 类型的最佳方法是什么?
如评论中所述,如果您要传递 makeStyles
的结果,则它是 Record<string, string>
。但是,如果您要传递 CSS-in-JS 对象,那么您可以这样做:
import { ListItemClassKey } from '@material-ui/core/ListItem';
type RecursiveCSSProperties = {
[key: string]: React.CSSProperties | RecursiveCSSProperties;
}
type MuiComponentClasses<T extends string> = {
[key in T]?: React.CSSProperties | RecursiveCSSProperties;
};
const myListItemClasses: MuiComponentClasses<ListItemClassKey> = {
secondaryAction: {},
divider: {
margin: 56,
top: 0,
"& .myClass": {
background: 'red',
textAlign: 'center'
}
}
}
输出结构应说明为什么需要递归 React.CSSProperties
以获得 CSS 的所有可能嵌套,这些嵌套可以使用 CSS-in-JSS 库进行。第二种类型 MuiComponentClasses
是可选的,但允许您从相关组件中提取键列表,这样您就可以只在 class 对象的顶层定位正确的键。
我一直在玩这个,有很多可能的设置。您可以通过传递单个 string
className
来设置 material-ui 组件的样式,您还可以传递对应于可用的不同选择器的 classes
对象对于那个组件。大多数(但不是全部)Material 组件的主要选择器是 root
。 Button
接受 classes root
、label
、text
和 many others.
当您调用 makeStyles
时,您创建的是一个 object
,其中的键是选择器,与您输入的键基本相同,值是 string
class 生成的 classes 的名称。您可以将此类型作为 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
是组件自己的属性,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>
);
};
};
基本上,class classes.root
将自动应用于父项的 className
,同时所有样式都作为道具传递给子项(但未应用)。我在 React.cloneElement
工作时遇到了问题,所以我要求 ui 组件的 children
是接收 classes
对象的 function
。
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
选择器的组件之一,需要采用不同的样式来定位弹出框。