Material-ui: 提高 Material-UI 性能

创建于 2018-03-23  ·  93评论  ·  资料来源: mui-org/material-ui

首先,非常感谢您提供这个很棒的组件库! 这很棒!

我在我的新应用中添加了一个抽屉。 大多数情况下,我复制粘贴了抽屉示例。 只是为了 PoC 我乘了

        <Divider />
        <List>{mailFolderListItems}</List>

部分。

之后感觉很慢,尤其是在移动设备上(nexus 4,cordova with crosswalk 20)。 我使用开发模式,但生产模式并没有加快多少。

通过 react 开发工具,我注意到 mailFolderListItems 中的组件在 react-router 中的每个链接点击时呈现,甚至菜单打开。 重新渲染 ONE {mailFolderListItems}需要花费 50-60 毫秒的时间。 我用

const modalProps = {
    keepMounted: true, // Better open performance on mobile.
};

为了消除其他应用程序组件的不确定性,我将mailFolderListItems为 Component 并禁用重新渲染:

class MailFolderListItems extends React.Component<{}, {}> {

    shouldComponentUpdate() {
        return false;
    }

    render() {
        return (
            <List>
                <Link to={Routes.Startpage.path}>
                    <ListItem>
                        <ListItemIcon>
                            <InboxIcon />
                        </ListItemIcon>
[...]


                <Divider />
                <MailFolderListItems />

之后这部分感觉还可以。

我找到了https://github.com/mui-org/material-ui/issues/5628我建议重新审视它。 优化shouldComponentUpdate是获得性能的基本也是最重要的一步。 PureComponent只是最常见的快捷方式。

此外,我注意到在WithStyles花费了很多时间(每个 material-ui 组件为 1-2ms 甚至更多)。

  • [x] 我已经搜索了这个存储库的问题,并相信这不是重复的。

预期行为

我期待为这个伟大的库获得大部分可能的反应性能。

当前行为

每个 material-ui 组件都会使应用程序变慢。

重现步骤(针对错误)

我还没有提供复制示例,因为我只是从组件演示页面复制粘贴,但如果需要我可以提供代码和框演示。 对于浏览器,如果浏览器在性能设置中的速度降低了 >=5 倍,则这一点很明显。

您的环境

| 科技 | 版本 |
|--------------|---------|
| Material-UI | 1.0.0-beta.38 |
| Material-UI 图标 | 1.0.0-beta.36 |
| 反应 | 16.2.0 |
| 浏览器 | 科尔多瓦人行横道 20(等于 android chrome 50)|

discussion performance

最有用的评论

@oliviertassinari作为一个伟大的开发者,你怎么能写出这样的东西? 为什么你使用_你的个人假设_作为论据而不是事实? 我想,我在上面提供了足够的事实。 我做了一切来展示它,因为我喜欢这个项目并想做出贡献。 这不够? 好的,然后是更多的事实和 _no_ 假设。

只为你,我减少到 10 个按钮。 10! 这意味着 material-ui 中的任何10 个组件(更糟糕的是:容器)都会减慢整个应用程序的速度,直到应用程序无法使用! 在没有任何油门的情况下在带有人行横道 21/chrome 51 的真实设备上进行测试:

普通按钮:
image

纯按钮:
image

这仍然是 8 倍的改进! 很大! 你能想象这在移动设备上有多重要吗?

一个页面上最多有 10 个按钮,而不是 100 个按钮。 尽管如此,您还是会发现 10 个网格、10 个 X 等。

我使用 Button 是因为它是最简单的组件之一! 这表明 material-ui 从性能的角度来看坏掉的。 现在,想象一个容器组件,如 AppBar、Toolbar、List、Drawer! 这更糟! 您很快就会在每个页面上获得 20 个组件/容器。 并且因为您在强大的台式机/mac 上没有任何缓慢的过期,这并不意味着 material-ui 不是令人难以置信的缓慢。

react-intl,之前和新道具之间的检查将始终为假。 您将浪费 CPU 周期。 所以 x13 -> x0.8

给我看一个关于 codeandbox 的例子。 我不明白为什么会发生这种情况。 我敢肯定,这只会发生在错误使用 React 组件的情况下。 官方示例显示了错误的用法,因为它似乎 react-intl 没有应用上下文订阅者。 但是还有许多其他组件与 React 指南和最佳实践保持一致,以保持性能。

顺便说一句:对于移动设备上的按钮,WithStyles 最多消耗 2.27 毫秒。 8 个组件,而您的速度低于 60fps。

所有93条评论

我期待为这个伟大的库获得大部分可能的反应性能。

@Bessonov性能将成为 v1 版本后的焦点。 我们试图尽可能快地保持库的核心。 对于 +90% 的情况,它应该足够了。 至少,这是我迄今为止使用图书馆的经验。 您没有提供任何外部参考,除了引起我们对性能很重要这一事实的关注之外,我们无法使这个问题具有可操作性。 如果您能够确定我们可以调查的 Material-UI 的性能根源限制(使用复制代码和框或存储库),请提出它。 在那之前,我要结束这个问题。

@oliviertassinari感谢您的快速回复! 我很高兴听到,该表现将在发布后成为焦点。

对于 +90% 的情况,它应该足够了。 至少,这是我迄今为止使用图书馆的经验。

在桌面上 - 是的,它是。 但是在移动设备上它真的很慢。 只是抽屉和一些按钮使应用程序滞后。 它没有响应,并根据需要消耗更多功率。

您没有提供任何外部参考,除了引起我们对性能很重要这一事实的关注之外,我们无法使这个问题具有可操作性。

我提供了对已经提出的问题的参考和对反应文档的参考。

如果您能够确定我们可以调查的 Material-UI 的性能根源限制(使用复制代码和存储库)

正如我所说,我可以做到。 这是纯成分和非纯成分的比较。 重现的步骤是:

  1. 使用 chrome 并转到https://codesandbox.io/s/r1ov818nwm
  2. 在预览窗口中单击“在新窗口中打开”
  3. 打开开发工具并转到“性能”选项卡
  4. 将 CPU 节流至少 5 倍(取决于您的 PC - 可能是 10 倍)以反映移动设备
  5. 点击汉堡,看看它是如何滞后的。

现在:

  1. 转到index.js
  2. pure更改true
  3. 节省
  4. 重复上面的步骤
  5. 好好享受!

这个例子有点不切实际,因为我插入了太多的 List 元素。 但正如我所说,在移动设备上,您可以很快地指出您“感觉到”每个动作的位置。

这是我对WithStyles 。 它在带有 CPU 节流的台式机上。 我想在星期一发布真实设备的屏幕截图。

WithStyles(ListItem)
image

ListItem
image

ListItem组件的差异为 1.26 毫秒。 使用 13 个像这样的组件,您无法再以 60fps 进行渲染。 但我注意到在桌面上,这个持续时间并不总是在这里。

这里是纯成分和非纯成分的比较

顺便说一句,我并不是说 PureComponent 是所有性能问题的解决方案。 我只想说,它可以成为使 material-ui 在移动设备上可用的帮手之一。

仅 ListItem 组件的差异是 1.26 毫秒。

@Bessonov对于同一组件的重复实例,WithStyles 组件应该非常便宜。 我们只注入一次 CSS。
也许您正在达到 React 的限制和高阶组件的成本。
有一系列解决方案可以缓解 React 中的性能问题。 例如,您可以使用元素记忆和虚拟化。 我将这个问题作为未来性能调查的起点。
我认为我们现在对此无能为力。 感谢您提出关注。

好吧,这只是一部分。 您认为更重要的部分是什么 - 优化shouldComponentUpdate

编辑:我想把这个问题分成两部分。 如果我可以显示更多信息,这部分是关于shouldComponentUpdateWithStyles的另一部分。 你觉得可以吗?

优化 shouldComponentUpdate

@Bessonov我们没有在 Material-UI 端故意实现这样的逻辑

  1. 我们的大多数组件都接受 react 元素,每次渲染都会更改 react 元素引用,从而使逻辑系统地浪费 CPU 周期。
  2. shouldComponentUpdate逻辑离 React 树的根越近,它的效率就越高。 我的意思是,你可以在树上修剪得越多越好。 Material-UI 组件是低级的。 存在低杠杆机会。

我们发现的唯一机会是使用图标。 如果您能识别新的,请告诉我们。

WithStyles 的另一部分

+90% 花在 WithStyles 上的时间实际上花在了JSS 上,在 Material-UI 方面我们能做的很少。
至少,我最近一直在寻找服务器端渲染的缓存机会,以大大提高重复渲染的性能。 但它只是服务器端。

我们的大多数组件都接受 react 元素,每次渲染都会更改 react 元素引用,从而使逻辑系统地浪费 CPU 周期。

这取决于使用情况,可以改进。 我没有对自己进行基准测试,但我确信正确的组件使用和优化的shouldComponentUpdate (带有浅层比较)比未优化的组件成本更低,尤其是对于不可变结构(并且不需要 immutable.js)顺便提一句)。 相关票证: https :

例如在https://github.com/mui-org/material-ui/blob/v1-beta/docs/src/pages/demos/drawers/PermanentDrawer.js#L68这会导致新对象并生成PureComponent毫无意义:

        classes={{
          paper: classes.drawerPaper,
        }}

因为它每次返回一个新对象。 但不是:

const drawerClasses = {
    paper: classes.drawerPaper,
};
[...]
        classes={drawerClasses}

组件也是如此。

shouldComponentUpdate 逻辑离 React 树的根越近,它的效率就越高。 我的意思是,你可以在树上修剪得越多越好。

是的,这是真的。

Material-UI 组件是低级的。 存在低杠杆机会。

这是部分正确的。 正如您在https://codesandbox.io/s/r1ov818nwm中看到的,我只是将List包裹在PureComponent 。 没有别的,它显着加快了抽屉。 我会声称这适用于每个组件,至少使用{this.props.children} 。 我创建了另一个演示: https :

只有 101 个按钮(pure = false)和包裹在 PureComponent 中的按钮(pure = true,查看 Button 从何处导入)。 同样的游戏结果,如果我点击“点击”按钮:

普通按钮:
image

Wrapped Button(带有开销等):
image

如您所见,对于普通按钮,有 637 毫秒与 47 毫秒! 您是否仍然认为优化shouldComponentUpdate (或仅PureComponent )不值得? :)

编辑:修复开头的措辞

@oliviertassinari @ore​​qizer我注意到有趣的事情。 似乎扩展PureComponent性能比Component更好

