Material-ui: Melhorar o desempenho da IU do Material

Criado em 23 mar. 2018  ·  93Comentários  ·  Fonte: mui-org/material-ui

Em primeiro lugar, muito obrigado por esta incrível biblioteca de componentes! É ótimo!

Eu adicionei uma gaveta em meu novo aplicativo. Principalmente, copiei um exemplo de gaveta. Apenas para PoC eu multipliquei

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

seção.

Depois disso, parece muito lento, especialmente no dispositivo móvel (nexo 4, cordova com faixa de pedestres 20). Eu uso o modo dev, mas o modo prod não acelera muito.

Por meio das ferramentas react dev, percebi que os componentes em mailFolderListItems renderizados em cada clique de link no react-router ou mesmo no menu aberto. Demora até 50-60ms para renderizar novamente ONE {mailFolderListItems} . eu uso

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

Para eliminar a incerteza com outros componentes do aplicativo, converti mailFolderListItems em Componente e desativei a re-renderização:

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

    shouldComponentUpdate() {
        return false;
    }

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


                <Divider />
                <MailFolderListItems />

Depois disso, essa parte parece OK.

Encontrei https://github.com/mui-org/material-ui/issues/5628 . Eu sugiro revisitá-lo . Otimizar shouldComponentUpdate é o passo fundamental e mais importante para obter desempenho. PureComponent é apenas o atalho mais comum.

Além disso, notei que muito tempo (1-2 ms e mais para CADA componente material-ui) é gasto em WithStyles .

  • [x] Pesquisei os problemas deste repositório e acredito que não seja uma duplicata.

Comportamento esperado

Espero obter o máximo de desempenho de reação possível para esta grande biblioteca.

Comportamento Atual

O aplicativo fica mais lento com cada componente do material-ui.

Etapas para reproduzir (para bugs)

Não forneço o exemplo de reprodução ainda, porque acabei de copiar da página de demonstração do componente, mas, se necessário, posso fornecer uma demonstração do codesandbox. Para o navegador, é perceptível, se o navegador ficar lento por fator> = 5x na configuração de desempenho.

Seu Ambiente

| Tech | Versão |
| -------------- | --------- |
| Material-UI | 1.0.0-beta.38 |
| Ícones da interface do usuário do material | 1.0.0-beta.36 |
| React | 16.2.0 |
| navegador | faixa de pedestres de cordova 20 (igual a cromo android 50) |

discussion performance

Comentários muito úteis

@oliviertassinari como um grande desenvolvedor, como você pode escrever coisas assim? Por que você usa _suas suposições pessoais_ como argumentos e não como fatos? Acho que apresentei fatos suficientes acima. Fiz de tudo para mostrar, porque gosto desse projeto e quero contribuir. Não é o suficiente? Ok, então mais fatos e _nenhuma_ suposições.

Só para você eu reduzo para 10 botões. 10! Isso significa que quaisquer 10 componentes (pior: contêineres) do material-ui tornam o aplicativo inteiro mais lento até que ele não possa

Botão normal:
image

PureButton:
image

Isso ainda é uma melhoria de 8x! É enorme! Você pode imaginar o quão importante isso é no dispositivo móvel?

Em vez de 100 botões, você encontrará no máximo 10 botões em uma página. Ainda assim, você encontrará 10 grades, 10 X, etc.

Usei o botão porque é um dos componentes mais simples! Isso mostra que o material-ui está quebrado do ponto de vista do desempenho. Agora, imagine os componentes de um contêiner como AppBar, Toolbar, List, Drawer! Isso é ainda pior! Você obtém rapidamente 20 componentes / contêineres em cada página. E porque você não expira nenhuma lentidão em seu poderoso desktop / mac, isso não significa que o material-ui não seja incrivelmente lento.

react-intl, a verificação entre os adereços anteriores e novos será sempre falsa. Você desperdiçará ciclos de CPU. Portanto, x13 -> x0,8

Mostre-me um exemplo em codesandbox. Não vejo por que isso deveria acontecer. Tenho certeza de que isso acontece apenas com o uso incorreto de componentes de reação. E o exemplo oficial mostra o uso incorreto, porque parecem assinantes de contexto react-intl não aplicados. Mas há muitos outros componentes que estão alinhados com as diretrizes de reação e as melhores práticas para manter o desempenho.

BTW: WithStyles consome até 2,27ms para o botão no dispositivo móvel. 8 componentes e você abaixo de 60fps.

Todos 93 comentários

Espero obter o máximo de desempenho de reação possível para esta grande biblioteca.

@Bessonov Performance será um foco após o lançamento da v1. Tentamos manter o núcleo da biblioteca o mais rápido possível. Deve ser bom o suficiente para + 90% dos casos. Pelo menos, é minha experiência usando a biblioteca até agora. Você não forneceu nenhuma referência externa, além de chamar nossa atenção para o fato de que o desempenho é importante, não podemos tornar esse problema acionável. Se você conseguir identificar uma limitação de raiz de desempenho do Material-UI que possamos investigar (com um codesandbox ou repositório de reprodução), indique-a. Até então, estou encerrando o assunto.

@oliviertassinari obrigado pela sua resposta rápida! Estou muito feliz em saber que esse desempenho será o foco após o lançamento.

Deve ser bom o suficiente para + 90% dos casos. Pelo menos, é minha experiência usando a biblioteca até agora.

No desktop - sim, é. Mas no celular é muito lento. Apenas a gaveta e alguns botões tornam o aplicativo lento. Não responde e consome mais energia conforme necessário.

Você não forneceu nenhuma referência externa, além de chamar nossa atenção para o fato de que o desempenho é importante, não podemos tornar esse problema acionável.

Forneci uma referência ao problema já levantado e uma referência à documentação de reação.

Se você for capaz de identificar uma limitação de raiz de desempenho do Material-UI que podemos investigar (com um código de reprodução e caixa ou repositório)

Como eu disse, eu consigo. Aqui está uma comparação de componente puro e não puro. As etapas para reproduzir são:

  1. Use o Chrome e vá para https://codesandbox.io/s/r1ov818nwm
  2. Clique em "Abrir em uma nova janela" na janela de visualização
  3. Abra as ferramentas de desenvolvimento e vá para a guia "desempenho"
  4. Acelere sua CPU em pelo menos 5x (dependendo do seu PC - pode ser 10x) para refletir o dispositivo móvel
  5. Clique no hambúrguer e veja como fica.

E agora:

  1. Vá para index.js
  2. altere pure para true
  3. Salve 
  4. Repita as etapas de cima
  5. Aproveite!

Este exemplo é um pouco irreal porque inseri muitos elementos List. Mas, como eu disse, no celular é muito rápido apontar onde você "sente" cada ação.

Aqui está meu problema com WithStyles . Está na área de trabalho com afogamento da CPU. Eu gostaria de postar screenshots de um dispositivo real na segunda-feira.

Hora de WithStyles(ListItem) :
image

Hora de ListItem :
image

A diferença é de 1,26 ms apenas para um componente ListItem . Com 13 componentes como este você não pode mais renderizar com 60fps. Mas percebi no Desktop, que essa duração nem sempre está aqui.

Aqui está uma comparação de componente puro e não puro

Btw, eu não digo que PureComponent é a solução para todos os problemas de desempenho. Apenas digo que pode ser um dos ajudantes a tornar o material-ui utilizável no celular.

A diferença é de 1,26 ms apenas para um componente ListItem.

@Bessonov O componente WithStyles deve ser muito barato para instâncias repetitivas do mesmo componente. Nós injetamos o CSS apenas uma vez.
Talvez você esteja atingindo as limitações do React e o custo de componentes de ordem superior.
Existe uma gama de soluções para mitigar o problema de desempenho no React. Por exemplo, você pode usar memoização e virtualização de elemento. Estou mantendo este problema aberto como o ponto de partida para futuras investigações de desempenho.
Não acho que haja muito que possamos fazer sobre isso agora. Obrigado por levantar a preocupação.

Ok, essa é apenas uma parte. O que você acha sobre a parte mais importante - otimizar shouldComponentUpdate ?

