Por que escrever sua grade de dados de reação em 2019

Olá Habr! Estou envolvido no desenvolvimento de um sistema ECM. E em uma curta série de artigos, quero compartilhar nossa experiência e a história do desenvolvimento da minha React Data Grid (daqui em diante simplesmente uma grade), a saber:


  • por que abandonamos os componentes acabados
  • que problemas e tarefas encontramos ao desenvolver nossa grade
  • que lucro o desenvolvimento de sua grade oferece

Antecedentes


Nosso sistema possui um aplicativo da web no qual os usuários trabalham com listas de documentos, resultados de pesquisa, diretórios. Além disso, as listas podem ser pequenas (10 funcionários) ou muito grandes (50.000 contratados). Para exibir essas listas, desenvolvemos nossa própria grade:


imagem


Quando começamos a desenvolver um aplicativo Web, queríamos encontrar uma biblioteca pronta para exibir uma grade que pode fazer tudo o que precisamos: classificar e agrupar registros, arrastar e soltar colunas, trabalhar com várias seleções, filtrar e calcular o total de colunas, em partes Baixe dados do servidor e exiba dezenas de milhares de registros.


Vou explicar o último requisito "exibir dezenas de milhares de registros". Em grades, esse requisito é implementado de várias maneiras: paginação, rolagem infinita, rolagem virtual.


As abordagens de paginação e rolagem infinita são comuns em sites, você as usa todos os dias. Por exemplo, paginação no Google:


imagem


Ou a rolagem infinita no mesmo Google nas imagens, onde a próxima parte das imagens é carregada quando você rola pela primeira parte até o final:


imagem


Mas a rolagem virtual (a seguir denominada rolagem virtual) raramente é usada na Web, sua principal diferença em relação à rolagem infinita é a capacidade de rolar rapidamente pelas listas muito grandes em qualquer lugar. Nesse caso, apenas os dados visíveis ao usuário serão baixados e exibidos.


imagem


Para nosso aplicativo da web, eu queria usar a rolagem virtual. Concordo que rolar para qualquer lugar na lista de 10.000 entradas é um caso bastante inventado. No entanto, a rolagem aleatória entre 500 e 1.000 registros é um caso ativo.


Quando implementam rolagem virtual, geralmente implementam a API do software para gerenciar essa rolagem. Este é um recurso muito importante. A rolagem de software é usada, por exemplo, para posicionar o registro selecionado no meio da tela ao abrir o diretório:


imagem


Voltar para os requisitos. Do que mais precisamos:


  • API de gerenciamento de rolagem virtual
  • Personalização da aparência da grade (linhas, colunas, menu de contexto) para que a grade não pareça estranha em nosso aplicativo
  • Suporte para as tecnologias que usamos: reagir, redux e flexbox
  • Que a rede funcionou em ie11

Em geral, havia muitos requisitos.


A primeira tentativa (2016). Grade de dados JavaScript do DevExtreme


Não demorando muito para explorar as bibliotecas existentes, deparamos com a DevExtreme JavaScript Data Grid. Por requisitos funcionais, essa grade cobria todas as nossas necessidades e apresentava uma aparência muito apresentável. No entanto, não era adequado para requisitos tecnológicos (nem reagir, nem redux, nem flexbox). Naquela época, o DevExtreme não tinha uma grade de reação.


Bem, não deixe que ele reaja, decidimos, porque a grade é bonita e funcional, vamos usá-la. E eles adicionaram a biblioteca ao seu projeto. Acabamos adicionando 3 MB de scripts.


Por algumas semanas, integramos a grade em nosso aplicativo Web e aumentamos a funcionalidade básica:


  • Embrulhou uma grade para fazer amizade com reagir e redux
  • Elevação da rolagem virtual e carregamento parcial de dados de nosso servidor da web
  • Classificação e seleção implementadas

No processo de aparafusar a grade, dois problemas sérios ficaram claros e vários outros menos sérios.


Primeiro problema sério


Tornar o DevExtreme JavaScript Data Grid com redux muito difícil. Conseguimos controlar as configurações da coluna e destacar registros através do redux, mas armazenar dados carregados em porções no redux e executar operações CRUD neles através do redux - isso não é realista. Eu tive que fazer uma muleta que, contornando o redux, manipulasse os dados da grade. A muleta acabou por ser complexa e frágil. Esta foi a primeira campainha de alarme que a grade não nos convinha, mas continuamos a apertá-la.


Segundo problema sério


Não há API de gerenciamento de rolagem virtual. Não foi possível recusar o controle de rolagem por software, tivemos que refazer as fontes do DevExtreme e encontrar a API de controle de rolagem interna. Obviamente, essa API tinha um monte de limitações, porque foi projetada para uso interno. Como resultado, conseguimos fazer com que a API interna funcionasse mais ou menos em nossos casos, mas, novamente, ignorando o redux e novamente um monte de muletas.


Problemas menos sérios


Problemas menos sérios apareciam o tempo todo, porque a funcionalidade padrão da grade de dados JavaScript do DevExtreme não era completamente adequada para nós e tentamos corrigi-lo:


  1. O alongamento da grade do DevExtreme em altura não funciona. Eu tive que escrever um hack para ensinar o DevExtreme como fazer isso (talvez não haja problemas com isso nas versões recentes).
  2. Quando o foco não está na grade, é impossível controlar a seleção de linhas através do teclado (e precisávamos disso). Eu tive que escrever meu controle de teclado.
  3. Ao alterar a composição das colunas e os dados, tivemos o problema de piscar de dados (com a rolagem virtual ativada).
  4. O problema de um grande número de solicitações na primeira exibição da grade. Foi especialmente notável quando controlamos a rolagem pela API interna.
  5. É difícil personalizar algumas partes da grade da interface do usuário. Por exemplo, havia um desejo no topo da linha de grade selecionada de desenhar ações de gerenciamento de linha (excluir uma linha, copiar, abrir um cartão). Mas como estragar isso no DevExtreme não estava claro, e mesmo usando o react:
    imagem
  6. É difícil catomizar a classificação (queríamos classificar por dados que não são exibidos na grade e não são mapeados em colunas).
  7. Muletas são necessárias para parafusar o componente de reação nas células da grade (afinal, a grade não está na reação).
  8. Nenhuma digitação do código do DevExtreme (fluxo / texto datilografado).
  9. Problema de velocidade com rolagem virtual longa.
  10. Problema de velocidade ao esticar / reorganizar as colunas (após rolagem virtual prolongada).
  11. O tamanho dos scripts de grade é de 3 Mb.

Embora a grade de funcionalidade do DevExtreme contivesse tudo o que precisávamos, eu queria reescrever quase toda a funcionalidade padrão. Durante seu uso, foram adicionadas centenas de linhas de código difíceis de entender que tentavam resolver os problemas de interação com o redux e o reagir, e era difícil usar a grade não-reativa em um aplicativo de reação.


Recusa de DevExtreme. Procure alternativas


Depois de algum tempo usando o DevExtreme, foi decidido abandoná-lo. Jogue fora todos os hacks, códigos complexos e também 3 MB de scripts DevExtreme. E encontre ou escreva uma nova grade.


Desta vez, estamos mais atentos ao estudo das redes existentes. Foram estudados a lista de detalhes da tela do MS, a grade virtualizada React, a grade de reação DevExtreme, a grade Telerik e a grade KendoUI.
Os requisitos permaneceram os mesmos, mas eles já tomaram forma em uma lista que entendemos.


Requisitos de tecnologia:


  • reagir
  • redux
  • flexbox

Requisitos funcionais:


  • Rolagem virtual (com a capacidade de exibir dezenas de milhares de registros)
  • API de gerenciamento de rolagem
  • Armazenando dados e configurações de grade no redux
  • Carregamento em lote de dados de um servidor da web
  • Gerenciamento de colunas (controle de estiramento / reorganização / visibilidade)
  • Classificação de colunas + filtragem
  • Seleção múltipla
  • Pesquisa semelhante com luz de fundo
  • Rolagem horizontal
  • Teclado
  • Menu de contexto (em uma linha, em uma área vazia, em colunas)
  • Suporte ie11, edge, chrome, ff, safari

Nesse ponto, a primeira versão do DevExtreme React Grid já havia sido exibida, mas a descartamos imediatamente pelos seguintes motivos:


  • Rolagem virtual não é suportada no ie11
  • A rolagem virtual não funciona em conjunto com o download de dados em lote do servidor (embora pareça haver algumas soluções alternativas).
  • E o mais importante, eu não queria entrar no mesmo rake quando queria reescrever metade da funcionalidade padrão de uma grade de terceiros.

A análise das soluções existentes mostrou que não existe uma “bala de prata”. Não existe uma grade que cubra todos os nossos requisitos. Foi decidido escrever nossa própria grade, que, em termos de funcionalidade, desenvolveremos na direção que precisamos e seremos amigos das tecnologias necessárias para o nosso produto.


Desenvolvendo sua grade de dados do React


O desenvolvimento da grade começou com protótipos, onde eles testaram os tópicos mais difíceis para nós:


  • rolagem virtual
  • armazenamento de todos os dados da grade no Redux

Rolagem virtual


O mais difícil acabou sendo a rolagem virtual. Na maioria das vezes, é feito de uma de três maneiras:


1. Virtualização de página
Os dados são desenhados em partes - páginas. Ao rolar, as páginas visíveis são adicionadas, as invisíveis são excluídas. A página consiste em 20 a 60 linhas (geralmente o tamanho é personalizável). É aqui que os produtos foram: DevExtreme JavaScript Data Grid, MS Fabric DetailsList.


imagem


2. Virtualização linha por linha
Somente linhas visíveis são desenhadas. Assim que uma linha sai da tela, ela é excluída imediatamente. Os produtos foram assim: Grade ReactVirtualized, Grade React DevExtreme, Grade Telerik.


imagem


3. Tela
Todas as linhas e seu conteúdo são desenhados usando o Canvas. Foi isso que o Google Docs fez.


imagem


Ao desenvolver a grade, criamos protótipos para as três opções de virtualização (mesmo para o Canvas). E eles escolheram a virtualização página por página.


Por que abandonar outras opções?


A virtualização linha a linha teve problemas com a velocidade de renderização no protótipo. Assim que o conteúdo das linhas se tornou mais complicado (muito texto, realce, corte, ícones, um grande número de colunas e em toda parte flexbox), ficou caro adicionar / remover linhas várias vezes por segundo. Obviamente, os resultados também dependem do navegador (fizemos suporte, incluindo ie11, edge):


imagem


A opção Canvas foi muito sedutora na velocidade de renderização, mas trabalhosa. Foi proposto desenhar tudo: texto, quebra de texto, corte de texto, destaque, ícones, linhas divisórias, destaque, recuo. Faça uma reação ao clicar no botão do mouse na tela, realçando as linhas quando você passa o mouse. Ao mesmo tempo, alguns elementos do Dom (mostrando dicas, "ações pop-up" na linha) devem ser aplicados sobre o Canvas. Ainda era necessário resolver o problema de desfocar texto e ícones no Canvas. Tudo isso é longo e difícil de fazer. Embora tenhamos dominado o protótipo. Ao mesmo tempo, qualquer personalização de linhas e células no futuro resultaria em uma grande laboriosa para nós.


Os benefícios da paginação


A virtualização de página por página selecionada tinha vantagens em comparação com linha por linha, que determinou sua escolha:


  • Se a página já estiver renderizada, a rolagem dentro da página é barata (a árvore DOM não muda ao rolar). A virtualização linha a linha para qualquer rolagem menor requer alteração da árvore DOM, e isso é caro quando a árvore DOM é complexa e o flexbox é usado em qualquer lugar.
  • Para listas pequenas (<200 entradas), as páginas não podem ser excluídas, basta adicionar. Mais cedo ou mais tarde, todas as páginas serão construídas e a rolagem será totalmente gratuita (em termos de tempo de renderização).

Seleção do tamanho da página


Um problema separado é a escolha do tamanho da página. Eu escrevi acima que o tamanho é personalizável e geralmente é de 20 a 60 linhas. Uma página grande é desenhada por um longo tempo, uma pequena leva a uma exibição frequente de uma "tela branca" ao rolar. Experimentalmente, um tamanho de página de 25 linhas foi selecionado. No entanto, para ie11, o tamanho foi reduzido para 5 linhas. Parece que a interface no IE é mais responsiva se você desenhar muitas páginas pequenas com pequenos atrasos do que uma grande com um grande atraso.


Reagir e rolagem virtual


