組合
Material UI 致力於讓組合盡可能地簡單。
包裝組件
為了提供最大的彈性和效能,Material UI 需要一種方法來了解組件接收的子元素的性質。為了解決這個問題,我們在需要時會用 muiName
靜態屬性標記某些組件。
然而,您可能需要包裝組件以增強其功能,這可能會與 muiName
解決方案衝突。如果您包裝了一個組件,請驗證該組件是否已設定此靜態屬性。
如果您遇到這個問題,您需要為您的包裝組件使用與被包裝組件相同的標籤。此外,您應該轉發 props,因為父組件可能需要控制被包裝組件的 props。
讓我們來看一個例子
const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
轉發插槽 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-router
的 Link
組件
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" />;
由於 ThirdPartyComponent
將 prop1
作為必要條件,因此 prop1
對於 GenericCustomComponent
也變成了必要的。
並非每個組件都完全支援您傳遞的任何組件類型。如果您遇到組件在 TypeScript 中拒絕其 component
props,請開啟 issue。目前正在努力透過使組件 props 泛型化來解決此問題。
關於 refs 的注意事項
本節涵蓋了當使用自訂組件作為 children
或用於 component
prop 時的注意事項。
某些組件需要存取 DOM 節點。這以前可以透過使用 ReactDOM.findDOMNode
來實現。此函數已被棄用,轉而使用 ref
和 ref 轉發。但是,只有以下組件類型可以被賦予 ref
- 任何 Material UI 組件
- 類別組件,即
React.Component
或React.PureComponent
- DOM(或主機)組件,例如
div
或button
- React.forwardRef 組件
- React.lazy 組件
- React.memo 組件
如果您在使用組件與 Material UI 結合時沒有使用上述類型之一,您可能會在控制台中看到來自 React 的警告,類似於
請注意,如果 lazy
和 memo
組件的被包裝組件無法持有 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} />);