EDIT: Eu quero dividir este problema em duas partes. Esta parte é sobre shouldComponentUpdate e a outra parte sobre WithStyles , se posso mostrar mais informações. É que ok para você?

otimizar shouldComponentUpdate

@Bessonov Não implementamos tal lógica propositalmente no lado do Material-UI por dois motivos:

  1. A maioria de nossos componentes aceita elementos react, uma mudança de referência de elemento react em cada render, fazendo a lógica desperdiçar ciclos de CPU sistematicamente .
  2. Quanto mais próxima a lógica shouldComponentUpdate estiver da raiz da árvore React, mais eficiente ela será. Quero dizer, quanto mais poda você puder fazer na árvore, melhor. Os componentes da IU do material são de baixo nível. Há baixa oportunidade de

A única oportunidade que encontramos foi com os ícones. Deixe-nos saber se você pode identificar novos.

a outra parte para WithStyles

+ 90% do tempo gasto em WithStyles é realmente gasto em JSS , há muito poucos que podemos fazer sobre isso no lado do Material-UI.
Pelo menos, tenho identificado uma oportunidade de armazenamento em cache para renderização do lado do servidor recentemente para melhorar muito o desempenho na renderização repetitiva. Mas é apenas do lado do servidor.

A maioria de nossos componentes aceita elementos react, uma mudança de referência de elemento react em cada render, fazendo a lógica desperdiçar ciclos de CPU sistematicamente.

Isso depende do uso e pode ser melhorado. Eu não me comparo, mas tenho certeza de que o uso correto do componente e shouldComponentUpdate otimizado (com comparação superficial) é menos caro do que o componente não otimizado, especialmente para estruturas imutáveis ​​(e immutable.js não é necessário por falar nisso). Tíquete relacionado: https://github.com/mui-org/material-ui/issues/4305

Por exemplo, em https://github.com/mui-org/material-ui/blob/v1-beta/docs/src/pages/demos/drawers/PermanentDrawer.js#L68 isso leva a novos objetos e ganha PureComponent sem sentido:

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

porque ele retorna toda vez que um novo objeto. Mas não:

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

O mesmo para componentes.

Quanto mais próxima a lógica shouldComponentUpdate estiver da raiz da árvore React, mais eficiente ela será. Quero dizer, quanto mais poda você puder fazer na árvore, melhor.

Sim, é verdade.

Os componentes da IU do material são de baixo nível. Há baixa oportunidade de alavancagem.

Isso é parcialmente verdade. Como você pode ver em https://codesandbox.io/s/r1ov818nwm , acabei de embrulhar List em PureComponent . Nada mais e isso acelera a gaveta por um fator significativo. Eu diria que isso é verdade para todos os componentes, pelo menos os que usam {this.props.children} . Criei outra demonstração: https://codesandbox.io/s/my7rmo2m4y

Há apenas 101 botões (puro = falso) e botões agrupados em PureComponent (puro = verdadeiro, veja de onde o botão é importado). Mesmo resultado do jogo, se eu clicar no botão "Clicar":

Botão normal:
image

Botão envolvido (com sobrecarga e assim por diante):
image

Como você pode ver, há 637ms ​​vs. 47ms apenas para botões normais! Você ainda acha que otimizar shouldComponentUpdate (ou apenas PureComponent ) não vale a pena? :)

EDITAR: corrigir o texto no início

@oliviertassinari @oreqizer Notei uma coisa interessante. Parece que extends PureComponent melhor desempenho porque Component com

  shouldComponentUpdate() {
    return false;
  }

image

EDIT: Eu acho que isso é uma otimização interna de reação.

Como você pode ver, há 637ms ​​vs. 47ms apenas para botões normais! Você ainda acha que otimizar shouldComponentUpdate (ou apenas PureComponent) não vale a pena? :)

@Bessonov Está demonstrando o potencial da lógica pura. Sim, pode ser muito útil! É uma melhoria x13. Mas não acho que seja próximo às condições da vida real:

  • Em vez de 100 botões, você encontrará no máximo 10 botões em uma página. Ainda assim, você encontrará 10 grades, 10 X, etc.
  • Em vez de propriedades simples fornecidas para os elementos de nível inferior, você usará algo como react-intl, a verificação entre os adereços anteriores e os novos sempre será falsa. Você desperdiçará ciclos de CPU . Então, x13 -> x0,8 ou mais

@oliviertassinari como um grande desenvolvedor, como você pode escrever coisas assim? Por que você usa _suas suposições pessoais_ como argumentos e não como fatos? Acho que apresentei fatos suficientes acima. Fiz de tudo para mostrar, porque gosto desse projeto e quero contribuir. Não é o suficiente? Ok, então mais fatos e _nenhuma_ suposições.

Só para você eu reduzo para 10 botões. 10! Isso significa que quaisquer 10 componentes (pior: contêineres) do material-ui tornam o aplicativo inteiro mais lento até que ele não possa

Botão normal:
image

PureButton:
image

Isso ainda é uma melhoria de 8x! É enorme! Você pode imaginar o quão importante isso é no dispositivo móvel?

Em vez de 100 botões, você encontrará no máximo 10 botões em uma página. Ainda assim, você encontrará 10 grades, 10 X, etc.

Usei o botão porque é um dos componentes mais simples! Isso mostra que o material-ui está quebrado do ponto de vista do desempenho. Agora, imagine os componentes de um contêiner como AppBar, Toolbar, List, Drawer! Isso é ainda pior! Você obtém rapidamente 20 componentes / contêineres em cada página. E porque você não expira nenhuma lentidão em seu poderoso desktop / mac, isso não significa que o material-ui não seja incrivelmente lento.

react-intl, a verificação entre os adereços anteriores e novos será sempre falsa. Você desperdiçará ciclos de CPU. Portanto, x13 -> x0,8

Mostre-me um exemplo em codesandbox. Não vejo por que isso deveria acontecer. Tenho certeza de que isso acontece apenas com o uso incorreto de componentes de reação. E o exemplo oficial mostra o uso incorreto, porque parecem assinantes de contexto react-intl não aplicados. Mas há muitos outros componentes que estão alinhados com as diretrizes de reação e as melhores práticas para manter o desempenho.

BTW: WithStyles consome até 2,27ms para o botão no dispositivo móvel. 8 componentes e você abaixo de 60fps.

Por que você usa suas suposições pessoais como argumentos e não como fatos?

O que te faz pensar que é uma suposição pessoal? Eu estava tentando realizar o pensamento crítico. Conceitualmente falando, a travessia de prop extra irá desacelerar a versão pura em comparação com a versão não pura, ela tem que podar algo para valer a pena. Mesmo raciocínio que # 5628 ou https://github.com/react-bootstrap/react-bootstrap/issues/633#issuecomment -234749417 ou https://github.com/reactstrap/reactstrap/pull/771#issuecomment -375765577

Com puro:
capture d ecran 2018-03-27 a 11 43 41

Sem puro:
capture d ecran 2018-03-27 a 11 44 15

A reprodução é a seguinte.

@oliviertassinari tem certeza de que codesandbox faz tudo certo para o seu teste? Porque meus resultados são muito diferentes:

Sem puro (mesmo sem acelerador é lento):
image

Puro (após alterar para verdadeiro e salvar, recebo um novo url para codesandbox):
image

Como não verifica as mudanças de contexto e também força os usuários a garantir que todos os componentes filhos também sejam "puros", não acredito que seja um bom ajuste para esta biblioteca. Esta biblioteca precisa ser o mais flexível possível e acredito que isso a tornará mais difícil de usar.

Eu entendo o ponto. Mas é uma troca muito interessante. Por um lado, mesmo o material-ui deve ser "o mais flexível possível", mas, por outro lado, o desempenho atual torna-o _inutilizável_ de todo.

Talvez você deva pensar em fornecer versões puras e não puras de componentes. Eu faço isso agora para meu aplicativo obter algum desempenho utilizável, mesmo no desktop.

