跳转到内容

Composition 封装

Material-UI 试着让封装尽可能的简单。

封装组件

我们需要一种了解组件接收的子元素的本质的方式,这样可以尽可能提供最大的灵活性和最好的性能。 我们会用 muiName 静态属性来标记一些我们的组件,这样能够解决这个问题。

但是,您仍可能需要封装一个组件以增强它的功能,而这可能与 muiName 的解决方案相冲突。 若你要封装一个组件,那么得验证该组件是否具有此静态属性的集合。

如果您遇到此问题,那么请为封装组件附加上与被封装组件一样的标记。 另外,鉴于父组件可能需要对被封装的组件属性加以控制,您应该向父组件传递这些属性。

让我们来看一个例子:

const WrappedIcon = props => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
<IconButton>
  <Icon>alarm</Icon>
</IconButton>
<IconButton>
  <WrappedIcon>alarm</WrappedIcon>
</IconButton>

组件属性

在 Material-UI 中,通过一个叫 component 的属性,您可以更改将被渲染的根元素。

它是如何工作的呢?

Materal-UI 将这样渲染自定义的组件:

return React.createElement(props.component, props)

例如,在默认情况下,List 组件会渲染 <ul> 元素。 但只要把一个 React 组件 属性传递给 component 属性,就即可将此更改。 在下面的例子里,就将 List 组件作为一个根元素来渲染成 <nav> 元素:

<List component="nav">
  <ListItem button>
    <ListItemText primary="Trash" />
  </ListItem>
  <ListItem button>
    <ListItemText primary="Spam" />
  </ListItem>
</List>

这种模式非常强大,它拥有很强的灵活性,也涵盖了与其他库互操作的方法,例如你最喜欢的一些 routing 或者 forms 的库。 但它也带有一个小小的警告!

当与内联函数一起使用时要注意

使用内联函数作为 component 属性的参数可能会导致 意外的卸载,因为每次 React 渲染时都会传递一个新的组件。 例如,如果要创建自定义 ListItem 来作为一个链接使用,您可以这样编写:

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>
  );
}

⚠️ 然而,由于我们使用一个内联函数来更改渲染的组件,所以每一次渲染 ListItemLink 时,React 都会先将它卸载。 React 不仅会不必要地更新 DOM,还会影响 ListItem 的涟漪效果。

解决方案很简单: 避免内联函数,取而代之的是将一个静态组件传递给 component 属性。 我们可以改变 ListItemLink 组件,这样一来 CustomLink 总是引用相同的组件:

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>
  );
}

带有传递属性的一些注意事项

您可以利用传递属性来简化您的代码。 在此示例中,我们不创建任何中间组件:

import { Link } from 'react-router-dom';

<ListItem button component={Link} to="/">

⚠️ 但是,这种策略会受到一些限制:属性的冲突。 提供 component 属性的组件(如:ListItem)则可能不会将其所有属性(如 dense)传递到根元素。

使用 TypeScript

您可以在 TypeScript 指南 中找到详细信息 。

路由库

通过 component 属性实现了与第三方路由库的整合。 该行为与上面的属性描述完全相同。 以下是一些 react-router-dom 的示例: 它覆盖按钮(Button)、链接(Link)和列表(List)组件,对所有的组件,你应该能应用相同的策略。

Button 按钮

<Router>
  <div>
    <Button color="primary" component={RouterLink} to="/">
      With prop forwarding
    </Button>
    <br />
    <Button color="primary" component={LinkBehavior}>
      Without prop forwarding
    </Button>
  </div>
</Router>

Link 链接

<Router>
  <div>
    <Link component={RouterLink} to="/">
      With prop forwarding
    </Link>
    <br />
    <Link component={LinkBehavior}>Without prop forwarding</Link>
  </div>
</Router>

List 列表

Current route: /drafts

使用 refs 时的一些注意事项

本节介绍了将一个自定义组件用作子组件或 作为 component 的属性时的一些注意事项。

某些组件需要访问 DOM 节点。 之前提到,通过使用 ReactDOM.findDOMNode 就能实现。 该方法已被废弃,代替的是使用 ref 和 ref 转递。 然而,只有给予下列组件类型一个 ref

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

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

请注意,在使用 lazymemo 组件时,如果被封装的组件无法承载一个 ref,您仍然有可能收到这个警告。

在某些情况下,我们发出了一个额外警告来帮助调试,类似于:

Invalid prop component supplied to ComponentName. Expected an element type that can hold a ref.

这只包含了两个最常见的用例。 欲了解更多信息,请查阅在 React 官方文档中的此章节

-const MyButton = props => <div role="button" {...props} />;
+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}>你好,世界!</div>);
<Tooltip title="Hello, again."><SomeContent /></Tooltip>;

若想知道您使用的 Material-UI 组件是否具有此需求,请查阅该组件的 API 文档中的属性部分。 如果您需要转递 refs,描述会关联到此章节。

使用 StrictMode 的注意事项

如果对上述情况,您使用类组件,那么您会看到 React.StrictMode 中的一些警告。 在内部使用 ReactDOMfindDOMNode 来达到向后的兼容性。 您可以使用 React.forwardRef 和类组件中的一个指定的属性来把 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} />);