  shouldComponentUpdate() {
    return false;
  }

image

编辑:我认为这是反应内部优化之一。

如您所见,对于普通按钮,有 637 毫秒与 47 毫秒! 您是否仍然认为优化 shouldComponentUpdate(或只是 PureComponent)不值得? :)

@Bessonov它展示了纯逻辑的潜力。 是的,它可能非常有用! 这是 x13 的改进。 但我不认为它接近现实生活条件:

  • 一个页面上最多有 10 个按钮,而不是 100 个按钮。 尽管如此,您还是会发现 10 个网格、10 个 X 等。
  • 您将使用诸如 react-intl 之类的东西,而不是提供给较低级别​​元素的简单属性,之前和新道具之间的检查将始终为假。 您将浪费 CPU 周期。 所以 x13 -> x0.8 左右

@oliviertassinari作为一个伟大的开发者,你怎么能写出这样的东西? 为什么你使用_你的个人假设_作为论据而不是事实? 我想,我在上面提供了足够的事实。 我做了一切来展示它,因为我喜欢这个项目并想做出贡献。 这不够? 好的,然后是更多的事实和 _no_ 假设。

只为你,我减少到 10 个按钮。 10! 这意味着 material-ui 中的任何10 个组件(更糟糕的是:容器)都会减慢整个应用程序的速度,直到应用程序无法使用! 在没有任何油门的情况下在带有人行横道 21/chrome 51 的真实设备上进行测试:

普通按钮:
image

纯按钮:
image

这仍然是 8 倍的改进! 很大! 你能想象这在移动设备上有多重要吗?

一个页面上最多有 10 个按钮,而不是 100 个按钮。 尽管如此,您还是会发现 10 个网格、10 个 X 等。

我使用 Button 是因为它是最简单的组件之一! 这表明 material-ui 从性能的角度来看坏掉的。 现在,想象一个容器组件,如 AppBar、Toolbar、List、Drawer! 这更糟! 您很快就会在每个页面上获得 20 个组件/容器。 并且因为您在强大的台式机/mac 上没有任何缓慢的过期,这并不意味着 material-ui 不是令人难以置信的缓慢。

react-intl,之前和新道具之间的检查将始终为假。 您将浪费 CPU 周期。 所以 x13 -> x0.8

给我看一个关于 codeandbox 的例子。 我不明白为什么会发生这种情况。 我敢肯定,这只会发生在错误使用 React 组件的情况下。 官方示例显示了错误的用法,因为它似乎 react-intl 没有应用上下文订阅者。 但是还有许多其他组件与 React 指南和最佳实践保持一致,以保持性能。

顺便说一句:对于移动设备上的按钮,WithStyles 最多消耗 2.27 毫秒。 8 个组件,而您的速度低于 60fps。

为什么你使用你的个人假设作为论据而不是事实?

是什么让您认为这是个人假设? 我试图进行批判性思维。 从概念上讲,与非纯版本相比,额外的 prop 遍历会减慢纯版本的速度,它必须修剪一些东西才能值得。 与#5628 或https://github.com/react-bootstrap/react-bootstrap/issues/633#issuecomment -234749417 或https://github.com/reactstrap/reactstrap/pull/771#issuecomment -375765577 相同的推理

与纯:
capture d ecran 2018-03-27 a 11 43 41

无纯:
capture d ecran 2018-03-27 a 11 44 15

再现如下。

@oliviertassinari您确定 codeandbox 使您的测试一切正常吗? 因为我的结果非常不同:

没有纯(即使没有油门它也很慢):
image

纯(更改为 true 并保存后,我获得了代码和框的新网址):
image

由于它不检查上下文更改并且还强制用户确保所有子组件也是“纯”的,我认为这不适合这个库。 这个库需要尽可能灵活,我相信这会让它更难使用。

我明白了。 但这是一个非常有趣的权衡。 一方面,甚至 material-ui都应该“尽可能灵活”,但另一方面,当前的性能使其完全_不可用_。

可能你应该考虑提供纯和非纯版本的组件。 我现在这样做是为了让我的应用程序即使在桌面上也能获得一些可用的性能。

@Bessonov未正确保存

<Button>
+ <i>
    Button
+ </i>
</Button>

我不明白为什么它会产生不同的结果? 我得到了更好的图表,但非纯版本要慢得多。

编辑:好的,我明白了。 试着弄清楚发生了什么......

好的我现在明白了。 只是相同的“每次渲染上的新对象”-事物。 我之前没有注意到。 在某些情况下,可以通过babel 插件自动使用常量进行改进。

那么,你已经知道了! :D 即使它本身没有太大的好处(你展示了大约 7%),它仍然是有利可图的,纯组件可以避免你上面提到的一些缺陷。 我现在使用 Pure Wrapper + babel 插件 + 生产版本对其进行了测试,它在同一移动设备上的速度令人印象深刻!

