跳到主要內容
+

組合

Material UI 致力於讓組合盡可能地簡單。

包裝組件

為了提供最大的彈性和效能,Material UI 需要一種方法來了解組件接收的子元素的性質。為了解決這個問題,我們在需要時會用 muiName 靜態屬性標記某些組件。

然而,您可能需要包裝組件以增強其功能,這可能會與 muiName 解決方案衝突。如果您包裝了一個組件,請驗證該組件是否已設定此靜態屬性。

如果您遇到這個問題,您需要為您的包裝組件使用與被包裝組件相同的標籤。此外,您應該轉發 props,因為父組件可能需要控制被包裝組件的 props。

讓我們來看一個例子

const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
按下 Enter 鍵開始編輯

轉發插槽 props

使用 mergeSlotProps 實用函數將自訂 props 與插槽 props 合併。如果參數是函數,則它們會在合併之前被解析,並且來自第一個參數的結果將覆蓋第二個。

以下列出在兩個參數之間合併的特殊屬性

  • className:值會被串聯而不是互相覆蓋。

    在下面的程式碼片段中,custom-tooltip-popper 類別被應用於 Tooltip 的 popper 插槽。

    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 插槽上

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

    原始範例中的 popper 插槽現在將同時應用這兩個類別,以及任何其他可能存在的類別:"[…] custom-tooltip-popper foo"

  • style:物件會被淺層合併,而不是互相取代。來自第一個參數的 style 鍵具有較高的優先級。

  • sx:值會被串聯成一個陣列。

  • ^on[A-Z] 事件處理器:這些函數會在兩個參數之間組合。

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

組件 prop

Material UI 允許您透過名為 component 的 prop 更改將被渲染的根元素。

例如,預設情況下,List 組件將渲染一個 <ul> 元素。這可以透過將 React 組件 傳遞給 component prop 來更改。以下範例將 List 組件渲染為以 <menu> 元素作為根元素

<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 可以與 component prop 一起使用,該 prop 應該設定為 'a'。此外,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} />);