在 Material-UI 中使用条件样式与 styled vs JSS

IT技术 reactjs material-ui jss emotion
2021-04-28 09:18:33

我正在使用 Material-UI v5 并尝试迁移到 usingstyled而不是makeStyles因为现在似乎这是“首选”方法。我知道 usingmakeStyles仍然有效,但我正在尝试采用新的样式解决方案。

我有一个代表导航链接的列表项列表,我想突出显示当前选择的项。这是我如何使用makeStyles

interface ListItemLinkProps {
    label: string;
    to: string;
}

const useStyles = makeStyles<Theme>(theme => ({
    selected: {
        color: () => theme.palette.primary.main,
    },
}));

const ListItemLink = ({ to, label, children }: PropsWithChildren<ListItemLinkProps>) => {
    const styles = useStyles();

    const match = useRouteMatch(to);
    const className = clsx({ [styles.selected]: !!match });

    return (
        <ListItem button component={Link} to={to} className={className}>
            <ListItemIcon>{children}</ListItemIcon>
            <ListItemText primary={label} />
        </ListItem>
    );
};

(注意这里我使用clsx来确定是否selected应该将样式应用于ListItem元素。)

我如何使用styled这是我目前想到的(注意:接口ListItemLinkProps没有改变所以我没有在这里重复):

const LinkItem = styled(ListItem, {
    shouldForwardProp: (propName: PropertyKey) => propName !== 'isSelected'
})<ListItemProps & LinkProps & { isSelected: boolean }>(({ theme, isSelected }) => ({
    ...(isSelected && { color: theme.palette.primary.main }),
}));

const ListItemLink = ({ to, label, children }: PropsWithChildren<ListItemLinkProps>) => {
    const match = useRouteMatch(to);

    return (
        // @ts-ignore
        <LinkItem button component={Link} to={to} isSelected={!!match}>
            <ListItemIcon>{children}</ListItemIcon>
            <ListItemText primary={label} />
        </LinkItem>
    );
};

所以,关于这个有两个问题:

  1. 这是做条件风格的最佳方式吗?

  2. 另一个问题是我无法计算出正确的styled声明类型-由于其类型的声明方式,我必须将// @ts-ignore注释放在上面LinkItem

1个回答

Material-UI v5 使用 Emotion 作为默认样式引擎,并始终styled在内部使用,以使想要使用 styled-components 而不是 Emotion 的人更容易不必将两者都包含在包中。

尽管styledAPI 在很多用例中都可以正常工作,但对于这个特定用例来说似乎很笨拙。有两个主要选项可以提供更好的 DX。

一种选择是使用所有 Material-UI 组件上可用的新sx prop(并且Box 组件可用于包装非 MUI 组件以访问sx功能)。下面是对演示此方法的 List 演示之一的修改(自定义ListItemButton模拟您的角色ListItemLink):

import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
  ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";

const ListItemButton = ({
  selected = false,
  ...other
}: ListItemButtonProps) => {
  const match = selected;
  return (
    <MuiListItemButton
      {...other}
      sx={{ color: match ? "primary.main" : undefined }}
    />
  );
};
export default function SelectedListItem() {
  const [selectedIndex, setSelectedIndex] = React.useState(1);

  const handleListItemClick = (
    event: React.MouseEvent<HTMLDivElement, MouseEvent>,
    index: number
  ) => {
    setSelectedIndex(index);
  };

  return (
    <Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
      <List component="nav" aria-label="main mailbox folders">
        <ListItemButton
          selected={selectedIndex === 0}
          onClick={(event) => handleListItemClick(event, 0)}
        >
          <ListItemIcon>
            <InboxIcon />
          </ListItemIcon>
          <ListItemText primary="Inbox" />
        </ListItemButton>
        <ListItemButton
          selected={selectedIndex === 1}
          onClick={(event) => handleListItemClick(event, 1)}
        >
          <ListItemIcon>
            <DraftsIcon />
          </ListItemIcon>
          <ListItemText primary="Drafts" />
        </ListItemButton>
      </List>
      <Divider />
      <List component="nav" aria-label="secondary mailbox folder">
        <ListItemButton
          selected={selectedIndex === 2}
          onClick={(event) => handleListItemClick(event, 2)}
        >
          <ListItemText primary="Trash" />
        </ListItemButton>
        <ListItemButton
          selected={selectedIndex === 3}
          onClick={(event) => handleListItemClick(event, 3)}
        >
          <ListItemText primary="Spam" />
        </ListItemButton>
      </List>
    </Box>
  );
}

编辑 SelectedListItem 材料​​演示

这种方法的唯一缺点是它目前明显比 usingstyled,但它仍然足够快,可以用于大多数用例。

另一种选择是通过其css prop直接使用 Emotion 。这允许类似的 DX(虽然使用主题不是很方便),但没有任何性能损失。

/** @jsxImportSource @emotion/react */
import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
  ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";
import { css } from "@emotion/react";
import { useTheme } from "@material-ui/core/styles";

const ListItemButton = ({
  selected = false,
  ...other
}: ListItemButtonProps) => {
  const match = selected;
  const theme = useTheme();
  return (
    <MuiListItemButton
      {...other}
      css={css({ color: match ? theme.palette.primary.main : undefined })}
    />
  );
};
export default function SelectedListItem() {
  const [selectedIndex, setSelectedIndex] = React.useState(1);

  const handleListItemClick = (
    event: React.MouseEvent<HTMLDivElement, MouseEvent>,
    index: number
  ) => {
    setSelectedIndex(index);
  };

  return (
    <Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
      <List component="nav" aria-label="main mailbox folders">
        <ListItemButton
          selected={selectedIndex === 0}
          onClick={(event) => handleListItemClick(event, 0)}
        >
          <ListItemIcon>
            <InboxIcon />
          </ListItemIcon>
          <ListItemText primary="Inbox" />
        </ListItemButton>
        <ListItemButton
          selected={selectedIndex === 1}
          onClick={(event) => handleListItemClick(event, 1)}
        >
          <ListItemIcon>
            <DraftsIcon />
          </ListItemIcon>
          <ListItemText primary="Drafts" />
        </ListItemButton>
      </List>
      <Divider />
      <List component="nav" aria-label="secondary mailbox folder">
        <ListItemButton
          selected={selectedIndex === 2}
          onClick={(event) => handleListItemClick(event, 2)}
        >
          <ListItemText primary="Trash" />
        </ListItemButton>
        <ListItemButton
          selected={selectedIndex === 3}
          onClick={(event) => handleListItemClick(event, 3)}
        >
          <ListItemText primary="Spam" />
        </ListItemButton>
      </List>
    </Box>
  );
}

编辑 SelectedListItem 材料​​演示

在我工作的应用程序中(我还没有开始迁移到 v5),我希望使用styled和 Emotion 的css功能/props的组合我很犹豫要不要sx大量使用这个props,直到它的性能有所提高(我认为这最终会发生)。尽管它在许多情况下“足够快”地执行,但当我有两个具有类似 DX 可用的选项并且一个的速度是另一个的两倍时,我发现很难选择较慢的一个。我选择sxprop的主要情况是我想为不同的断点或类似区域设置不同的 CSS 属性的组件,在这些区域中,sxprop 提供比其他选项更好的 DX。

相关回答: