组合
Material UI 尝试使组合尽可能容易。
包裹组件
为了提供最大的灵活性和性能,Material UI 需要一种方法来了解组件接收的子元素的性质。为了解决这个问题,我们在需要时用 muiName
静态属性标记一些组件。
但是,您可能需要包裹一个组件以增强它,这可能会与 muiName
解决方案冲突。如果您包裹了一个组件,请验证该组件是否设置了此静态属性。
如果您遇到此问题,则需要为您的包裹组件使用与被包裹组件相同的标签。此外,您应该转发 props,因为父组件可能需要控制被包裹组件的 props。
让我们看一个例子
const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
转发 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-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
可以与应设置为 '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" />;
由于 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} />);