如何通过材料UI样式的组件API(Typescript)使用自定义道具和主题?

时间:2020-10-05 17:04:22

标签: reactjs typescript material-ui

我正在尝试使用Material UI样式的组件API来注入自定义主题和一些道具到特定的自定义元素。

我有一个自定义主题或一些道具,但不能同时使用

自定义主题

我的自定义主题在单独的文件中定义如下...

export interface ITheme extends Theme {
    sidebar: SideBarTheme;
}

type SideBarTheme = {
    // Various properties
};

const theme = createMuiTheme(
    {
        // Normal theme modifications
    },
    {
        sidebar: {
            // Custom theme modifications
        },
    } as ITheme
);

然后我将其与Material-UI样式的组件API结合使用,如下所示。...

const Profile = styled(Button)(({ theme }: { theme: ITheme }) => ({
    backgroundColor: theme.sidebar.profile.backgroundColor,
    paddingTop: 20,
    paddingBottom: 20
}));

在我的jsx中使用<Profile />可以正常工作。

道具

对于道具,我本质上是使用示例here

的精确副本
interface MyButtonProps {
    color: string;
}

const Profile = styled( ({ color, ...other }: MyButtonProps & Omit<ButtonProps, keyof MyButtonProps>) => (
    <Button {...other} />
  ))({
    backgroundColor: (props: MyButtonProps) => props.color,
    paddingTop: 20,
    paddingBottom: 20
});

使用<Profile color="red"/>

时,这在我的jsx上正常工作

如何使这两种字体与打字稿一起使用?

我从字面上试图像下面这样组合它们,但道具不会消失...

const Profile = styled( ({ color, ...other }: MyButtonProps & Omit<ButtonProps, keyof MyButtonProps>) => (
    <Button {...other} />
  ))(({ theme }: { theme: ITheme }) => ({
    backgroundColor: (props: MyButtonProps) => props.color,
    color: theme.sidebar.profile.backgroundColor,
    paddingTop: 20,
    paddingBottom: 20
}));

我尝试做的其他任何事情都只会破坏TypeScript。 Material-ui styled函数也太复杂了,我无法理解。

非常感谢,

2 个答案:

答案 0 :(得分:2)

这个问题也几乎把我逼疯了,但我已经设法用这种方法让它工作了:

import React from 'react'
import { styled, Theme, withTheme } from '@material-ui/core/styles'
import { Box, BoxProps } from '@material-ui/core'

interface SidebarSectionProps {
  padded?: boolean
  theme: Theme
}

export const SidebarSectionComponent = styled(
  (props: SidebarSectionProps & BoxProps) => <Box {...props} />
)({
  marginBottom: ({ theme }: SidebarSectionProps) => theme.spacing(3),
  paddingLeft: ({ theme, padded }: SidebarSectionProps) =>
    padded ? theme.spacing(3) : 0,
  paddingRight: ({ theme, padded }: SidebarSectionProps) =>
    padded ? theme.spacing(3) : 0
})

export const SidebarSection = withTheme(SidebarSectionComponent)

答案 1 :(得分:1)

我不确定使用样式组件或材质styled HOC是否有意义,因为它们对于设置不依赖于道具的固定样式更好。

但是您可以通过style组件的Button道具来设置所需的样式。

这似乎是可行的解决方案:

interface AddedProps {
  theme: Theme;
  color: string;
};

type InnerProps = AddedProps & Omit<ButtonProps, keyof AddedProps>

const InnerProfile = ({color, theme, ...other}: InnerProps) => (
  <Button
    {...other}
    style={{
      ...other.style,
      paddingTop: 20,
      paddingBottom: 20,
      backgroundColor: color,
      color: (theme as MyTheme).sidebar?.profile?.backgroundColor,
    }}
  />
)

export const Profile = withTheme( InnerProfile );

我们说Profile需要两个AddedProps:颜色和主题。我们写InnerProfile假设那些道具存在。然后,在一个单独的步骤中,我们应用withTheme HOC,以便设置theme属性。 color属性是必需的,使用<Profile />时必须由您设置。

现在是因为这种疯狂的疯狂行为:

color: (theme as MyTheme).sidebar?.profile?.backgroundColor

发生了什么事,withTheme知道它注入了一个主题,但它不知道它注入了您的特定主题,所以打字稿不知道该主题完全具有属性sidebar,因为这是您添加的自定义属性。您需要通过执行(theme as MyTheme)来告诉打字稿“假设该主题具有MyTheme的属性”。您可以通过在创建自定义主题的对象上使用MyTheme来获得该类型typeof

由于我无权访问您的自定义主题对象,因此我要做的是定义一个接口,该接口具有所需的属性路径,但所有选项都是可选的。看起来像:

interface MyTheme extends Theme {
  sidebar?: {
    profile?: {
      backgroundColor?: string;
    } 
  }
}

在访问?.时,我使用了打字稿sidebar?.profile?.backgroundColor表示法,以避免“对象可能未定义”错误。