A virtualização de página teve que ser implementada usando o react. Para fazer isso, várias tarefas devem ser resolvidas:


Tarefa 1. Como adicionar / remover páginas através do React ao rolar?


Para resolver esse problema, os seguintes conceitos foram introduzidos:


  • modelo de página
  • visualização de página

Um modelo é uma informação sobre a qual construir uma exibição. Uma visualização é um componente React.


imagem


De fato, a tarefa de virtualização depois disso se resumiu à manipulação de modelos de página: armazene uma lista de modelos de página, adicione e remova modelos ao rolar. E já a partir da lista de modelos através do react, crie / reconstrua a exibição:


imagem


No decorrer da implementação, as regras para trabalhar com modelos de página foram formadas:


  • As páginas devem ser adicionadas uma de cada vez. Após cada adição, reserve um tempo para desenhar. É aceitável adicionar 1 página a cada 300-500ms - essa é uma situação de rolagem rápida. Se você adicionar, por exemplo, 5 páginas de uma só vez, a interface do usuário ficará suspensa na construção.
  • As páginas não precisam ser mantidas em dezenas. Um exemplo de uma situação problemática: 20 páginas são exibidas, o usuário vai para outra lista e todas as 20 páginas devem ser excluídas de uma vez. A remoção de um grande número de páginas é uma operação cara; a limpeza da árvore DOM leva 1 segundo. Para evitar isso, é melhor manter no máximo 10 páginas por vez.
  • Para qualquer manipulação de coluna (reorganização, adição, exclusão, alongamento), é melhor excluir páginas que não são visíveis para o usuário com antecedência. Isso evitará a reconstrução cara de todas as páginas renderizadas.

Tarefa 2. Como exibir a barra de navegação?


A rolagem virtual pressupõe a disponibilidade de uma barra de rolagem, que leva em consideração o tamanho da lista e permite rolar para qualquer lugar:


imagem


Como exibir uma barra de navegação? A solução mais simples é desenhar uma div invisível do tamanho necessário, em vez de dados reais. E já no topo desta div, exibimos as páginas visíveis:


imagem


Tarefa 3. Como monitorar o tamanho da viewport?


Viewport é a área de dados visível da grade. Por que ficar de olho no tamanho dela? Para calcular o número de páginas que precisam ser exibidas para o usuário. Suponha que tenhamos um tamanho de página pequeno (5 linhas) e uma resolução de tela grande (1920x1080). Quantas páginas o usuário precisa exibir para fechar a janela de exibição inteira?


imagem


Você pode resolver esse problema se souber a altura da janela de exibição e a altura de uma página. Agora vamos complicar a tarefa, suponha que o usuário altere a escala acentuadamente no navegador - define 50%:


imagem


A situação da escala mostra que não é suficiente descobrir o tamanho da janela de visualização uma vez; é necessário monitorar o tamanho. E agora vamos complicar completamente a tarefa: os elementos html não têm um evento de redimensionamento, no qual você pode se inscrever e monitorar o tamanho. Somente o objeto da janela foi redimensionado.


A primeira coisa que vem à mente é usar um timer e pesquisar constantemente a altura do elemento html. Mas há uma solução ainda melhor que vimos na grade de dados do DevExtreme JavaScript: crie um iframe invisível, estenda-o para o tamanho da grade e assine o evento de redimensionamento do iframe.contentWindow:


imagem


imagem


Sumário


PS: Este não é o fim. No próximo artigo, mostrarei como fizemos amizade com o redux.


Para obter uma rolagem virtual completa, muitas outras tarefas precisavam ser resolvidas. Mas os descritos acima foram os mais interessantes. Aqui estão algumas outras tarefas que também aparecem:


  • Leve em consideração a direção e a velocidade da rolagem ao adicionar / remover páginas.
  • Leve em consideração as alterações de dados para minimizar os modelos de página de reconstrução. Por exemplo, excluiu uma linha ou adicionou uma linha, o que fazer com as páginas já renderizadas? Jogue tudo fora ou deixe um pouco? Há espaço para otimização.
  • Ao alterar a seleção, reorganize o número mínimo de páginas necessário.

Se você tiver perguntas sobre a implementação, escreva-as nos comentários.

Source: https://habr.com/ru/post/pt457504/


All Articles