@Bessonov O codesandbox não foi salvo corretamente. Desculpa. Estava faltando o seguinte diff. Eu atualizei o link:

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

Não entendo por que deveria produzir resultados diferentes? Eu obtenho um gráfico melhor, mas a versão não pura é significativamente mais lenta.

EDIT: ok, entendo. Tente descobrir o que está acontecendo ...

OK, eu entendo agora. Exatamente o mesmo "novo objeto em cada renderização" -coisa. Eu não percebi isso antes. Em alguns casos, pode ser melhorado com constantes através do plugin babel automaticamente.

Bem, então você já sabe disso! : D Mesmo não havendo muito benefício por si só (você mostrou ~ 7%), ainda é lucrativo em conclusão com componentes puros para evitar algumas falhas que você mencionou acima. Eu testei agora com Pure Wrapper + plugin babel + build de produção e é impressionante rápido no mesmo dispositivo móvel!

Como eu disse, acho melhor oferecer ambos - componentes não puros para flexibilidade e componentes puros (invólucros são suficientes para mantê-lo simples e fácil de manter) para desempenho. Mas, para mim, posso viver apenas com componentes puros, porque a melhoria geral do desempenho é muito maior do que as desvantagens do desempenho. Ou melhor: não posso usar material-ui sem componentes puros.

Ok, por enquanto estou esperando por mais informações sobre este tópico e criar seus próprios wrappers em meu aplicativo)

Obrigado por insights!

Eu nunca ouvi falar de transformar-reagir-constante-elementos sendo realmente usados ​​e úteis. É normal incluir uma micro-otimização aleatória, mas na prática você raramente escreve um código simples o suficiente para extrair muito dele. Embora eu suponha que não seria uma má otimização para todos os componentes do ícone no estilo SVG, como <Add /> .

Dê uma olhada neste exemplo (clique em Plugins ao lado, procure por "react-constant" e clique na caixa de seleção "transform-react-constant-elements") e você verá que quase nada foi otimizado:

  • O estático simples InputAdornment foi movido para o topo, sim.
  • Mas o botão enviar trivialmente simples não foi mais otimizado no momento em que colocamos um manipulador de eventos nele.
  • E enquanto o próprio InputAdornment agora é içado, o InputProps={{startAdornment: ...}} ainda está embutido e criando um novo objeto a cada renderização, o que torna o PureComponent impossível.
  • Da mesma forma, classes={{label: classes.runButtonLabel}} está impossibilitando o PureComponent de otimizar o botão Executar.

Eu pessoalmente gosto do PureComponent e tento usá-lo em qualquer lugar e otimizar as coisas o máximo que posso para que funcione. Mas não parece que o MUI foi feito de forma que o PureComponent funcionasse.

  • Os *Props props como InputProps são um padrão fundamental para o funcionamento do MUI. Não é apenas uma maneira avançada de modificar as partes internas do MUI quando você precisa, mas algo que é usado regularmente em casos de uso simples. Esse padrão geralmente torna qualquer componente folha que normalmente poderia ser otimizado no modo puro, não otimizado.
  • Da mesma forma, o padrão classes={{...}} também não funciona com PureComponent e é a maneira de adicionar qualquer estilo às coisas no MUI. (E dizer para usar classes={classes} é de forma alguma prático porque um consumidor da vida real provavelmente tem um nome de classe diferente do da classe interna do componente e classes provavelmente também inclui classes destinadas a estilizar outros elementos no mesmo componente de consumo)
  • Crianças são muito usadas, em vez de ter 1 componente, um padrão comum geralmente faz uso de 2-3 e apenas um deles pode ser totalmente otimizado, se algum deles puder.

Se quisermos otimizar alguma coisa, essas questões fundamentais precisam ser tratadas, caso contrário, simplesmente permitir que as pessoas habilitem um MUI de modo puro não otimizará muito. Posso pensar em duas possibilidades.

Nós apenas permitimos que o modo puro seja ativado, os consumidores têm que memorizar ou içar objetos manualmente para realmente obter otimizações

  1. Uma maneira que eu posso pensar para fazer isso seria algo menor que shallowMemoize usando o this e um key (para que você possa usá-lo em diferentes bits de dados) que memoriza os dados, desde que sejam rasos e iguais
  2. Outra coisa em que posso pensar é usar algo como selecionar novamente para criar seletores que memorizarão os dados.
  3. Outra maneira de fazer shallowMemoize em 1. seria passá-lo para render() com um decorador. Desta forma, poderíamos passar um novo para cada renderização e, em vez de precisar de key , podemos apenas verificar se algum dos objetos memoizados da última renderização deve ser reutilizado e, em seguida, descartar todos os valores antigos.

O problema, claro, é que isso torna os consumidores muito maiores e mais confusos e requer que a lógica seja içada manualmente até lugares onde está longe do código que a usa.

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

Em vez de tentar otimizar InputProps, classes, etc ... encorajamos as pessoas a fazer micro-componentes para todas as suas classes de uso

Se esta for a maneira recomendada de usar o MUI, podemos nem mesmo precisar de um modo puro. Como você pode ver, assim que você começa a fazer pequenos componentes auxiliares para seus casos de uso comuns, esses próprios componentes podem ser facilmente componentes puros. No exemplo WeightTextField agora nunca é renderizado novamente, contanto que value ainda seja o mesmo, ignorando completamente comStyles, qualquer trabalho de memoização necessário para InputProps ou a configuração de InputAdornment. E quando value mudar, temos que renderizar novamente o TextField de qualquer maneira para que o InputProps={{...}} não puro não importe.

Estou bem com esse caminho. Eu gosto de micro-componentes em teoria; embora eu odeie todas as sintaxes / padrões atualmente válidos para escrevê-los que eu possa imaginar. Não quero MyComponent = enhance(MyComponent) , quero decorá-los, mas você não pode decorar nenhuma das maneiras curtas de escrever um componente minúsculo. Também não gosto de transformar import {TextField} from 'material-ui'; em import WeightTextField from '../../../ui/WeightTextField ; `.

