跳到内容
+

组合

Material UI 尝试使组合尽可能容易。

包裹组件

为了提供最大的灵活性和性能,Material UI 需要一种方法来了解组件接收的子元素的性质。为了解决这个问题,我们在需要时用 muiName 静态属性标记一些组件。

但是,您可能需要包裹一个组件以增强它,这可能会与 muiName 解决方案冲突。如果您包裹了一个组件,请验证该组件是否设置了此静态属性。

如果您遇到此问题,则需要为您的包裹组件使用与被包裹组件相同的标签。此外,您应该转发 props,因为父组件可能需要控制被包裹组件的 props。

让我们看一个例子

const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
Enter 开始编辑

转发 slot props

使用 mergeSlotProps 实用函数将自定义 props 与 slot props 合并。如果参数是函数,则它们将在合并之前解析,并且第一个参数的结果将覆盖第二个参数。

在两个参数之间合并的特殊属性如下所示

  • className:值被连接而不是相互覆盖。

    在下面的代码片段中,custom-tooltip-popper 类应用于 Tooltip 的 popper slot。

    import Tooltip, { TooltipProps } from '@mui/material/Tooltip';
    import { mergeSlotProps } from '@mui/material/utils';
    
    export const CustomTooltip = (props: TooltipProps) => {
      const { children, title, sx: sxProps } = props;
    
      return (
        <Tooltip
          {...props}
          title={<Box sx={{ p: 4 }}>{title}</Box>}
          slotProps={{
            ...props.slotProps,
            popper: mergeSlotProps(props.slotProps?.popper, {
              className: 'custom-tooltip-popper',
              disablePortal: true,
              placement: 'top',
            }),
          }}
        >
          {children}
        </Tooltip>
      );
    };
    

    如果您通过 Custom Tooltip 上的 slotProps prop 添加了另一个 className (如下所示),那么两者都将存在于渲染的 popper slot 上

    <CustomTooltip slotProps={{ popper: { className: 'foo' } }} />
    

    原始示例中的 popper slot 现在将同时应用这两个类,以及可能存在的任何其他类:"[…] custom-tooltip-popper foo"

  • style:对象是浅合并而不是相互替换。来自第一个参数的 style 键具有更高的优先级。

  • sx:值被连接到一个数组中。

  • ^on[A-Z] event handlers:这些函数在两个参数之间组合。

    mergeSlotProps(props.slotProps?.popper, {
      onClick: (event) => {}, // composed with the `slotProps?.popper?.onClick`
      createPopper: (popperOptions) => {}, // overridden by the `slotProps?.popper?.createPopper`
    });
    

Component prop

Material UI 允许您通过名为 component 的 prop 更改将要渲染的根元素。

例如,默认情况下,List 组件将渲染一个 <ul> 元素。这可以通过将 React component 传递给 component prop 来更改。以下示例使用 <menu> 元素作为根元素渲染 List 组件

<List component="menu">
  <ListItem>
    <ListItemButton>
      <ListItemText primary="Trash" />
    </ListItemButton>
  </ListItem>
  <ListItem>
    <ListItemButton>
      <ListItemText primary="Spam" />
    </ListItemButton>
  </ListItem>
</List>

这种模式非常强大,并且允许很大的灵活性,以及与您的其他库(例如您最喜欢的路由库或表单库)进行互操作的方式。

传递其他 React 组件

您可以将任何其他 React 组件传递给 component prop。例如,您可以传递来自 react-routerLink 组件

import { Link } from 'react-router';
import Button from '@mui/material/Button';

function Demo() {
  return (
    <Button component={Link} to="/react-router">
      React router link
    </Button>
  );
}

使用 TypeScript

为了能够使用 component prop,props 的类型应与类型参数一起使用。否则,component prop 将不会存在。

下面的示例使用 TypographyProps,但对于任何具有用 OverrideProps 定义的 props 的组件,它都将起作用。

import { TypographyProps } from '@mui/material/Typography';

function CustomComponent(props: TypographyProps<'a', { component: 'a' }>) {
  /* ... */
}
// ...
<CustomComponent component="a" />;

现在,CustomComponent 可以与应设置为 'a'component prop 一起使用。此外,CustomComponent 将具有 <a> HTML 元素的所有 props。Typography 组件的其他 props 也将存在于 CustomComponent 的 props 中。

您可以在 这些演示 中找到 Button 和 react-router 的代码示例。

泛型

也可以使用接受任何 React 组件的通用自定义组件,包括 内置组件

function GenericCustomComponent<C extends React.ElementType>(
  props: TypographyProps<C, { component?: C }>,
) {
  /* ... */
}

如果 GenericCustomComponent 与提供的 component prop 一起使用,则它也应具有所提供组件所需的所有 props。

function ThirdPartyComponent({ prop1 }: { prop1: string }) {
  /* ... */
}
// ...
<GenericCustomComponent component={ThirdPartyComponent} prop1="some value" />;

由于 ThirdPartyComponentprop1 作为要求,因此 prop1 成为 GenericCustomComponent 的必需属性。

并非每个组件都完全支持您传入的任何组件类型。如果您遇到某个组件在 TypeScript 中拒绝其 component props,请打开一个 issue。目前正在努力通过使组件 props 通用化来解决此问题。

refs 的注意事项

本节介绍当使用自定义组件作为 children 或用于 component prop 时的注意事项。

某些组件需要访问 DOM 节点。这以前可以通过使用 ReactDOM.findDOMNode 来实现。此函数已被弃用,转而使用 ref 和 ref 转发。但是,只有以下组件类型可以被赋予 ref

如果您在使用 Material UI 组件时未使用上述类型之一,您可能会在控制台中看到来自 React 的警告,类似于

请注意,如果 lazymemo 组件的被包裹组件无法容纳 ref,您仍然会收到此警告。在某些情况下,会发出额外的警告以帮助进行调试,类似于

仅涵盖了两个最常见的用例。有关更多信息,请参阅 React 官方文档中的此部分

-const MyButton = () => <div role="button" />;
+const MyButton = React.forwardRef((props, ref) =>
+  <div role="button" {...props} ref={ref} />);

 <Button component={MyButton} />;
-const SomeContent = props => <div {...props}>Hello, World!</div>;
+const SomeContent = React.forwardRef((props, ref) =>
+  <div {...props} ref={ref}>Hello, World!</div>);

 <Tooltip title="Hello again."><SomeContent /></Tooltip>;

要了解您正在使用的 Material UI 组件是否具有此要求,请查看该组件的 props API 文档。如果您需要转发 refs,则描述将链接到本节。

StrictMode 的注意事项

如果您在上述情况下使用类组件,您仍然会在 React.StrictMode 中看到警告。ReactDOM.findDOMNode 在内部用于向后兼容。您可以在类组件中使用 React.forwardRef 和指定的 prop 将 ref 转发到 DOM 组件。这样做不应再触发与 ReactDOM.findDOMNode 的弃用相关的任何警告。

 class Component extends React.Component {
   render() {
-    const { props } = this;
+    const { forwardedRef, ...props } = this.props;
     return <div {...props} ref={forwardedRef} />;
   }
 }

-export default Component;
+export default React.forwardRef((props, ref) => <Component {...props} forwardedRef={ref} />);