正如我所说,我认为最好同时提供 - 非纯组件以获得灵活性和纯组件(包装器足以保持简单和可维护)以获得性能。 但对我来说,我可以只接受纯组件,因为整体性能改进要远远大于性能缺陷。 或者更好:我不能在没有纯组件的情况下使用 material-ui。

好的,现在我正在等待有关此主题的进一步输入并在我的应用程序中创建自己的包装器)

感谢您的见解!

我从来没有听说过实际上使用了transform-react-constant-element并且真的很有用。 加入随机微优化是可以的,但在实践中,您很少编写足够简单的代码来从中获益。 虽然,我认为对于所有 SVG 样式的图标组件(如<Add />来说,这不会是一个糟糕的优化。

看看这个例子(点击旁边的插件,搜索“react-constant”并点击“transform-react-constant-elements”上的复选框),你会发现几乎没有任何优化:

  • 简单的静态InputAdornment已移至顶部,是的。
  • 但是当我们在其上放置事件处理程序时,不再优化简单的提交按钮。
  • 虽然 InputAdornment 本身现在被提升了,但InputProps={{startAdornment: ...}}仍然是内联的,并且在每次渲染时都会创建一个新对象,这使得 PureComponent 变得不可能。
  • 同样, classes={{label: classes.runButtonLabel}}使 PureComponent 无法优化 Run 按钮。

我个人喜欢 PureComponent,并尝试在任何地方使用它,并尽我所能优化它以使其正常工作。 但看起来 MUI 根本不像 PureComponent 那样可以工作。

  • InputProps这样的*Props道具是 MUI 工作方式的基本模式。 它不仅仅是在您需要时修改 MUI 内部的高级方法,而是在简单用例中经常使用的东西。 这种模式通常会使通常可以在纯模式下优化的任何叶组件无法优化。
  • 同样, classes={{...}}模式也不适用于 PureComponent,它是向 MUI 中的事物添加任何样式的方法。 (并且说使用classes={classes}是不切实际的,因为现实生活中的消费者可能具有与组件内部类不同的类名,并且classes可能还包括旨在为其他元素设置样式的类在同一个消费组件中)
  • 儿童被大量使用,而不是有 1 个组件,一个常见的模式经常使用 2-3 个,如果其中任何一个可以的话,只有其中一个可以被纯粹优化。

如果我们想要优化任何东西,就需要处理这些基本问题,否则仅仅让人们启用纯模式 MUI 实际上根本不会优化太多。 我能想到两种可能。

我们只允许启用纯模式,消费者必须手动记忆或提升对象才能实际获得优化

  1. 我能想到的一种方式做这将是一些短期的shallowMemoize使用本地这thiskey (所以你可以使用它在不同的数据位)只要数据是浅相等的,它就会记住数据
  2. 我能想到的另一个是使用 reselect 之类的东西来创建将记忆数据的选择器。
  3. 在 1. 中执行shallowMemoize另一种方法是使用装饰器将其传递给render() 。 这样我们就可以在每次渲染时传入一个新的,而不是需要key我们可以检查是否应该重新使用上次渲染中的任何记忆对象,然后丢弃所有旧值。

问题当然是这会让消费者变得更大更混乱,并且需要手动将逻辑提升到远离使用它的代码的地方。

import {createSelector} from 'reselect';

class FormPage extends PureComponent {
  state = { example: '' };

  change = e => this.setState({example: e.target.value});
  submit = () => {
    console.log('Submit: ', this.state.example);
  };

  runButtonClasses = createSelector(
    props => props.classes.runButtonLabel,
    runButtonLabel => ({runButtonLabel}));

  render() {
    const {title} = this.props;
    const {example} = this.state;

    return (
      <form>
        {title}
        <TextField
          InputProps={this.shallowMemoize('a', {
            // This example assumes use of transform-react-constant-elements to make this object always the same
            startAdornment: <InputAdornment position="start">Kg</InputAdornment>,
          }}}
          onChange={example}
          value={example} />
        <Button classes={this.runButtonClasses(classes)}>Run</Button>
        <Button onClick={this.submit}>Submit</Button>
      </form>
    );
  }
}
// ...
  <strong i="6">@withShallowMemoize</strong>
  render(memo) {
    const {title} = this.props;
    const {example} = this.state;

    return (
      <form>
        {title}
        <TextField
          InputProps={memo({
            startAdornment: <InputAdornment position="start">Kg</InputAdornment>,
          }}}
          onChange={example}
          value={example} />
        <Button classes={memo(classes)}>Run</Button>
        <Button onClick={this.submit}>Submit</Button>
      </form>
    );
  }

与其尝试优化 InputProps、类等……我们鼓励人们为他们的所有用例制作微型组件

如果这是使用 MUI 的推荐方式,我们甚至可能不需要纯模式。 如您所见,一旦您开始为常见用例制作小型辅助组件,这些组件本身就很容易成为纯组件。 在示例中WeightTextField现在永远不会重新渲染,只要value仍然相同,完全跳过 withStyles、InputProps 所需的任何记忆工作或 InputAdornment 设置。 当value确实发生变化时,无论如何我们都必须重新渲染 TextField,因此非纯InputProps={{...}}无关紧要。

我对这条路很好。 我喜欢理论上的微型组件; 尽管我讨厌我能想到的所有当前有效的语法/模式来编写它们。 我不想MyComponent = enhance(MyComponent) ,我想装饰它们,但是您无法装饰任何编写小组件的简短方法。 我也不喜欢把import {TextField} from 'material-ui';变成import WeightTextField from '../../../ui/WeightTextField ;`。

```js
让 WeightTextField = ({unit, InputProps, ...props}) => (
{...道具}
输入道具={{
开始装饰:{单元} ,
...输入道具
}}
onChange={示例}
值={示例} />
);
WeightTextField = pure(WeightTextField);

运行按钮 = 撰写(
withStyles(主题 => ({
标签: {
字体重量:'800',
},
})),
纯的
)(按钮);

const SubmitButton = pure(Button);

类 FormPage 扩展组件 {
状态 = { 示例:'' };

change = e => this.setState({example: e.target.value});
提交 = () => {
console.log('提交:', this.state.example);
};

使成为() {
const {title} = this.props;
const {example} = this.state;

return (
  <form>
    {title}
    <WeightTextField
      unit='Kg'
      onChange={example}
      value={example} />
    <RunButton>Run</RunButton>
    <SubmitButton onClick={this.submit}>Submit</SubmitButton>
  </form>
);

}
}
````

我有一个用例,我需要在一个大列表的页面上显示 500-2000 个复选框。 使用本机浏览器复选框,性能很好,但使用<Checkbox>组件,性能很差,并且与页面上的复选框数量呈线性关系。 示例: https :

我正在使用mui@next — 是否有一些策略我可以采用 _now_ 使其可行?

@威尔逊杰克逊
首先,不要做以下事情。 这将在每次渲染的每个复选框中创建一个新处理程序,这将取消优化您尝试执行的任何 PureComponent 优化。

  handleChange = index => event => {
    this.setState({

其次,制作自己的小组件来包装 Checkbox 并使该组件纯。 这有一个额外的好处,您可以添加所有复选框通用的任何属性。 而且,由于您有一个共同的问题,即每个项目都需要不同的更改事件处理程序,我们可以使用类组件并在组件中而不是在列表容器中执行此操作。

https://codesandbox.io/s/r7l64j6v5n

如果我们想要优化任何东西,就需要处理这些基本问题,否则仅仅让人们启用纯模式 MUI 实际上根本不会优化太多。 我能想到两种可能。

@dantman 做出这些 API 选择是为了在尝试足够快的同时尽可能地改进 DX。

与其尝试优化 InputProps、类等……我们鼓励人们为他们的所有用例制作微型组件

是的,我们有。 包装模式绝对是自定义库的鼓励方式。 它可以扩展到应用性能优化。 在用户空间上更容易,因为组件使用的可变性要低得多。 我们甚至可以在文档中添加有关这一点的常见问题解答或指南部分。

是的,我们有。 包装模式绝对是自定义库的鼓励方式。 它可以扩展到应用性能优化。 在用户空间上更容易,因为组件使用的可变性要低得多。 我们甚至可以在文档中添加有关这一点的常见问题解答或指南部分。

好的。 在这种情况下:

  • 我们想推荐像 recompose 这样的库,它可以轻松编写小型无状态组件。 哎呀,如果有人告诉我有另一种模式或库类似于 recompose 但可以构建像 recompose 的模式这样的纯无状态函数,但与使用装饰器语法一样好,我会欢迎它
  • 就像我们列出了关于如何在带有 MUI 的 js 库中使用各种自动完成库和 css 的建议一样,我们可能还想建议各种现有的文件夹组织模式和路径黑客库(那些让您将../../../../ui/Foo导入something-local/Foo ),可用于使用您自己的本地系列微型组件来包装 MUI 与使用import {TextField} from 'material-ui';一样好,并且不会感觉像开发人员轻松的回归.

@dantman太棒了,谢谢。

由于 withStyles(或者更确切地说 JSS)非常慢,我需要多次应用 sCU。 我不知道JSS的代码,但感觉它可以优化很多。 我通常使用 styled-components 或 glamorous,因此最终使用 JSS 和应用程序中的另一个,它们都优于 JSS。

虽然这些情况可能有点烦人,但它们很容易通过应用程序级别的 sCU 或更智能的状态更新来解决。 我还没有看到单个 MUI 组件慢到足以出现问题,而且我还没有在 MUI 中实际编写代码以花费大量时间。

并不是说它不可能更快,并且如果需要更少的监督肯定会更好,但至少在我看来,在这种情况下,花时间直接优化 JSS 比 MUI 更好。

@Pajn感谢您的反馈。 看到某些情况下 withStyles 性能有问题或样式组件优于它的情况真的很棒。

有人检查过这个 repo https://github.com/reactopt/reactopt吗?

$ click - button (text: مقالات ) => CssBaseline,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,ScrollbarSize,TransitionGroup,TouchRipple,Ripple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,Main,ScrollbarSize,TransitionGroup,TouchRipple,Ripple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple,TransitionGroup,TouchRipple

这些组件只需单击一下即可进行不必要的重新渲染,为什么不尝试应 Component Update 生命周期?

@尼玛77

有人检查过这个 repo https://github.com/reactopt/reactopt吗?
不,我使用简单的为什么更新和 chrome/react 开发工具功能。

这些组件只需单击一下即可进行不必要的重新渲染,为什么不尝试应 Component Update 生命周期?

见上面的讨论。 这并不适合每个用例。 对我来说,似乎 material-ui 应该同时提供 - 纯和非纯版本的组件。 我看到纯组件带来了巨大的性能提升。

@dantman

看看这个例子(点击旁边的插件,搜索“react-constant”并点击“transform-react-constant-elements”上的复选框),你会发现几乎没有任何优化:

这只是解决此问题的一种方法。 还有其他选项、​​插件和其他手工优化。 不要误会我的意思,但我对优化是好是坏的理论讨论不感兴趣。 我对 WORKS 的动手优化很感兴趣。 至少在我的设置中。 我上面写的所有内容都对我有用,并使我的应用程序至少可以在中低端移动设备上使用。 尽管我被迫进行更多优化,例如重新排序组件树,但如果没有纯组件和其他自动优化,则无法实现性能。 是的,我进行了大量的分析和优化以完成这项工作。

@Bessonov也许我们可以使用 prop 使 shouldComponentUpdate 方法进行浅比较(https://reactjs.org/docs/shallow-compare.html)或始终返回 false,
这不会增加两个版本的组件(纯和普通)的包大小

@lucasljj如果将其作为上述有状态组件的包装器完成,我不希望包大小显着增加。 另请参阅上面的配置文件:在 sCU 中,纯组件比return false;快。

纯组件或实现 sCU 的组件的问题是,如果您在其中使用非纯组件或children 。 见上面的陈述。 另一个要解决的问题是主题切换。 未经测试,但我认为这至少可以通过 Context API 克服。

@bossonov纯组件被认为是 react 的默认设置,并不是因为它们来晚了。 但我同意大多数人会创建 redux 表单样式包装器来减轻子树上它的缺失

您引用的有关纯组件和子组件的问题仅在顶级道具不传播给子组件时才会发生。 每个道具或状态更改都会触发通过子树的重新渲染,直到道具不会传播的级别。
因为纯组件的想法是它在每个 prop 或 state 变化时重新渲染。 我不太了解内部结构,但我想知道在您不能使用传递给每个孩子的每个组件更改属性。 您甚至可以为此目的使用新的上下文 api,这样就不必在整个树中传递道具。

@oliviertassinari

性能将是 v1 版本发布后的重点。

太好了,v1 发布了 :) 知道什么时候应该解决性能问题吗? 我注意到这个问题不是 post v1 里程碑的一部分。

@Bessonov我认为花一些时间更新路线图会很棒。 关于表现。 我有两件事可以付诸行动,但我希望能发现更多:

  1. 我们无法改进我们看不到的东西。 我们需要一个基准。 到目前为止,我已经看到了两个有趣的库:
  2. 渲染 CSS 服务器端需要约 30% 的 React 需要渲染的内容。 因为我们不支持style = f(props) (好吧,我们还不支持它:#7633)。 我们可以实现非常高效的缓存功能,将重复请求的成本降低到接近 0%。 如果 SSR 性能损害我们的业务指标,我可能会为办公室工作。
    但这不是唯一的选择,我们还可以考虑使用 Babel 插件在编译时而不是在运行时应用昂贵的 JSS 预设(以平衡包大小影响)cc @kof。

谢谢你的网址。

完全同意上面的 1 和链接 #4305。

  1. SSR 可以帮助加载第一页,但对缓慢的重新渲染或cordova 没有帮助。 虽然我个人可以忽略在桌面甚至移动设备上的首次加载,但首次加载后缓慢的重新渲染仍然会影响移动设备。

编译时优化会很棒,从我的角度来看应该是首选,因为它可以改善首次加载和重新渲染。

另一件事是有关如何使用 mui(和 babel 等)获得更好性能的文档或示例项目。

不知道能不能用jss-cache

ISTF + 预处理管道将能够减少一些毫秒。

在星期六,2018 5月19日,安东19:06 Bessonov [email protected]写道:

谢谢你的网址。

完全同意 1 并链接 #4305
https://github.com/mui-org/material-ui/issues/4305以上。

  1. SSR 可以帮助加载首页,但不能帮助加载缓慢
    重新渲染或科尔多瓦。 虽然我个人可以忽略第一次加载
    桌面甚至移动设备,缓慢的重新渲染仍然会影响移动设备
    第一次加载。

编译时优化会很棒,从我的角度来看应该
是首选,因为它可以改善首次加载和重新渲染。

另一件事是如何使用 mui 的文档或示例项目
(和 babel 等)以获得更好的性能。

不知道jss-cache http://cssinjs.org/jss-cache?v=v3.0.0 是否可以
用过的。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/mui-org/material-ui/issues/10778#issuecomment-390418709
或静音线程
https://github.com/notifications/unsubscribe-auth/AADOWGrCxNGqrT4MijiX8r9Ad32z6RsJks5t0FEtgaJpZM4S4woq
.

哦,react-jss(由 material-ui 使用)似乎很慢

@janhoeck不是,我敢肯定,您将无法证明。

@kof我很清楚微基准测试并没有说明太多,但是与真的很慢。 事实上,material-ui 的整体性能并不突出。

与阿佛洛狄忒相比 4x-6x 真的很慢

@Bessonov基准方法是关键。 例如,您可以在浏览器中尝试使用:
https://twitter.com/necolas/status/954024318308007937?lang=fr

在我的浏览器中的结果:
⚠️带着不确定性

capture d ecran 2018-06-12 a 17 58 54

material-ui 的整体性能不亮

我们很可能会受到 React 本身和组件成本的限制。

@Bessonov无论您用于检查性能,当涉及到 aphrodite 时,它​​都不是真的,因为它使用 asap 并延迟渲染,因此大多数基准测试测量 CPU 时间而不是最终渲染性能。 话虽如此, @oliviertassinari发布的替补

关于 MUI 性能,当您根本不使用任何抽象时,您可以变得更快,但这不是重点。 MUI next 非常快。 它是如此之快,以至于您永远不必担心它的性能,除非您做错了什么或滥用了库。

@kof不,默认情况下 MUI 速度不快。 我们被迫实施变通方法以获得可接受的性能。 可以肯定的是:您没有看到它是关于移动设备而不是高端 Mac 的吗?

永远不要担心它的性能,除非你做错了什么或滥用了库

好吧,上面是一些 codesanbox 示例。 如果我们只是在容器中使用 MUI 组件并将输入值保存在容器状态中,就像组件演示中所示,那么它很快就会变得无法使用。 包裹在 PureComponent 中,使用不受控制的组件、树重新排序、常量元素、记忆等帮助我们保持一些可接受的性能。 欢迎您展示如何正确使用 MUI,以获得更好的结果,例如在(atm 我们不再使用抽屉):
https://codesandbox.io/s/r1ov818nwm

@oliviertassinari感谢您的链接。 以下是在 nexus 4 上使用 Chrome 66 的结果。如您所见,结果差了 10 倍。 我认为在人行横道 21/chrome 51 中它可能会慢一点:

screenshot_2018-06-12-18-20-19

我们很可能会受到 React 本身和组件成本的限制。

恕我直言,不一定,正如其他人所提到的。 每次避免重新渲染都是性能和电池的巨大胜利。 不要忘记,性能是路线图上的第三点:+1:

这样看:如果你在移动设备上用 cssinjs 做的事情太慢,你也不应该使用这个 react 和大多数其他东西。 如果 cssinjs 减慢了你的速度,那么 React 组件会减慢你的速度。 您需要重新考虑级别 ob 抽象或应用优化。

@oliviertassinari就是那个 evtl。 mui 包装器可能会在更新时重新呈现 jss 样式? 可能有一些泄漏可能会做不需要的工作。

@kof

这样看 [...]

我明白你的意思。 但是 react 和 react(纯)组件本身并不是瓶颈,而且性能非常好。 例如,MUI 不使用 PureComponent(优点和缺点如上所述),但它们使我们的生活更安全。 @oliviertassinari提到有机会通过使用缓存或预编译器获得更好的性能。

不要误会我的意思,你们是很棒的家伙,我对每个 MUI 版本都感到非常高兴 :clap: 但是您也需要考虑性能,因为并非每个用户都通过高性能桌面访问网站

如果 react 不是性能瓶颈并且您确定它,JSS 也不会。 如果有一些不必要的工作正在进行并且css在更新时重新生成,我能想象的唯一需要验证的事情。

如果您需要优化,纯组件是您应该在应用程序级代码中使用的东西。 MUI 不必这样做,它是一个通用库,不应做任何假设。 PureComponent 并不总是好的。

JSS 从一开始就考虑性能,我花了无数个小时在微优化上。

这是荒唐的。 我无法阻止材料 ui 组件在每次交互时重新渲染。 不管我做什么,像这样简单的事情:

class RowText extends Component {
  shouldComponentUpdate = (nextProps, nextState) => {
    return false;
  };

  render() {
    const { title, artist } = this.props;
    return <ListItemText primary={title} secondary={artist} />;
  }
}

触发底层 Typography 元素的重新渲染。 这怎么可能?

嘿,这只会发生在列表和列表项中。 为什么 ? 有什么我可以做的吗?

@ danielo515没有完整的例子很难说。 List组件也使用上下文,因此如果您也更改该值,它也会触发重新渲染。

我明白@eps1lon。 但是,您能否解释一下上下文的变化是如何发生的? 我没有将任何内容传递给列表组件,只是在其中渲染丢失的孩子。

@danielo515基本上每次List渲染时。 新的上下文 API 启用了一些优化策略,我们可以通过记住上下文值来探索。

当前,如果值将 WRT 更改为严格相等,则响应上下文 API 会在每个消费者上触发重新渲染。 由于我们在每次对List渲染调用中创建一个新对象,因此每个消费者也将重新渲染。

所以现在一个简单的性能提升是包装List这样就不会频繁地重新渲染。 我会建议先做基准测试,或者更早地退出渲染。 重复渲染调用Typography不应该那么糟糕。

React.memo包装组件适用于任何没有孩子的东西。 我有一个包含 3 个 ExpansionPanel 和一些 14 个 FormControl 的表单,它在桌面上滞后。

如果没有针对这些性能问题的解决方案,我将无法继续使用 material-ui :s

@prevostc没有例子就很难说出什么问题。

@prevostc没有看到 codeandbox 复制,我们和你都不知道这是否与这个问题有关

@prevostc创建您自己的不需要孩子的组件,然后您可以

即创建您自己的纯/备忘录MyExpansionPanel组件,该组件接受数据/事件道具而不是子项,并负责渲染单个扩展面板。 然后使用<MyExpansionPanel ... />来渲染每个扩展面板。 然后重新渲染将仅限于单个扩展面板(或在两个扩展面板之间转换时)。

@oliviertassinari @kof @dantman
这是我的性能问题的代码和框再现: https ://codesandbox.io/s/yvv2y2zxxx

这是一个包含约 20 个字段(并不少见)的表单,您会在用户输入时遇到一些延迟。 在较慢的设备上,这种形式是不可用的。

性能问题来自对用户输入的大量重新渲染,但将 MUI 组件包装在纯组件 (React.memo) 中没有任何作用,因为这里的所有内容都有子级和子级,这将强制重新渲染 AFAIK(来源:https:// reactjs.org/docs/react-api.html#reactpurecomponent)

下面是我的手动基准测试的一些屏幕截图,一个没有任何优化,一个记录了所有内容,另一个使用具有本地状态的自定义输入组件以避免过于频繁地在整个表单上设置状态。
在每个配置中,用户输入协调大约需要 60 毫秒(远高于我需要以 60 fps 呈现的 16 毫秒。

请注意,我很高兴得知我做错了什么,因为我喜欢 MUI,如果没有简单的修复,我会很难过 <3

@dantman你怎么能写一个ExpansionPanel不接受任何React.ReactNode作为输入(孩子或道具)? 如果你的意思是我应该为我的应用程序中的每个面板编写一个特定的组件,不幸的是这是不可能的,我有太多的组件。

screenshot 2018-12-20 at 22 04 08

screenshot 2018-12-20 at 21 56 57

screenshot 2018-12-20 at 22 05 00

@dantman你怎么能写一个ExpansionPanel不接受任何React.ReactNode作为输入(孩子或道具)? 如果你的意思是我应该为我的应用程序中的每个面板编写一个特定的组件,不幸的是这是不可能的,我有太多的组件。

是的,而不是使用深度嵌套的面板树制作单个大型组件,而是将扩展面板部件分成多个组件。 像这样优化大树是不可能的。

这不是不可能的。 React 组件非常轻量级。 将大量组件中的代码块复制并粘贴到函数中,您就快完成了,之后您只需连接道具。 只要你 React.memo 那个功能并且避免传递破坏 props 纯优化的东西,那么事情就很容易优化了。

请记住,如果您正在创建一个小组件来从一个巨大的组件中分离出一个块。 它不必是具有自己的文件和自己的道具验证的复杂事物。 它可以是与使用它的组件相同的文件中的一个简单的功能组件,没有 propTypes。 即您可以在与它们相同的文件中创建仅对组件可用的小组件。

是的,Material UI 比低级 dom 元素慢一点。 但是,即使 MUI Input比原始input慢 10 倍,并且在 100 之后事情变得太慢。那么即使没有 MUI,您仍然会遇到问题,因为即使原始input快 10 倍如果您有 1000 个,

@prevostc这是通过将扩展面板拆分成一个小的相同文件组件来优化的演示。 正如您所看到的,当输入更新时,只有一个扩展面板会重新呈现。 重新渲染无关的扩展面板不会浪费时间。 如果我愿意,我什至可以做一些类似于具有相似模式的输入的事情,从而导致不相关的输入也不会重新渲染。

我会注意到这不仅是一个很好的优化模式,还是一个很好的编码模式。 在实际代码中,您不是制作输入数组,而是使用定义的名称、标签和用途明确地写出它们。 寻找边界和重复模式不仅可以优化事物,还可以减少样板文件,使代码更加干燥和可读。 即而不是InputWrapper我可能会将 FormControl+InputLabel+FormHelperText+Input 组合分解为一个小的本地 SimpleTextInput 组件。 这不仅会优化它(导致不相关的输入不会重新渲染),而且还意味着代码不需要重复额外的样板。

https://codesandbox.io/s/0o7vw76wzp

screen shot 2018-12-20 at 2 51 31 pm

阅读本文后,我得出的结论是,为了优化 mui,您需要创建更小的特定组件。 这是我已经意识到并成功尝试的事情。 但是,我也确实明白没有办法优化列表组件,因为上下文 api 更改了所有输入道具。

问候

好的,这里是更新的压力测试https://codesandbox.io/s/wz7yy1kvqk

我同意@dantman关于这个通用点https://github.com/mui-org/material-ui/issues/10778#issuecomment -449153635 但我没想到这么低数量的组件会出现这种性能问题,但请忍受我找到了性能问题的根源。

JSS 慢吗? (剧透警报:否)

参考该线程中的一些早期评论,我在压力测试中添加了一个复选框以删除对withStyles所有调用,我得出的结论是 JSS 速度很快,并且它不是性能问题的根源(正如@kof在 https://github.com/mui-org/material-ui/issues/10778#issuecomment-396609276 中指出的那样)。

screenshot 2018-12-22 at 15 17 26

修复

对于我的特定用例,我能够查明问题是每个表单输入都在表单更新时重新呈现,即使实际上只有一个输入发生了变化。
在下面的屏幕截图中,我将 FormControl 和 Input 都包装在一个记忆化组件中,如果值没有改变,则避免渲染。 @dantman实际上建议我为每个ExpansionPanel创建一个特定的组件,但这是一种不太通用的解决方案。 作为旁注,每个面板仍然重新渲染,性能远非最佳,但现在已经足够了。

screenshot 2018-12-22 at 15 18 22

所以? 下一步是什么?

我认为如果不对当前依赖于React.ReactNode组合的当前 API 进行大量更改,就无法通过更改 material-ui 代码来避免此类问题。
但正如@dantmanhttps://github.com/mui-org/material-ui/issues/10778#issuecomment -449153635 中提到的那样,MUI 比它预期的要慢一些。 恕我直言,根本不解决这个问题是一个错误。

意识到这个问题后,我们可能需要创建一个与性能问题以及如何解决这些问题相关的文档页面。 即使他的页面将主要重定向到官方文档(https://reactjs.org/docs/optimizing-performance.html)并列出可能导致性能问题的组件,这也是一个开始。
发生此类问题时,最好向用户发出 console.warn 警告,但我无法找到一种方法来检测 material-ui 级别的问题。

@prevostc这个消息让我开心,这就是我喜欢的社区。 你有什么想法 mui 可以改变以提高性能并避免用户空间优化的需要吗? API 更改可能是可行的。

我不:s

我对 MUI 的内部了解不够,现在不知道如何提高它的原始性能(没有 api 更改)。 我正在研究一些想法,但截至今天还没有定论:我有一个应用程序,其中一个无线电组在它的直接父级不是时重新渲染,还不能在本地重新生成它。

任何 API 更改都会考虑从 API 中删除任何React.ReactNode道具(子项和其他道具,如图标等),但我找不到保持相同可配置性的好方法。
这是我尝试过的: https :

另外需要注意的是,MUI 在开发模式下特别慢,因为在开发模式下反应特别慢。 不知道有没有什么方法可以改进。

在添加功能(如@Bessonov建议的功能)以优化 Material-UI 当前面临的性能问题方面是否有任何进展?
当我们开始在我们的项目中使用这个很棒的库时,我不知道随着项目越来越大,可能会出现这样的性能问题; 此外,我在 Material-UI 文档中没有看到任何部分告诉我可能导致 Material-UI 变慢并损害 UX 的情况。
本期报告了许多与 Material-UI 直接或间接相关的性能问题。 我认为将它们列在另一个问题中是个好主意,以便每个人都可以跟踪进度。 如果你觉得没问题,我可以开一个新问题。

@mkermani144我们还没有看到与 Material-UI 做错的事情直接相关的性能报告。 到目前为止,这个问题已被用作为遇到困难的人提供帮助的论坛。 您是否确认没有报告任何可操作的内容? 我要说的很明显, React 抽象是有代价的。 包装主机的每个组件都会增加渲染树的重量,它会减慢它的速度。 虽然您可以使用本机主机渲染 100 多个列表项,但当您使用类组件包装它们时,它开始成为一个问题。 它不是 Material-UI 特有的。

桌子

开发模式

让我们以表格为例。 这是人们发现缓慢的组件。 我们已经记录了虚拟化,它很有帮助。

在下面的测试用例中,我们在开发模式下渲染了 100 个项目。 我们可以考虑以下几种情况:

  1. 原始表格: https : //codesandbox.io/s/v066y5q7z363 毫秒
  2. Table Material-UI Master: https://codesandbox.io/s/m4kwmvj9ly : 250ms in the render
  3. Table Material-UI Next: https://codesandbox.io/s/2o35yny1jn : 262ms in the render

因此,在开发模式下使用 Material-UI 而不是宿主元素的开销约为 x4(生产中的差异更小!),仅仅是因为我们创建了中间组件。 这就是为什么在呈现大约 100 个表项的列表后虚拟化开始变得重要的原因。 现在,我们可以深入了解为什么以及我们可以做些什么?

  1. Table Raw + 函数组件。 为什么是功能组件? 我们想抽象使用的类名。 我们不希望组件用户重复它们。
    https://codesandbox.io/s/1zl75mwlpj渲染中为
  2. Table Raw + 函数组件 + forwardRef。 为什么是forwardRef? 我们希望组件是“透明的”,能够通过引用访问宿主元素。
    https://codesandbox.io/s/32o2y0o9op :渲染中为120 毫秒
  3. Table Raw + 函数组件 + forwardRef + withStyles。 为什么使用样式? 因为我们想要为我们的组件设置样式:
    https://codesandbox.io/s/j2n6pv768y :渲染时间为200 毫秒
  4. Table Raw + 函数组件 + forwardRef + makeStyles。 makeStyles 可能比 withStyles 更快,让我们试试:
    https://codesandbox.io/s/yw52n07l3z :渲染时间为130 毫秒

因此,我们有一个可用的杠杆:将所有组件从 withStyles 迁移到 makeStyles。 在开发模式下,我们可以获得大约 +30% 的性能 (262 / (262 - 70))。

生产方式

我在生产模式下运行了相同的测试用例:

  • n°1 30ms在水合物中
  • n°3 106ms在水合物中
  • n°4 40ms在水合物中
  • n°5 41ms在水合物中
  • n°6 80ms在水合物中
  • n°7 57ms在水合物中

所以从withStylesmakeStyles迁移理论上在生产模式下仍然是+30% 的加速。

如果你觉得没问题,我可以开一个新问题。

@mkermani144如果你有一个特定的案例,Material-UI 做错了,当然。

我阅读了此问题下方的所有评论。 我的问题不适合前面提到的任何其他问题。

我有一个List组件,其中包含一些ListItem s,其中一个被选中并突出显示。 当我单击另一个ListItem以选择并突出显示它时,整个列表(包含其子项)会再次重新呈现。

我知道这个问题可能看起来与之前的评论完全相同,但事实并非如此; 至少我是这么认为的。

让我们看看 React 分析器的结果:

image
如您所见,我在图像的顶层有一个MyList组件。 这个组件只是 MUI List一个包装器,只是让它变得纯粹,即:

class MyList extends React.PureComponent {
  render() {
    return (
      <List>
        {this.props.children}
      </List>
    );
  }
}

我添加了这个包装器,因为@eps1lon他的一条评论中提到重新渲染List会导致上下文更新,并且此上下文更新使所有消费者(包括ListItem s)也重新渲染.

我还尝试使我所有的ListItem纯并再次分析该应用程序。 结果是一样的,除了我的自定义组件(即MyListItem )没有重新渲染 _itself_,而是它下面的所有子组件 __did__。

我知道问题的发生是因为上下文 MUI 用于样式重新渲染_somehow_。 但我不知道为什么会发生这种重新渲染以及如何避免它。

或者,我做错了什么?

注意:我使用 MUI new (alpha) 样式解决方案,即@material-ui/styles 。 我不知道这是否重要。

@mkermani144删除带有原生元素的 Material-UI,观察重新渲染仍然存在。 纯粹的逻辑不会像这样帮助。 React.createElement 在每次渲染时都会创建新的引用,它会使您的 PureComponent 无效。

是的,我知道元素是对象,对象在 Javascript 中并不严格相等,所以sCU失败。

但是当你说React.createElement再次被调用时,我不明白你的意思。 哪个调用createElement ? 如果您的意思是List内部的调用,则只有在重新渲染时,它才会为其子项( ListItem s)调用createElement 。 如果没有重新渲染,则不会调用createElement并且不会发生重新渲染。 问题是List本身被重新渲染。

@mkermani144如果您可以创建一个最小的复制示例,我们可以查看它。

您的MyList (因此List )被重新渲染,因为渲染MyList (我们称之为MyComponent )的组件被重新渲染。 MyList上的 PureComponent 没有帮助,因为MyComponent已被重新渲染并为MyList创建了新的子项,因此MyList的检查失败。

您的MyComponent可能会重新渲染,因为您可以在其中存储选择哪个项目的状态。

我认为 List 的 MUI 实现应该更改为不重新创建每次渲染的 List 上下文值
在这里: https :

所以让 List 看起来像这样:

const List = React.forwardRef(function List(props, ref) {
  const {
    children,
    classes,
    className,
    component: Component,
    dense,
    disablePadding,
    subheader,
    ...other
  } = props;
  const context = React.useMemo(() => ({ dense }), [dense]);

  return (
    <Component
      className={clsx(
        classes.root,
        {
          [classes.dense]: dense && !disablePadding,
          [classes.padding]: !disablePadding,
          [classes.subheader]: subheader,
        },
        className,
      )}
      ref={ref}
      {...other}
    >
      <ListContext.Provider value={context}>
        {subheader}
        {children}
      </ListContext.Provider>
    </Component>
  );
});

这将简化创建 ListItems 的sCU版本

由于呈现 MyList(我们称之为 MyComponent)的组件被重新呈现,因此您的 MyList(以及 List)被重新呈现。 MyList 上的 PureComponent 没有帮助,因为 MyComponent 已被重新渲染并为 MyList 创建了新的子项,因此 MyLists 检查失败。

@Pajn不,看看我的React 分析器结果MyList没有重新渲染(灰色),但List重新渲染(蓝色)。 我不坚持PureComponentMyList 。 即使我为MyList实现了sCU以便它不会重新渲染, List __does re-render__。

@oliviertassinari
我创建了一个最小的复制示例:

import React, { Component } from 'react';

import StylesProvider from '@material-ui/styles/StylesProvider';
import ThemeProvider from '@material-ui/styles/ThemeProvider';

import { createMuiTheme } from '@material-ui/core';

import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';

const theme = createMuiTheme({});

const MyListItem = React.memo(ListItem, (prev, next) => prev.selected === next.selected);

class App extends Component {
  state = {
    selected: null,
  }
  render() {
    return (
      <StylesProvider>
        <ThemeProvider theme={theme}>
          <List>
            {[0, 1, 2, 3, 4].map(el => (
              <MyListItem
                button
                selected={el === this.state.selected}
                onClick={() => this.setState({ selected: el })}
              >
                {el}
              </MyListItem>
            ))}
          </List>
        </ThemeProvider>
      </StylesProvider>
    );
  }
}

export default App;

React Profiler 结果(点击第 4 个列表项后):

image

正如你所看到的,它按预期工作,即不存在额外的重新呈现(除非ButtonBase内部组件ListItem S)。 问题是,这种复制_太小了_; 我跳过了很多东西。

我知道你不能告诉我我的代码有什么问题导致额外的重新渲染。 但是我问你一个问题:什么会导致在包装 MUI 组件的WithStylesInner组件中重新渲染?

@mkermani144您如何看待此修复程序?

--- a/packages/material-ui/src/List/List.js
+++ b/packages/material-ui/src/List/List.js
@@ -40,6 +40,13 @@ const List = React.forwardRef(function List(props, ref) {
     ...other
   } = props;

+  const context = React.useMemo(
+    () => ({
+      dense,
+    }),
+    [dense],
+  );
+
   return (
     <Component
       className={clsx(
@@ -54,7 +61,7 @@ const List = React.forwardRef(function List(props, ref) {
       ref={ref}
       {...other}
     >
-      <ListContext.Provider value={{ dense }}>
+      <ListContext.Provider value={context}>
         {subheader}
         {children}
       </ListContext.Provider>

您要提交拉取请求吗? :) 我们对 Table 组件使用相同的策略。 它工作得很好。 感谢您报告问题!

@oliviertassinari当然。 这正是@Pajn之前建议的。 我提交了PR

14934被合并; 但是,如果List组件足够大,则无论优化多少,都可能成为性能瓶颈。 我们不应该提供一个示例来展示react-windowreact-virtualizedList组件的用法,就像我们在Table文档中所使用的那样吗?

我们不应该提供一个示例来展示 react-window 或 react-virtualized 与 List 组件的用法,就像我们在 Table 文档中提供的那样?

那太好了:+1:

Fwiw 我构建了一个聊天应用程序,该应用程序需要呈现大量联系人列表。 我遇到了同样的问题
@mkermani144有。

https://stackoverflow.com/questions/55969987/why-do-the-children-nodes-rerender-when-the-parent-node-is-not-even-being-update/55971559

@henrylearn2rock您是否考虑过使用虚拟化? 我们为列表添加了一个演示: https: //next.material-ui.com/demos/lists/#virtualized -list。

这也真的让我绊倒了。 我认为大多数人(包括我在内)都认为纯组件下的所有内容都不会被重新渲染,而这个库显然不是这种情况。 我将按照您最近的建议尝试虚拟化。 谢谢!

我认为大多数人(包括我在内)都认为纯组件下的所有内容都不会被重新渲染,而这个库显然不是这种情况。

这不是 React.PureComponent 或 React.memo 的工作方式。 它只影响组件本身。 如果上下文发生变化,孩子们可能仍然需要重新渲染。

@pytyl你能分享一下你使用 PureComponent 并期望它防止在其子树中重新渲染的代码吗?

@eps1lon以下文档使它看起来好像从 shouldComponentUpdate 返回 false 会自动跳过子组件中的重新渲染。
https://reactjs.org/docs/optimizing-performance.html#shouldcomponentupdate -in-action

由于对于以 C2 为根的子树,shouldComponentUpdate 返回 false,React 没有尝试渲染 C2,因此甚至不必在 C4 和 C5 上调用 shouldComponentUpdate。

也许我错了? 以下是我的分析器的快照。 只是为了测试,我在我的 Menu 组件中为 shouldComponentUpdate 显式返回 false :

Screen Shot 2019-05-08 at 7 46 32 PM

这使得我所有的子组件(类别、类别、类别项目、类别项目)都不会重新渲染。 许多与 MUI 相关的东西似乎在底部重新渲染,这似乎导致了很多延迟。 像 withStyles、Typography、ButtonBase 之类的东西。 对 React 还是有点新,所以请原谅我的无知。 下面是我的菜单组件代码(我为 shouldComponentUpdate 返回 false):

import React, { Component } from "react";
import Categories from "./Categories";
import { withStyles, Paper } from "@material-ui/core";

const styles = theme => ({
  root: {
    paddingTop: 0,
    marginLeft: theme.spacing.unit * 2,
    marginRight: theme.spacing.unit * 2,
    marginTop: theme.spacing.unit * 1
  }
});

class Menu extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.categories.length == this.props.categories.length) {
      return false;
    }
    return true;
  }

  render() {
    const { classes, categories } = this.props;

    return (
      <Paper className={classes.root}>
        <Categories categories={categories} />
      </Paper>
    );
  }
}

export default withStyles(styles)(Menu);

我需要一个完整的 codeandbox 来理解这个问题。

@ eps1lon我会尝试为明天制作一个。 谢谢你。

@eps1lon这里是代码笔:
https://codesandbox.io/s/348kwwymj5

简短的介绍
这是一个基本的餐厅菜单应用程序(通常有 100 多个菜单项)。 当用户单击菜单项时,它会打开一个“添加到订单”对话框。 我将附上一些分析器表现不佳的情况(这些统计数据不在生产版本中)。

系统
MacBook Pro(视网膜显示屏,13 英寸,2015 年初)
3.1 GHz 英特尔酷睿 i7
火狐 66.0.3

案例 1(用户点击菜单项)
渲染持续时间:218ms

Screen Shot 2019-05-10 at 4 45 26 AM

Screen Shot 2019-05-10 at 4 45 48 AM

案例 2(用户单击对话框中的添加到订单按钮)
渲染持续时间:356ms

Screen Shot 2019-05-10 at 4 46 24 AM

Screen Shot 2019-05-10 at 4 47 10 AM

我确定我在这里犯了一些新手错误,因此非常感谢任何指导。

由于 WithStyles(ButtonBase) 被重新渲染,我假设 WithStyles 使用重新创建的上下文,即使它不需要。

我设法找到了这个https://github.com/mui-org/material-ui/blob/048c9ced0258f38aa38d95d9f1cfa4c7b993a6a5/packages/material-ui-styles/src/StylesProvider/StylesProvider.js#L38但找不到地方StylesProvider 在实际代码中使用(GitHubs 搜索不是很好),但这可能是原因。

@eps1lon是否知道这可能是原因? 如果是,则上下文对象上的 useMemo 可能会解决此问题。 虽然我不知道 localOptions 是否稳定或者 useMemo 是否需要进一步传播。

也许。 但是您应该首先调查为什么使用 StylesProvider 的组件会重新呈现。 这要么位于树的顶部,要么位于某个 UI 边界。 在任何一种情况下,这些都应该很少重新渲染。 请记住,无论如何,react 上下文并未针对频繁更新进行优化。

我们不应该仅仅因为在渲染过程中重新创建了一些对象而过早地优化这些东西。 记忆化不是灵丹妙药。 所以没有一个具体的例子,我无能为力。 是的东西重新渲染。 有时比他们需要的更频繁。 但是浪费的重新渲染并不意味着它是性能瓶颈的原因。

@pytyl我看过你的代码和盒子,渲染架构有问题。 单击菜单项时,您会重新渲染所有内容。 您的 GlobalContext 跳过了纯逻辑。

@eps1lon我认为我们应该关闭这个问题。 最好将重点放在具体确定的问题上。

TL;DR:创建上下文切片,记忆上下文值,特别是 material-ui 没有问题: https :

做了一些挖掘,问题是你有这个在渲染过程中重新创建的大全局上下文。 当您单击重新创建全局上下文时,您将重新渲染您的应用程序。 您的 CategoryItem 正在收听它,它在您的应用程序中出现了 100 次。 由于您有 100 个 material-ui 菜单项,因此您会遇到经典的死亡一千次。

因此具有讽刺意味的是,解决方案的一部分是记忆上下文值,但重要的部分是识别单独的上下文切片。 似乎状态和调度上下文是合适的。 在将 useContext 与 useReducer 一起使用时推荐这样做,并且似乎适合这里。

这可以创建一棵相当大的树,并在您拥有的上下文越多的情况下渲染道具地狱。 我鼓励你看看useContext 。 如果您开始面对这些问题,将会有很大帮助。

@oliviertassinari用解决方案收集常见的陷阱是个好问题。 我们可以决定是否要从中创建单独的问题。

@oliviertassinari @eps1lon感谢您的修改! 性能似乎很棒。

我只是遇到了渲染性能缓慢的问题。 我通过用<div>替换<Box>组件的所有实例来完全解决它。 我使用 react devtools 火焰图进行调试,从大约 420 毫秒到 20 毫秒。

<Box> es;
Screen Shot 2019-08-16 at 12 47 25 AM

没有<Box> es:
Screen Shot 2019-08-16 at 12 42 38 AM

@mankittens您可以保留 Box 组件,使用 styled-components 作为样式引擎。 性能会好很多。 在不久的将来,JSS 应该会变得更好https://github.com/mui-org/material-ui/pull/16858。

我正在关闭这个问题。 对于每个潜在的改进领域,我们需要一份专门的绩效报告,而不是一个保护伞线程。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

ryanflorence picture ryanflorence  ·  3评论

reflog picture reflog  ·  3评论

ghost picture ghost  ·  3评论

activatedgeek picture activatedgeek  ·  3评论

rbozan picture rbozan  ·  3评论