Composition
Material-UI tries to make composition as easy as possible.
Wrapping components
In order to provide the maximum flexibility and performance, we need a way to know the nature of the child elements a component receives. To solve this problem we tag some of the components with a muiName
static property when needed.
You may, however, need to wrap a component in order to enhance it, which can conflict with the muiName
solution. If you wrap a component, verify if that component has this static property set.
If you encounter this issue, you need to use the same tag for your wrapping component that is used with the wrapped component. In addition, you should forward the properties, as the parent component may need to control the wrapped components props.
Давайте рассмотрим пример:
const WrappedIcon = props => <Icon {...props} />; WrappedIcon.muiName = Icon.muiName;
<IconButton>
<Icon>alarm</Icon>
</IconButton>
<IconButton>
<WrappedIcon>alarm</WrappedIcon>
</IconButton>
Component prop
Material-UI allows you to change the root element that will be rendered via a prop called component
.
How does it work?
The custom component will be rendered by Material-UI like this:
return React.createElement(props.component, props)
Например, по умолчанию компонент List
будет отображать <ul>
элемент. This can be changed by passing a React component to the component
prop. The following example will render the List
component with a <nav>
element as root element instead:
<List component="nav">
<ListItem button>
<ListItemText primary="Trash" />
</ListItem>
<ListItem button>
<ListItemText primary="Spam" />
</ListItem>
</List>
This pattern is very powerful and allows for great flexibility, as well as a way to interoperate with other libraries, such as your favorite routing or forms library. Но **с небольшой оговоркой! **
Caveat with inlining
Using an inline function as an argument for the component
prop may result in unexpected unmounting, since a new component is passed every time React renders. ⚠️ Однако, поскольку мы используем встроенную функцию для изменения отрисованного компонента, React будет демонтировать ссылку каждый раз, когда ListItemLink
отрисован.
import { Link } from 'react-router-dom';
function ListItemLink(props) {
const { icon, primary, to } = props;
const CustomLink = props => <Link to={to} {...props} />;
return (
<li>
<ListItem button component={CustomLink}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={primary} />
</ListItem>
</li>
);
}
⚠️ Однако, поскольку мы используем встроенную функцию для изменения отрисованного компонента, React будет демонтировать ссылку каждый раз, когда ListItemLink
отрисован. Не только React сделает ненужное обновление DOM, но и ripple эффект ListItem
будет работать неправильно.
The solution is simple: avoid inline functions and pass a static component to the component
prop instead. Let's change the ListItemLink
component so CustomLink
always reference the same component:
import { Link } from 'react-router-dom';
function ListItemLink(props) {
const { icon, primary, to } = props;
const CustomLink = React.useMemo(
() =>
React.forwardRef((linkProps, ref) => (
<Link ref={ref} to={to} {...linkProps} />
)),
[to],
);
return (
<li>
<ListItem button component={CustomLink}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={primary} />
</ListItem>
</li>
);
}
Caveat with prop forwarding
You can take advantage of the prop forwarding to simplify the code. В этом примере мы не создаем ни одного промежуточного компонента:
import { Link } from 'react-router-dom';
<ListItem button component={Link} to="/">
⚠️ However, this strategy suffers from a limitation: prop collisions. The component providing the component
prop (e.g. ListItem) might not forward all the props (for example dense) to the root element.
With TypeScript
You can find the details in the TypeScript guide.
Routing libraries
The integration with third-party routing libraries is achieved with the component
prop. The behavior is identical to the description of the prop above. Here are a few demos with react-router-dom. They cover the Button, Link, and List components. You can apply the same strategy with all the components (BottomNavigation, Card, etc.).
Button
<Router>
<div>
<Button component={RouterLink} to="/">
With prop forwarding
</Button>
<br />
<Button component={LinkBehavior}>Without prop forwarding</Button>
</div>
</Router>
<Router>
<div>
<Link component={RouterLink} to="/">
With prop forwarding
</Link>
<br />
<Link component={LinkBehavior}>Without prop forwarding</Link>
</div>
</Router>
Caveat with refs
This section covers caveats when using a custom component as children
or for the component
prop.
Some of the components need access to the DOM node. This was previously possible by using ReactDOM.findDOMNode
. This function is deprecated in favor of ref
and ref forwarding. However, only the following component types can be given a ref
:
- Any Material-UI component
- class components i.e.
React.Component
orReact.PureComponent
- DOM (or host) components e.g.
div
orbutton
- React.forwardRef components
- React.lazy components
- React.memo components
If you don't use one of the above types when using your components in conjunction with Material-UI, you might see a warning from React in your console similar to:
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Обратите внимание, что вы все равно получите это предупреждение для lazy
и memo
компонентов, если обернутый ими компонент не может содержать ссылку. In some instances an additional warning is issued to help with debugging, similar to:
Invalid prop
component
supplied toComponentName
. Expected an element type that can hold a ref.
Only the two most common use cases are covered. For more information see this section in the official React docs.
-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>;
To find out if the Material-UI component you're using has this requirement, check out the the props API documentation for that component. If you need to forward refs the description will link to this section.
Caveat with StrictMode
If you use class components for the cases described above you will still see warnings in React.StrictMode
. ReactDOM.findDOMNode
is used internally for backwards compatibility. You can use React.forwardRef
and a designated prop in your class component to forward the ref
to a DOM component. Doing so should not trigger any more warnings related to the deprecation of 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} />);