`` `js
deixe WeightTextField = ({unit, InputProps, ... props}) => (
{...adereços}
InputProps = {{
startAdornment:{unidade} ,
... InputProps
}}
onChange = {exemplo}
valor = {exemplo} />
);
WeightTextField = puro (WeightTextField);

RunButton = compose (
withStyles (theme => ({
rótulo: {
fontWeight: '800',
},
})),
puro
)(Botão);

const SubmitButton = pure (botão);

class FormPage extends Component {
estado = {exemplo: ''};

alterar = e => this.setState ({exemplo: e.target.value});
enviar = () => {
console.log ('Enviar:', this.state.example);
};

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

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

}
}
`` ``

Eu tenho um caso de uso em que preciso exibir 500–2000 caixas de seleção em uma página em uma grande lista. Usando as caixas de seleção do navegador nativo, o desempenho é bom, mas usando o componente <Checkbox> , o desempenho é muito ruim e escala linearmente com o número de caixas de seleção na página. Exemplo: https://codesandbox.io/s/5x596w5lwn

Estou usando o mui @ next - há alguma estratégia que eu possa empregar _agora_ para tornar isso viável?

@wilsonjackson
Primeiro, não faça o seguinte. Isso criará um novo manipulador a cada caixa de seleção a cada renderização, o que desotimizará quaisquer otimizações PureComponent que você tentar fazer.

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

Em segundo lugar, faça seu próprio pequeno componente para envolver a Checkbox e torne esse componente puro. Isso tem o benefício extra de que você pode adicionar quaisquer propriedades comuns a todas as suas caixas de seleção. E, como você tem o problema comum de precisar de um manipulador de eventos de mudança diferente para cada item, podemos usar um componente de classe e fazer isso no componente em vez de no contêiner de lista.

https://codesandbox.io/s/r7l64j6v5n

Se quisermos otimizar alguma coisa, essas questões fundamentais precisam ser tratadas, caso contrário, simplesmente permitir que as pessoas habilitem um MUI de modo puro não otimizará muito. Posso pensar em duas possibilidades.

@dantman Essas escolhas de API foram feitas para melhorar o DX tanto quanto possível, tentando ser rápido o suficiente.

Em vez de tentar otimizar InputProps, classes, etc ... encorajamos as pessoas a fazer micro-componentes para todas as suas classes de uso

Sim nós fazemos. O padrão de agrupamento é definitivamente a maneira encorajada de customizar a biblioteca. Ele pode ser estendido para aplicar otimizações de desempenho. É mais fácil no userland, onde a variabilidade do uso dos componentes é muito menor. Poderíamos até adicionar uma pergunta FAQ ou seção de guia sobre este ponto na documentação.

Sim nós fazemos. O padrão de agrupamento é definitivamente a maneira encorajada de customizar a biblioteca. Ele pode ser estendido para aplicar otimizações de desempenho. É mais fácil no userland, onde a variabilidade do uso dos componentes é muito menor. Poderíamos até adicionar uma pergunta FAQ ou seção de guia sobre este ponto na documentação.

OK. Nesse caso:

  • Vamos recomendar bibliotecas como recompose, que tornam mais fácil escrever pequenos componentes sem estado. Eu adoraria se alguém me dissesse que há outro padrão ou biblioteca que é semelhante a recompor, mas torna a construção de funções puras sem estado como os padrões de recomposição, mas é tão bom quanto usar a sintaxe do decorador
  • Como listamos sugestões sobre como usar as várias bibliotecas de preenchimento automático e css em bibliotecas js com MUI, provavelmente também queremos sugerir os vários padrões de organização de pasta existentes e bibliotecas de hacking de caminho (aqueles que permitem que você altere as importações de ../../../../ui/Foo em algo como something-local/Foo ) que pode ser usado para tornar o uso de sua própria série local de micro-componentes envolvendo o MUI tão bom quanto usar import {TextField} from 'material-ui'; e não parecer uma regressão na facilidade do desenvolvedor .

@dantman fantástico, obrigado.

Eu tenho várias vezes necessário para aplicar sCU devido ao withStyles (ou melhor, JSS) ser muito lento. Não conheço o código do JSS, mas parece que pode ser bastante otimizado. Eu geralmente uso componentes estilizados ou glamorosos e, portanto, acabo com JSS e um do outro no aplicativo e ambos superam o JSS.

Embora esses casos possam ser um pouco irritantes, eles são fáceis de contornar com sCU de nível de aplicativo ou atualizações de estado mais inteligentes. Ainda não vi um único componente do MUI ser lento o suficiente para ser problemático e também não consegui codificar realmente dentro do MUI para levar um tempo significativo.

Não quer dizer que não poderia ser mais rápido e com certeza seria melhor se menos supervisão fosse necessária, mas pelo menos para o que eu vejo, seria melhor gastar tempo otimizando JSS diretamente do que MUI nesse caso.

@Pajn Obrigado pelo feedback. Seria muito bom ver alguma situação em que o desempenho do withStyles seja problemático ou em que seja superado por componentes estilizados.

alguém verificou este repositório 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

esses componentes fazem uma nova renderização desnecessária apenas com um simples clique, por que não tentar o ciclo de vida de atualização do componente deve?

@ nimaa77

alguém verificou este repositório https://github.com/reactopt/reactopt ?
Não, eu uso os recursos de ferramentas de desenvolvimento de por-que-você-atualizou e cromo / react.

esses componentes fazem uma nova renderização desnecessária apenas com um simples clique, por que não tentar o ciclo de vida de atualização do componente deve?

Veja a discussão acima. Isso não se aplica a todos os casos de uso. Para mim, parece que o material-ui deve oferecer ambos - versões puras e não puras de componentes. Vejo uma grande melhoria de desempenho com componentes puros.

@dantman

Dê uma olhada neste exemplo (clique em Plugins ao lado, procure por "react-constant" e clique na caixa de seleção em "transform-react-constant-elements") e você verá que quase nada foi otimizado:

Esta é apenas uma maneira de resolver esse problema. Existem também outras opções, plug-ins e outras otimizações feitas à mão. Não me interpretem mal, mas não estou interessado em discussões teóricas sobre quais otimizações são boas ou ruins. Estou interessado na otimização prática que é TRABALHOS. Pelo menos na minha configuração. Tudo o que escrevi acima está FUNCIONANDO para mim e torna meu aplicativo pelo menos utilizável em dispositivos móveis de médio porte. Embora eu seja forçado a fazer mais otimizações, como reordenar árvores de componentes, o desempenho alcançado não seria possível sem componentes puros e outras otimizações automáticas. E sim, eu crio o perfil e otimizo muito para fazer isso.

@Bessonov talvez possamos usar um prop para fazer com que o método shouldComponentUpdate faça uma comparação superficial (https://reactjs.org/docs/shallow-compare.html) OU sempre retornar falso,
isso não aumentará o tamanho do pacote com duas versões de componentes (puro e normal)

@lucasljj Não espero um aumento significativo no tamanho do pacote se isso for feito como um invólucro para o componente com estado, como mencionado acima. Veja também os perfis acima: apenas componentes puros são mais rápidos do que return false; em sCU.

O problema com componentes puros ou componentes que implementam sCU é se você usar componentes não puros dentro dele ou children . Veja as declarações acima. Outra questão a ser abordada é a troca de tema. Não testado, mas acho que isso pode ser superado pelo menos com a API de contexto.

@bossonov Pure Components são considerados o padrão de reação que não deveria ser porque eles chegaram atrasados ​​à festa. Mas eu concordo que a maioria das pessoas criará redux envoltórios de estilo de formulário, atenuando a falta dele na subárvore

o problema que você faz referência a respeito de componentes puros e filhos, só ocorre se os adereços de nível superior não se propagam para os filhos. Cada prop ou mudança de estado irá disparar um rerender através da árvore filha até o nível que o prop não se propaga.
Porque a ideia de componentes puros é que ele seja refeito a cada mudança de suporte ou estado. Eu não conheço os componentes internos intimamente, mas estava pensando em você não poderia usar uma propriedade de alteração por componente que você passa para todas as crianças. Você pode até usar a nova API de contexto para esse propósito, de modo a não ter que passar um pilar por toda a árvore.

@oliviertassinari

O desempenho será um foco após o lançamento da v1.

Ótimo, a v1 foi lançada :) Há alguma idéia de quando o desempenho deve ser abordado? Percebi que esse problema não faz parte do marco pós v1.

@Bessonov Acho que seria ótimo dedicar um tempo para atualizar o ROADMAP. Em relação ao desempenho. Tenho duas coisas em mente acionáveis, mas espero descobrir mais:

  1. Não podemos melhorar algo que não podemos ver. Precisamos de uma referência. Eu vi duas bibliotecas interessantes até agora:
  2. Renderizar o lado do servidor CSS requer cerca de 30% do que o React precisa para renderizar. Porque não oferecemos suporte para style = f(props) (bem, ainda não oferecemos suporte: # 7633). Podemos implementar uma capacidade de cache muito eficiente, cortando o custo perto de 0% para solicitações repetidas. Posso trabalhar nisso para o escritório se o desempenho do SSR prejudicar nossas métricas de negócios.
    Mas não são as únicas opções, também podemos pensar nos plug-ins Babel para aplicar os presets JSS caros no momento da compilação em vez de no tempo de execução (para equilibrar com as implicações do tamanho do pacote) cc @kof.

Obrigado pelos urls.

Concordo totalmente com 1 e link # 4305 acima.

  1. SSR pode ajudar com o carregamento da primeira página, mas não ajuda com renderização lenta ou cordova. Embora pessoalmente eu possa ignorar o carregamento inicial no desktop ou mesmo no celular, a renderização lenta ainda afeta os dispositivos móveis após o primeiro carregamento.

Otimizações de tempo de compilação seriam ótimas e, da minha perspectiva, deveriam ser preferidas, porque podem melhorar o carregamento inicial e o rerender.

Outra coisa é um tipo de documentação ou projeto de exemplo de como usar mui (e babel, etc.) para obter um melhor desempenho.

Não sei se o jss-cache pode ser usado.

O pipeline de pré-processamento ISTF + será capaz de reduzir alguns ms.

No sábado, 19 de maio de 2018, 19:06 Anton Bessonov [email protected] escreveu:

Obrigado pelos urls.

Concordo totalmente com 1 e vinculado # 4305
https://github.com/mui-org/material-ui/issues/4305 acima.

  1. SSR pode ajudar com o carregamento da primeira página, mas não ajuda com lentidão
    renegociação ou cordova. Embora pessoalmente eu possa ignorar o carregamento inicial no
    desktop ou mesmo móvel, a renderização lenta ainda afeta os dispositivos móveis após
    primeiro carregamento.

Otimizações de tempo de compilação seriam ótimas e, da minha perspectiva,
ser preferido, porque pode melhorar o carregamento inicial e renderizar novamente.

Outra coisa é um tipo de documentação ou projeto de exemplo de como usar mui
(e babel etc.) para obter um melhor desempenho.

Não sei se jss-cache http://cssinjs.org/jss-cache?v=v3.0.0 pode ser
usado.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/mui-org/material-ui/issues/10778#issuecomment-390418709 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AADOWGrCxNGqrT4MijiX8r9Ad32z6RsJks5t0FEtgaJpZM4S4woq
.

Oh react-jss (que é usado por material-ui) parece ser bem lento

@janhoeck , não é, e você não poderá provar o contrário, tenho certeza.

@kof Compreendo muito lento. Na verdade, o desempenho geral do material-ui não é brilhante.

4x-6x em comparação com afrodite é muito lento

@Bessonov A metodologia de benchmark
https://twitter.com/necolas/status/954024318308007937?lang=fr

Os resultados em meu navegador:
⚠️ com a incerteza

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

desempenho geral do material-ui não é brilhante

Podemos muito bem ser limitados pelo próprio React e pelo custo dos componentes.

@Bessonov tudo o que você usou para verificar o desempenho, quando se trata de afrodite, não é verdade, porque usa o mais rápido possível e atrasa a renderização, portanto, a maioria dos benchmarks mede o tempo da CPU, mas não o desempenho da renderização final. Dito isso, o banco postado por @oliviertassinari é muito realista.

Com relação ao desempenho do MUI, você pode ficar mais rápido quando não usa nenhuma abstração, mas esse não é o ponto. O próximo MUI é muito rápido. É tão rápido que você nunca deve se preocupar com seu desempenho, a menos que VOCÊ tenha feito algo totalmente errado ou usado indevidamente a biblioteca.

@kof não, o MUI não é rápido por padrão. Somos forçados a implementar soluções alternativas para obter um desempenho aceitável. Com certeza: você não viu que se trata de dispositivos móveis e não de macs de última geração?

nunca se preocupe com seu desempenho, a menos que VOCÊ tenha feito algo totalmente errado ou usado indevidamente a biblioteca

Bem, acima estão alguns exemplos de codesanbox. Se apenas usarmos os componentes MUI em um contêiner e salvarmos os valores de entrada no estado do contêiner, como mostrado nas demonstrações de componentes, ele se tornará inutilizável muito rápido. Envolva em PureComponent, use componentes não controlados, reordenamento de árvore, elementos constantes, memoize e assim por diante, ajudando-nos a manter um desempenho aceitável. Sinta-se à vontade para mostrar como usar o MUI da maneira certa, para obter melhores resultados, por exemplo em (atm não usamos mais gaveta):
https://codesandbox.io/s/r1ov818nwm

@oliviertassinari obrigado pelo link. Aqui estão os resultados para o Chrome 66 no Nexus 4. Como você pode ver, os resultados são 10 vezes mais pobres. Acho que na faixa de pedestres 21 / cromo 51 pode ser um pouco mais lento:

screenshot_2018-06-12-18-20-19

Podemos muito bem ser limitados pelo próprio React e pelo custo dos componentes.

IMHO não necessariamente, como mencionado por outros também. Cada renderização evitada é uma grande vitória para desempenho e bateria. Não se esqueça, o desempenho é o terceiro ponto em seu roteiro: +1:

Veja dessa forma: se o que você faz com cssinjs é muito lento em um dispositivo móvel, você não deve usá-lo para esta reação e para a maioria das outras coisas também. Se cssinjs atrasar você, o componente de reação o atrasará muito mais. Você precisa repensar o nível de abstração ou aplicar otimizações.

@oliviertassinari é esse evtl. possível que mui wrapper rerenders estilos jss na atualização? Pode haver algum vazamento que potencialmente faz um trabalho desnecessário.

@kof

Veja assim [...]

Eu vejo seu ponto. Mas os componentes de reagir e reagir (puros) não são gargalos por si próprios e têm um desempenho muito bom. Por exemplo, MUI não usa PureComponent (com prós e contras descritos acima), mas eles são nossa vida mais segura. @oliviertassinari mencionou que há uma chance de obter melhor desempenho usando caches ou pré-compilador.

Não me interpretem mal, vocês são caras incríveis e estou muito feliz com cada lançamento de MUI: clap: Mas você precisa considerar o desempenho também, porque nem todo usuário visita sites através de um desktop de alto desempenho .

se react não é um gargalo de desempenho e você tem certeza disso, o JSS também não será. A única coisa que eu poderia imaginar que precisa ser validada se houver algum trabalho desnecessário em andamento e o css for regenerado na atualização.

Componente puro é algo que VOCÊ deve usar em seu código de nível de aplicativo, se precisar de uma otimização. O MUI não precisa fazer isso, é uma biblioteca genérica e não deve fazer suposições. PureComponent nem sempre é bom.

JSS considerou o desempenho desde o primeiro dia e passei horas intermináveis ​​em micro otimizações.

Isto é ridículo. Não posso impedir que componentes materiais da interface do usuário sejam renderizados novamente em cada interação. Não importa o que eu faça, sempre algo tão simples como isto:

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

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

Os gatilhos reproduzem novamente os elementos de tipografia subjacentes. Como isso é possível ?

Ei, isso só acontece com a lista e o item da lista. Porque ? Existe algo que eu possa fazer?

@ danielo515 Difícil dizer sem o exemplo completo. Os componentes List também estão usando contexto, portanto, se você alterar esse valor, ele também acionará os renderizadores.

Eu entendo que @ eps1lon. No entanto, você poderia explicar como uma mudança de contexto pode acontecer? Não estou passando nada para o componente de lista, apenas renderizando uma perda de crianças dentro dele.

@ danielo515 Basicamente toda vez que List renderiza novamente. A nova API de contexto permitiu algumas estratégias de otimização que poderíamos explorar memorizando o valor do contexto.

Atualmente, a API de contexto reage dispara um rerender em cada consumidor se o valor alterar WRT para igualdade estrita. Como criamos um novo objeto em cada chamada de renderização para List cada consumidor também renderá novamente.

Portanto, por enquanto, um aumento de desempenho fácil seria embrulhar List para que não renderize novamente com tanta frequência. Eu recomendaria fazer benchmarks primeiro ou sair da renderização antes. Chamadas de renderização repetidas Typography não deveriam ser tão ruins.

O empacotamento de componentes com React.memo funciona bem para qualquer coisa sem filhos. Eu tenho um formulário com 3 ExpansionPanel e cerca de 14 FormControl e está demorando na área de trabalho.

Sem uma solução para esses problemas de desempenho, não poderei continuar usando o material-ui: s

@prevostc é difícil dizer o que está errado sem um exemplo.

@prevostc sem ver uma reprodução do codesandbox, nem nós nem você podemos saber se isso está relacionado a este problema

@prevostc Crie seus próprios componentes que não requerem filhos, os quais você pode memorizar.

isto é, crie seu próprio componente MyExpansionPanel puro / memorizado que recebe dados / objetos de evento, mas não filhos, e é responsável por renderizar um único painel de expansão. Em seguida, use esse <MyExpansionPanel ... /> para renderizar cada um de seus painéis de expansão. Em seguida, a renderização será limitada a um único painel de expansão (ou dois ao fazer a transição entre dois).

@oliviertassinari @kof @dantman
Aqui está uma reprodução codesandbox do meu problema de desempenho: https://codesandbox.io/s/yvv2y2zxxx

É um formulário com cerca de 20 campos (não incomum); você experimentará algum atraso na entrada do usuário. Em dispositivos mais lentos, este formulário simplesmente não pode ser usado.

O problema de desempenho vem da grande re-renderização na entrada do usuário, mas envolver os componentes MUI em um componente puro (React.memo) não faz nada, pois tudo aqui tem filhos e filhos, isso forçará a nova renderização do AFAIK (fonte: https: // reactjs.org/docs/react-api.html#reactpurecomponent)

Abaixo estão algumas capturas de tela do meu benchmark manual, um sem qualquer otimização, um com tudo memorizado e um usando um componente de entrada personalizado com um estado local para evitar definir o estado em todo o formulário com muita frequência.
Em cada configuração, uma reconciliação de entrada do usuário leva cerca de 60 ms (muito acima dos 16 ms que preciso renderizar a 60 fps.

Observe que eu ficaria feliz em saber que fiz algo errado porque amo a MUI e ficaria triste se não houvesse uma solução fácil <3

@dantman Como você pode escrever um ExpansionPanel que não tenha nenhum React.ReactNode como entrada (filhos ou adereços)? Se você quer dizer que devo escrever um componente específico para cada painel em meu aplicativo, isso não é possível, infelizmente, tenho muitos deles.

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 Como você pode escrever um ExpansionPanel que não tenha nenhum React.ReactNode como entrada (filhos ou adereços)? Se você quer dizer que devo escrever um componente específico para cada painel em meu aplicativo, isso não é possível, infelizmente, tenho muitos deles.

Sim, em vez de fazer um único componente maciço com uma árvore profundamente aninhada de painéis, separe as peças do painel de expansão em componentes. É impossível otimizar árvores enormes como essa.

Não é impossível. Os componentes do React são muito leves. Copie e cole um bloco de código de um componente enorme em uma função e você está quase pronto, depois disso, você só precisa conectar os adereços. E contanto que você React.memo essa função e evite passar coisas que quebram a otimização pura dos adereços, então as coisas são otimizadas com bastante facilidade.

Lembre-se, se você estiver criando um pequeno componente para quebrar um pedaço de um componente enorme. Não precisa ser algo complexo com seu próprio arquivo e sua própria validação de propriedade. Pode ser um componente funcional simples no mesmo arquivo que o componente em que é usado sem propTypes. ou seja, você pode criar pequenos componentes que estão disponíveis apenas para o componente no mesmo arquivo que eles.

Sim, a IU do material é um pouco mais lenta do que os elementos dom de baixo nível. Mas mesmo se um MUI Input fosse 10x mais lento que um input bruto e as coisas ficassem muito lentas depois de 100. Então você ainda tem um problema mesmo sem MUI porque mesmo se ele fosse 10x mais rápido que input bruto

@prevostc Aqui está sua demonstração otimizada ao dividir o painel de expansão em um minúsculo componente do mesmo arquivo. Como você pode ver, apenas um painel de expansão é renderizado novamente quando uma entrada é atualizada. O tempo não é perdido renderizando novamente os painéis de expansão não relacionados. Se eu quisesse, poderia até fazer algo semelhante a entradas que compartilham um padrão semelhante, resultando em entradas não relacionadas também não renderizadas.

Observarei que este não é apenas um bom padrão de otimização, é um bom padrão de codificação. No código real, onde você não está criando uma matriz de entradas, mas explicitamente as escrevendo com nomes, rótulos e propósitos definidos. Encontrar limites e repetir padrões não apenas otimiza as coisas, mas também reduz o clichê, tornando o código mais DRY e legível. ou seja, em vez de InputWrapper eu provavelmente quebraria essa combinação FormControl + InputLabel + FormHelperText + Input em um pequeno componente SimpleTextInput local. O que não apenas o otimizaria (resultando em entradas não relacionadas que não seriam renderizadas novamente), mas também significaria que o código não precisa repetir o clichê extra.

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

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

Lendo isso, cheguei à conclusão de que, para otimizar o mui, você precisa criar componentes específicos menores. Isso é algo que já percebi e tentei com sucesso. No entanto, também entendi que não há como otimizar os componentes da lista porque a API de contexto altera todos os adereços de entrada.

Cumprimentos

Ok, aqui está um teste de estresse atualizado https://codesandbox.io/s/wz7yy1kvqk

Eu concordo com @dantman neste ponto genérico https://github.com/mui-org/material-ui/issues/10778#issuecomment -449153635 MAS eu não esperava esse problema de desempenho com esta baixa quantidade de componentes, mas tenha paciência com quando encontrei a origem do meu problema de desempenho.

O JSS é lento? (alerta de spoiler: não)

Em referência a alguns dos comentários anteriores neste tópico, adicionei uma caixa de seleção no teste de estresse para remover todas as chamadas para withStyles e cheguei à conclusão de que JSS é rápido e não é a origem do problema de desempenho (como @kof apontou em https://github.com/mui-org/material-ui/issues/10778#issuecomment-396609276).

screenshot 2018-12-22 at 15 17 26

O conserto

Para meu caso de uso específico, consegui identificar o problema: cada entrada de formulário foi renderizada novamente na atualização do formulário, embora apenas uma entrada tenha realmente mudado.
Na captura de tela abaixo, envolvi FormControl e Input em um componente memoized, evitando renderizar se o valor não mudasse. @dantman realmente sugeriu que eu criasse um componente específico para cada ExpansionPanel mas esta é uma solução menos genérica. Como uma observação lateral, cada painel ainda é renderizado novamente e o desempenho está longe de ser o ideal, mas é suficiente por enquanto.

screenshot 2018-12-22 at 15 18 22

Então? Qual é o próximo?

Acho que não há como evitar esse tipo de problema com uma mudança no código material-ui sem uma mudança massiva na API atual, dependendo fortemente da composição de React.ReactNode .
Mas, como @dantman mencionou em https://github.com/mui-org/material-ui/issues/10778#issuecomment -449153635, o MUI é um pouco mais lento do que o que esperava dele. Não abordar isso é um erro IMHO.

Cientes desse problema, podemos precisar criar uma página de documento relacionada aos problemas de desempenho e como resolvê-los. Mesmo que sua página redirecione principalmente para o documento oficial (https://reactjs.org/docs/optimizing-performance.html) e liste os componentes que podem causar problemas de desempenho, é um começo.
Seria melhor consolar. Avisar o usuário quando esse problema ocorrer, mas não consigo descobrir uma maneira de detectar problemas no nível da interface do usuário do material.

@prevostc esta mensagem fez o meu dia, este é o tipo de comunidade que amo. Você tem ideias sobre o que o mui poderia mudar para melhorar o desempenho e evitar a necessidade de otimizações de terreno do usuário? Uma mudança de API pode ser factível.

Eu não: s

Eu não conheço o MUI interno o suficiente para ter alguma ideia de como melhorar seu desempenho bruto (sem mudança de API) agora. Estou trabalhando em algumas idéias, mas nada conclusivo até o momento: tenho um aplicativo em que um grupo de rádio é re-renderizado quando seu pai direto não é, não posso reproduzi-lo localmente ainda.

Qualquer alteração de API seria considerar a remoção de quaisquer React.ReactNode props da API (filhos e outros adereços como ícone, etc), mas não consegui encontrar uma boa maneira de manter a mesma configurabilidade.
Aqui está o que eu tentei: https://codesandbox.io/s/jpw36jw65 (aviso: não concluído).

Também é algo a se notar que o MUI é especialmente lento no modo de desenvolvimento devido a reagir sendo especialmente lento no modo de desenvolvimento. Não sei se tem como melhorar isso.

Há algum progresso na adição de recursos (como o sugerido por @Bessonov ) para otimizar os problemas de desempenho que o Material-UI enfrenta atualmente?
Quando começamos a usar esta ótima biblioteca para nosso projeto, eu não sabia que esses problemas de desempenho podem acontecer quando o projeto fica cada vez maior; além disso, não vi nenhuma seção nos documentos do Material-UI informando sobre os casos que podem fazer com que o Material-UI se torne lento e prejudique a UX.
Muitos problemas de desempenho - direta ou indiretamente relacionados ao Material-UI - são relatados nesta edição. Acho que será uma boa ideia listá-los em outra edição para que todos possam acompanhar o progresso. Se você acha que está tudo bem, posso abrir um novo problema.

@ mkermani144 Ainda não vimos um relatório de desempenho diretamente relacionado a algo que o Material-UI está fazendo de errado. Até agora, esta edição tem sido usada como um fórum de ajuda para pessoas com dificuldades. Você confirma que nada acionável foi relatado? Vou dizer o óbvio: a abstração do React tem um custo . Cada componente envolvendo um host adicionará peso à sua árvore de renderização, tornando-a mais lenta. Embora você possa renderizar mais de 100 itens de lista com o host nativo, começa a ser um problema quando você os envolve com um componente de classe. Não é específico para Material-UI.

Mesa

Modo de desenvolvimento

Vejamos o exemplo de uma mesa. É um componente que as pessoas consideram lento. Documentamos a virtualização , isso ajuda muito.

No caso de teste a seguir, renderizamos 100 itens no modo dev . Podemos considerar os seguintes casos:

  1. Tabela bruta : https://codesandbox.io/s/v066y5q7z3 : 63ms na renderização
  2. Tabela Material-UI Master: https://codesandbox.io/s/m4kwmvj9ly : 250ms na renderização
  3. Table Material-UI Next: https://codesandbox.io/s/2o35yny1jn : 262ms na renderização

Portanto, a sobrecarga de usar Material-UI no modo dev sobre o elemento host é em torno de x4 (a diferença é menor na produção!), Simplesmente porque criamos componentes intermediários. É por isso que a virtualização começa a ser importante após renderizar uma lista de aproximadamente 100 itens de tabela. Agora, podemos mergulhar um pouco em Por que e o que podemos fazer a respeito?

  1. Tabela Raw + componente de função. Por que um componente funcional? Queremos abstrair os nomes das classes usados. Não queremos que os usuários do componente os repitam.
    https://codesandbox.io/s/1zl75mwlpj : 105ms na renderização
  2. Tabela Raw + componente de função + forwardRef. Por que forwardRef? Queremos que o componente seja "transparente", podendo acessar o elemento host com um ref.
    https://codesandbox.io/s/32o2y0o9op : 120ms na renderização
  3. Tabela Raw + função componente + forwardRef + withStyles. Por que comStyles? Porque queremos estilizar nosso componente:
    https://codesandbox.io/s/j2n6pv768y : 200ms na renderização
  4. Tabela Raw + componente de função + forwardRef + makeStyles. makeStyles pode ser mais rápido do que withStyles, vamos tentar:
    https://codesandbox.io/s/yw52n07l3z : 130ms na renderização.

Portanto, temos uma vantagem disponível: migre todos os componentes de withStyles para makeStyles. Podemos ganhar cerca de + 30% de desempenho (262 / (262 - 70)) no modo dev .

Modo de produção

Eu executei os mesmos casos de teste no modo de produção:

  • n ° 1 30ms no hidrato
  • n ° 3 106ms no hidrato
  • n ° 4 40ms no hidrato
  • n ° 5 41ms no hidrato
  • n ° 6 80ms no hidrato
  • n ° 7 57ms no hidrato

Portanto, a migração de withStyles para makeStyles ainda é um aumento de + 30% em teoria no modo de produção .

Se você acha que está tudo bem, posso abrir um novo problema.

@ mkermani144 Se você tiver um caso específico em que o Material-UI está fazendo isso errado, com certeza.

Eu li sobre todos os comentários abaixo deste problema. Meu problema não se encaixa em nenhum dos outros mencionados anteriormente.

Eu tenho um componente List contendo alguns ListItem s, tendo um deles selecionado e destacado. Quando clico em outro ListItem para selecioná-lo e destacá-lo, a lista inteira (contendo seus filhos) é renderizada novamente.

Sei que esse problema pode parecer exatamente igual a um comentário anterior , mas não é; pelo menos eu acho que sim.

Vejamos os resultados do criador de perfil React:

image
Como você pode ver, tenho um componente MyList no nível superior da imagem. Este componente é apenas um invólucro em torno do MUI List e apenas o torna puro, ou seja:

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

Eu adicionei este invólucro porque @ eps1lon em um de seus comentários mencionou que a re-renderização List faz com que o contexto seja atualizado, e essa atualização de contexto faz com que todos os consumidores (incluindo ListItem s) também renderizem novamente .

Também tentei tornar todos os meus ListItem s puros e criei o perfil do aplicativo novamente. Os resultados foram os mesmos, exceto que meu componente personalizado (ou seja, MyListItem ) não foi renderizado novamente, mas todos os filhos abaixo dele __did__.

Eu sei que o problema ocorre porque o contexto que o MUI usa para recriar o estilo _ de alguma forma_. Mas não sei por que essa re-renderização aconteceu e como posso evitá-la.

Ou estou fazendo algo errado?

Observação: eu uso a nova solução de estilo (alfa) MUI, ou seja, @material-ui/styles . Não sei se isso importa.

@ mkermani144 Remova Material-UI com elementos nativos, observe que a re-renderização ainda está presente. A lógica pura não vai ajudar assim. React.createElement cria novas referências em cada renderização, ele invalida seu PureComponent.

Sim, eu sei que os elementos são objetos e os objetos não são estritamente iguais em Javascript, então sCU falha.

Mas não entendo o que você quer dizer quando afirma que React.createElement é chamado novamente. Qual chamada para createElement ? Se você quer dizer as chamadas dentro de List , ele só chama createElement para seus filhos ( ListItem s) apenas se for renderizado novamente. Se não for renderizado novamente, nenhum createElement será chamado e não deverá acontecer nenhuma nova renderização. O problema é que o próprio List é renderizado novamente.

@ mkermani144 Se você puder criar um exemplo de reprodução mínima, podemos dar uma olhada nele.

Seu MyList (e, portanto, List ) é renderizado por causa do componente que renderiza MyList (vamos chamá-lo de MyComponent ) é renderizado novamente. PureComponent em MyList não ajuda, pois MyComponent foi processado novamente e criou novos filhos para MyList modo que a verificação de MyList s falha.

Seu MyComponent provavelmente será renderizado novamente porque é onde você armazena o estado do item selecionado.

Acho que a implementação de List de MUIs deve mudar para não recriar o valor de contexto de List de cada renderização
aqui: https://github.com/mui-org/material-ui/blob/fb4889f42613b05220c49f8fc361451066989328/packages/material-ui/src/List/List.js#L57

Então, em vez disso, faça com que List se pareça com isto:

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

Isso simplificaria a criação de sCU versões dos ListItems

Sua MyList (e, portanto, List) é renderizada novamente porque o componente que renderiza MyList (vamos chamá-lo de MyComponent) é renderizado novamente. PureComponent em MyList não ajuda, pois MyComponent foi renderizado novamente e criou novos filhos para MyList, de forma que a verificação de MyLists falhou.

@Pajn Não, olhe para os resultados do meu MyList não renderizou novamente (é cinza), mas List sim (é azul). Eu não insisto em PureComponent por MyList . Mesmo que eu implemente sCU para MyList para que ele não renderize novamente, List __faz uma nova renderização__.

@oliviertassinari
Eu criei um exemplo de reprodução mínima:

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;

Resultados do profiler de reação (após clicar no 4º item da lista):

image

Como você pode ver, funciona conforme o esperado, ou seja, não há nenhuma nova renderização extra (exceto para ButtonBase componentes dentro de ListItem s). O problema é que essa reprodução é _muito mínima_; há muitas coisas que eu pulo nele.

Eu sei que você não pode me dizer o que há de errado com meu código, causando novas renderizações extras. Mas eu lhe faço uma pergunta: O que pode causar uma nova renderização nos componentes WithStylesInner que envolvem os componentes MUI?

@ mkermani144 O que você acha dessa correção?

--- 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>

Você deseja enviar uma solicitação de pull? :) Estamos usando a mesma estratégia com o componente Tabela. Funciona muito bem. Obrigado por relatar o problema!

@oliviertassinari Certo. Isso é exatamente o que @Pajn sugeriu anteriormente. Enviei um PR .

14934 é mesclado; o componente List , entretanto, pode se tornar um gargalo de desempenho se for grande o suficiente, não importa o quanto seja otimizado. Não deveríamos fornecer um exemplo mostrando o uso de react-window ou react-virtualized com List componente, como aquele que temos em Table docs?

Não deveríamos fornecer um exemplo mostrando o uso de react-window ou react-virtualized com o componente List, como aquele que temos nos documentos da tabela?

Isso seria ótimo: +1:

Fwiw eu construí um aplicativo de bate-papo e o aplicativo precisa renderizar uma grande lista de contatos. Eu passei pelo mesmo problema
@ mkermani144 tinha.

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

@ henrylearn2rock Você já pensou em usar a virtualização? Adicionamos uma demonstração para a lista: https://next.material-ui.com/demos/lists/#virtualized -list.

Isso também me fez tropeçar. Acho que a maioria das pessoas (inclusive eu) presumiu que tudo em um componente puro estava protegido contra um rerender, o que evidentemente não é o caso desta biblioteca. Vou tentar a virtualização como você sugeriu mais recentemente. Obrigado!

Acho que a maioria das pessoas (inclusive eu) presumiu que tudo em um componente puro estava protegido contra um rerender, o que evidentemente não é o caso desta biblioteca.

Não é assim que React.PureComponent ou React.memo funcionam. Afeta apenas o próprio componente. As crianças ainda podem ter que renderizar novamente se o contexto mudar.

@pytyl Você pode compartilhar o código em que usou um PureComponent e esperava que ele evitasse qualquer nova renderização em sua subárvore?

@ eps1lon a documentação a seguir faz parecer que o retorno de false de shouldComponentUpdate pula automaticamente a re-renderização em componentes filhos.
https://reactjs.org/docs/optimizing-performance.html#shouldcomponentupdate -in-action

Como o shouldComponentUpdate retornou falso para a subárvore com raiz em C2, o React não tentou renderizar o C2 e, portanto, nem mesmo precisou invocar o shouldComponentUpdate no C4 e C5.

Talvez eu esteja enganado sobre isso? A seguir está um instantâneo do meu criador de perfil. Apenas para fins de teste, eu retorno explicitamente false para shouldComponentUpdate em meu componente Menu:

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

Isso fez com que todos os meus componentes filhos (Categories, Category, CategoryItems, CategoryItem) não fossem renderizados novamente. Muitas coisas relacionadas ao MUI parecem estar sendo renderizadas novamente na parte inferior, o que parece estar causando muito atraso. Coisas como withStyles, Typography, ButtonBase. O React ainda é um pouco novo, então desculpe minha ignorância. Abaixo está meu código para o componente Menu (onde estou retornando false para shouldComponentUpdate):

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

Eu precisaria de um codesandbox completo para entender o problema.

@ eps1lon tentarei produzir um para amanhã. Obrigada.

@ eps1lon aqui está o código:
https://codesandbox.io/s/348kwwymj5

Pequena descrição
É um aplicativo de menu básico para restaurantes (que geralmente têm mais de 100 itens de menu). Quando o usuário clica em um item de menu, ele abre uma caixa de diálogo "adicionar ao pedido". Vou anexar algumas situações em que o criador de perfil está mostrando um desempenho ruim (essas estatísticas não estão em uma compilação de produção).

Sistema
MacBook Pro (Retina, 13 polegadas, início de 2015)
Intel Core i7 de 3,1 GHz
Firefox 66.0.3

Caso 1 (o usuário clica em um item de menu)
Duração da renderização: 218ms

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

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

Caso 2 (o usuário clica no botão adicionar ao pedido na caixa de diálogo)
Duração da renderização: 356ms

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

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

Tenho certeza de que estou cometendo algum erro de novato aqui, então qualquer orientação é muito apreciada.

Como WithStyles (ButtonBase) é renderizado novamente, presumo que WithStyles usa um contexto que é recriado mesmo que não seja necessário.

Consegui encontrar https://github.com/mui-org/material-ui/blob/048c9ced0258f38aa38d95d9f1cfa4c7b993a6a5/packages/material-ui-styles/src/StylesProvider/StylesProvider.js#L38, mas não consigo encontrar um lugar onde StylesProvider é usado no código real (a pesquisa de GitHubs não é muito boa), mas pode ser o motivo.

@ Eps1lon sabe se essa pode ser a causa? Se for, um useMemo no objeto de contexto provavelmente consertaria isso. Embora eu não saiba se localOptions são estáveis ​​ou se useMemo precisa ser propagado ainda mais.

Pode ser. Mas você deve primeiro investigar por que seu componente usando rerenders StylesProvider. Isso é algo no topo da sua árvore ou deve estar em algum limite da IU. Em ambos os casos, eles raramente devem ser processados ​​novamente. Lembre-se de que o contexto de reação não é otimizado para atualizações frequentes de qualquer maneira.

Não devemos otimizar prematuramente essas coisas apenas porque algum objeto é recriado durante a renderização. Memoização não é uma bala de prata. Portanto, sem um exemplo concreto, não posso fazer muito. Sim, as coisas são renderizadas novamente. Às vezes, com mais frequência do que precisam. Mas uma re-renderização desperdiçada não significa que seja a causa de um gargalo de desempenho.

@pytyl Eu olhei em seu codesandbox, há um problema com a arquitetura de renderização. Você renderiza tudo novamente quando o item de menu é clicado. Seu GlobalContext pula a lógica pura.

@ eps1lon Acho que devemos encerrar este problema. Seria melhor se concentrar em questões especificamente identificadas.

TL; DR: crie fatias de contexto, memorize valores de contexto, sem problemas com material-ui especificamente: https://codesandbox.io/s/8lx6vk2978

Fiz algumas pesquisas e o problema é que você tem um grande contexto global que é recriado durante a renderização. Você renderiza novamente seu aplicativo quando clica em qual ponto o contexto global é recriado. Seu CategoryItem está ouvindo, o que aparece 100 vezes em seu aplicativo. Desde que você tem 100 MenuItems material-ui você encontra a morte clássica por mil cortes.

Ironicamente, parte da solução é memorizar um valor de contexto, mas a parte importante é identificar fatias de contexto separadas. Parece que um estado e contexto de despacho são apropriados. Isso é recomendado ao usar useContext com useReducer e parece se encaixar aqui.

Isso pode criar uma árvore bem grande e renderizar adereços quanto mais contextos você tiver. Recomendo que você dê uma olhada em useContext . Vai ajudar muito se você começar a enfrentar esses problemas.

@oliviertassinari É uma boa questão coletar armadilhas comuns com soluções. Podemos decidir se queremos criar questões separadas a partir dele.

@oliviertassinari @ eps1lon obrigado pela revisão! O desempenho parece ótimo.

Eu só tive um problema com o desempenho de renderização lento. Eu resolvi isso substituindo todas as instâncias do componente <Box> por <div> s. Eu depurei usando o flamegraph react devtools, e passei de cerca de 420ms para 20ms.

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

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

@mankittens Você pode manter o componente Box, usando componentes estilizados como mecanismo de estilo. O desempenho seria muito melhor. Deve ficar melhor com JSS em um futuro próximo https://github.com/mui-org/material-ui/pull/16858.

Estou encerrando este problema. Precisamos de um relatório de desempenho dedicado para cada área potencial de melhoria, não um segmento guarda-chuva.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

mb-copart picture mb-copart  ·  3Comentários

finaiized picture finaiized  ·  3Comentários

revskill10 picture revskill10  ·  3Comentários

zabojad picture zabojad  ·  3Comentários

ryanflorence picture ryanflorence  ·  3Comentários