React-window: reage a barra de rolagem personalizada com a janela react

Criado em 17 dez. 2018  ·  35Comentários  ·  Fonte: bvaughn/react-window

Estou usando react-custom-scrollbar e gostaria de integrá-lo a FixedSizeList .

Verifiquei a solução para este problema em react-virtualized : https://github.com/bvaughn/react-virtualized/issues/692#issuecomment -339393521

Mas o código está gerando um erro: Uncaught TypeError: Cannot read property 'handleScrollEvent' of undefined na rolagem, nesta função:

  handleScroll = ({ target }) => {
    const { scrollTop, scrollLeft } = target;

    const { Grid: grid } = this.List;

    grid.handleScrollEvent({ scrollTop, scrollLeft });
  }

Eu adicionei ref={ instance => { this.List = instance; } } no componente fixedSixe <List .

💬 question

Comentários muito úteis

A melhor abordagem seria passar Scrollbars como outerElementType

const CustomScrollbars = ({ onScroll, forwardedRef, style, children }) => {
  const refSetter = useCallback(scrollbarsRef => {
    if (scrollbarsRef) {
      forwardedRef(scrollbarsRef.view);
    } else {
      forwardedRef(null);
    }
  }, []);

  return (
    <Scrollbars
      ref={refSetter}
      style={{ ...style, overflow: "hidden" }}
      onScroll={onScroll}
    >
      {children}
    </Scrollbars>
  );
};

const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
  <CustomScrollbars {...props} forwardedRef={ref} />
));

// ...

<FixedSizeList
  outerElementType={CustomScrollbarsVirtualList}
  {...rest}
/>

exemplo https://codesandbox.io/s/vmr1l0p463

Todos 35 comentários

Esta biblioteca e react-virtualized são implementações completamente diferentes. As técnicas gerais que você vê nas questões de react-virtualized podem frequentemente fornecer dicas úteis sobre como abordar algo, mas detalhes de implementação específicos como este não são aplicáveis. (Em react-virtualized , List decora um componente Grid . Em react-window eles são componentes separados, para melhor desempenho e tamanho.)

Não sei se algo como react-custom-scrollbar funcionaria com react-window pois nunca experimentei. Não é algo que eu esteja muito interessado em apoiar para ser honesto, já que acho que as barras de rolagem personalizadas geralmente são uma má ideia por causa de como elas afetam o desempenho. Portanto, encerrarei este problema.

Mas podemos continuar a conversar sobre isso se você tiver perguntas adicionais 😄

Vou descobrir algo sobre isso, estou interessado em usar react-window porque é leve e resolve meu problema. Mesmo eu não gosto de usar a barra de rolagem personalizada, mas ela não está em minhas mãos, precisamos de um design personalizado para a barra de rolagem.

Usarei react-virtualized , na pior das hipóteses.
Obrigado :)

@ Rahul-Sagore, você acabou recebendo barras de rolagem personalizadas com react-window ?

Não, não tive tempo para isso. Precisa verificar e descobrir.

Não tenho certeza se @bvaughn aprova isso, mas parece funcionar: https://codesandbox.io/s/00nw2w1jv

A melhor abordagem seria passar Scrollbars como outerElementType

const CustomScrollbars = ({ onScroll, forwardedRef, style, children }) => {
  const refSetter = useCallback(scrollbarsRef => {
    if (scrollbarsRef) {
      forwardedRef(scrollbarsRef.view);
    } else {
      forwardedRef(null);
    }
  }, []);

  return (
    <Scrollbars
      ref={refSetter}
      style={{ ...style, overflow: "hidden" }}
      onScroll={onScroll}
    >
      {children}
    </Scrollbars>
  );
};

const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
  <CustomScrollbars {...props} forwardedRef={ref} />
));

// ...

<FixedSizeList
  outerElementType={CustomScrollbarsVirtualList}
  {...rest}
/>

exemplo https://codesandbox.io/s/vmr1l0p463

Eu amo quantas coisas avançadas são possíveis usando outerElementType ou innerElementType

Estou tentando detectar a posição de rolagem na parte inferior da janela de reação.
É possível? Você tem alguma ideia para isso? Eu gostaria de ter um exemplo de

Você deve ser capaz de usar os recursos de referência disponíveis para pedir a lista por seu scrollHeight @rufoot.

Tentei usar scrollHeight nos adereços. Mas eu acho que não há scrollHeight propriedade nisso.
@bvaughn Você tem alguma ideia sobre isso?

https://codesandbox.io/s/github/bvaughn/react-window/tree/master/website/sandboxes/scrolling-to-a-list-item

scrollToRow200Auto = () => { this.listRef.current.scrollToItem(200); console.log("current position = ", this.listRef.current.props.scrollHeight) };

posição atual = indefinido

https://codesandbox.io/embed/jzo2lool2y

Ao rolar para baixo na parte inferior da janela de reação, você pode ver um alerta.

@piecyk Eu tentei sua solução com outerElementType e é super lag, você já testou?: D

Eu tentei com react-scrollbars-custom . É menos lento, mas ainda assim. Alguém teve problemas com atrasos ao usar barras de rolagem personalizadas?

@ jancama2 não o testou 😂Você pode compartilhar o Code Sandbox quando ele ficar lento?

@rufoot algo assim https://codesandbox.io/s/4zjwwq98j4 ?

Estou usando sua implementação, mas recebo um erro dizendo que forwardedRef não é uma função.

O problema em meu aplicativo que estou tentando resolver é que tenho uma grande lista de itens e, quando clico em um, o aplicativo redireciona para outra rota, então preciso controlar a posição de rolagem (provavelmente em redux) e em seguida, defina a posição de rolagem após a mudança de rota.

@piecyk , você tentou combiná-lo com o InfiniteLoader de react-window-infinite-loader .
Estou usando react-scrollbars-custom e parece estar quebrando o carregador infinito :(

Meu código:

<InfiniteLoader isItemLoaded={isItemLoaded} itemCount={items.total} loadMoreItems={loadMoreItems}>
      {({
        onItemsRendered,
        ref,
      }: {
        onItemsRendered: (props: ListOnItemsRenderedProps) => any;
        ref: React.Ref<any>;
      }) => (
        <List
          height={TABLE_HEIGHT}
          width={width}
          itemCount={items.total}
          itemSize={ROW_HEIGHT}
          onItemsRendered={onItemsRendered}
          itemData={{ items }}
          ref={ref}
          outerElementType={Scrollbar}
        >
          {Row}
        </List>
      )}
    </InfiniteLoader>

Atualização: não acho que esteja relacionado ao componente de rolagem infinita. Não consigo fazer funcionar com a barra de rolagem personalizada.

Eu apreciaria se alguém pudesse compartilhar a implementação do react-scrollbars-custom comigo

@ranihorev você precisa usar esse pacote? A caneta de código @piecyk compartilhada acima usa

Ok, preciso perguntar novamente. @piecyk Estou usando seu exemplo de código acima (onde você respondeu a @simjes ). em vez de usar uma lista de tamanho fixo, porém, estou usando o dimensionador automático para envolver um VariableSizeList e especificar o outerElementType. Algo assim:
<TreeRoot onDragOver={e => this.onDragOver(e)} > <AutoSizer> {({ height, width }) => ( <List treeRef={this.props.treeRef} width={width} height={height} itemSize={index => rowHeights[index]} itemCount={nodeTree.length} itemData={{ nodeTree, expandedNodes, nodesFetching, nodesFetchingFailed, selectedNode, selectedNodeId, resetRowHeight: this.resetRowHeight, }} outerElementType={TreeScrollbar} outerRef={this.props.outerRef} onScroll={({ scrollOffset, scrollUpdateWasRequested }) => { if (scrollUpdateWasRequested) { console.log('scrollOffset: ', scrollOffset); } }} > {Node} </List> )} </AutoSizer> </TreeRoot>

Implemento CustomScrollbars e CustomScrollbarsVirtualList da mesma forma que você, mas o ref é sempre nulo. Tentei criar o ref no componente que estou usando o AutoSizer, bem como em seu pai, mas é sempre nulo. Eu realmente apreciaria alguma ajuda para entender o porquê. Como mencionei acima, preciso controlar a posição de rolagem e ser capaz de defini-la em uma mudança de rota em meu aplicativo para que a lista não volte ao topo.

@ChristopherHButler Já estou usando esse pacote várias vezes no meu código, então prefiro continuar usando o mesmo pacote. A solução é bastante semelhante, mas não busca novos dados corretamente

@ChristopherHButler muito difícil de dizer sem nenhum exemplo do Code Sandbox do problema, você pode compartilhar algo?
A ideia básica aqui é definir o ref de react-custom-scrollbars para que a react-window

AutoSizer ou VariableSizeList funcionarão com esta abordagem, como neste exemplo
https://codesandbox.io/s/react-window-custom-scrollbars-t4352

@ranihorev você também pode compartilhar o exemplo do Code Sandbox?

@piecyk eu crio um exemplo simples (e quebrado):
https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-64kzh?fontsize=14

Eu também tentei outras variações (por exemplo, definir o ref para ser o wrapper do scroller), mas sem sucesso ...

Obrigado!

@ranihorev, essa abordagem seria a mesma, independentemente da implementação de rolagem customizada. Eles precisam definir a referência para o mesmo elemento que é responsável por rolar e manipular esse evento de rolagem.

react-scrollbars-custom fornece uma API mais agradável usando o padrão de propriedades de renderização para que você possa fazer algo como https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-pjyxs

Não fiz nenhum perfil, espero que isso ajude 👍

@piecyk Estou trabalhando com sua demonstração aqui: https://codesandbox.io/s/4zjwwq98j4 . Eu quero usar o onScrollStop para despachar uma ação em redux para que eu possa definir a posição de rolagem (uma ideia deste tópico: https://github.com/malte-wessel/react-custom-scrollbars/issues/146 mas onScrollStart e onScrollStop não parece funcionar no meu código ou na sua demonstração. Você sabe por que isso acontece? Existe alguma maneira de passar adereços da lista para as barras de rolagem?

@ChristopherHButler acho que sua melhor opção é usar o contexto para passar os adereços, algo como

https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-v2-usi1m

--- Editado
Se você desmontar a lista inteira quando a rota mudar, você pode despachar a ação, Last scrollOffset pode ser armazenado em ref e atualizado em cada mudança de rolagem usando onScroll: function de VariableSizeList, então você não precisa passar adereços para barras de rolagem

@piecyk isso é incrível, obrigado!
btw, parece que tudo que você precisa é a função onScroll (que eu perdi) e não há necessidade de encaminhar um ref.
https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-99dn1

@piecyk, desculpe, eu deveria ter mencionado que minha implementação do VariableSizeList está envolvida em um AutoSizer e o componente que o renderiza é um componente baseado em classe, então não tenho certeza se posso usar o contexto. Talvez seja por isso que não consigo fazer o onScrollStop funcionar?

Rápida atualização:
Eu adicionei o estado do componente e configurei-o como onScroll.

`
class BaseTree extends Component {
...
estado = {scrollPosition: 0,};
...
listRef = React.createRef ();
outerRef = React.createRef ();
...
render {
const {nodes, nodesFetching, nodesFetchingFailed, extendedNodes, selectedNode} = this.props;
Retorna (

{({altura, largura}) => (
ref = {this.listRef}
className = "Lista"
outerRef = {this.outerRef}
outerElementType = {TreeScrollbar}
largura = {largura}
altura = {altura}
onScroll = {({scrollOffset}) => this.setState ({scrollPosition: scrollOffset})}
itemSize = {index => rowHeights [index]}
itemCount = {nodeTree.length}
itemData = {{
nodeTree,
ExpandidoNodes,
nodesFetching,
nodesFetchingFailed,
selectedNode,
selectedNodeId,
resetRowHeight: this.resetRowHeight,
}}
>
{Nó}

)}

);
}}

const mapStateToProps = state => ({
scrollPosition: selectors.getTreeScrollPosition (state),
});

const mapDispatchToProps = dispatch => ({
setTreeScrollPosition: position => dispatch (actions.setTreeScrollPosition ({position})),
});

exportar conexão padrão (mapStateToProps, mapDispatchToProps) (BaseTree);
`

então, em componentWillUnmout eu despacho a ação para definir a posição em redux assim:

componentWillUnmount() { this.props.setTreeScrollPosition(this.state.scrollPosition); }

meu único problema agora é ser capaz de definir a posição de rolagem quando o componente for montado novamente. Tentei acessar o método scrollTop em this.listRef assim:

this.listRef.current.scrollTop() mas recebo um erro informando que não é uma função. Não tenho certeza de qual propriedade (this.listRef ou this.outerRef) posso usar para definir a posição de rolagem ou em qual método. Eu estava pensando que poderia defini-lo no componentDidMount do BaseTree da seguinte maneira:
componentDidMount() { const { scrollPosition } = this.props; if (this.listRef.current) { // console.log('initializing scroll position to : ', scrollPosition); this.listRef.current.scrollTop(scrollPosition); } }
Qualquer ajuda para fazer isso funcionar seria muito apreciada!
editar: Lamento muito, mas meu código não parece estar formatado corretamente neste editor, então peço desculpas por isso :(

@ranihorev forward ref para a janela de reação é necessária para funções como scrollTo, scrollToItem para funcionar

O contexto de criado , a partir de documentos

O contexto fornece uma maneira de passar dados pela árvore de componentes sem ter que passar os adereços manualmente em todos os níveis.

Mas sim, como mencionei antes, se você desmontar todo o componente na mudança de rota, não há necessidade de passar pelos adereços. Não armazenaria scrollPosition no estado, pois você não precisa dele enquanto a rolagem está acontecendo, menos re-renderiza, armazena-o na ref.

onScroll={({ scrollOffset }) => this.setState({ scrollPosition: scrollOffset })}

// to 

lastScrollOffsetRef = React.createRef(0);

<List
  onScroll={({ scrollOffset }) => {
    this.lastScrollOffsetRef.current = scrollOffset
  }}
  // ...rest
/>

// then in 
componentWillUnmount() { 
  this.props.setTreeScrollPosition(this.lastScrollOffsetRef.current); 
}

Com relação a outerRef , não é necessário no seu caso. listRef.current.scrollTo é o método correto que você deseja usar. Hmm, sua ideia de chamar o método em componentDidMount está correta e deve funcionar,

https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-80zo7

parece um problema de temporização ao definir refs, talvez o AutoSizer atrasa a renderização da lista que faz com que this.listRef.current seja indefinido,

a opção hacky é quebrar o ciclo de vida de reação com setTimeout e chamar o árbitro no próximo tick

componentDidMount() {
  window.setTimeout(() => {
    if (this.listRef.current) {
      this.listRef.current.scrollTo(this.props.scrollPosition);
    }
  }, 0);
}

@piecyk muito obrigado pela ajuda, eu realmente agradeço :)

Obrigado @piecyk Isso parece funcionar. Há uma pequena oscilação na montagem após a mudança de rota, mas eu consigo viver com isso :) Eu também tentei outro hack que era usar o className para passar o scrollPosition e definir scrollTop no refSetter de CustomScrollbars:
if (scrollbarsRef) { forwardedRef(scrollbarsRef.view); // HACK scrollbarsRef.scrollTop(className); }
Isso parece funcionar bem.
Obrigado novamente por toda sua ajuda, é muito apreciado !! 🙌🏻

Infelizmente, todos os exemplos neste problema não funcionam corretamente (às vezes o mouse para de responder para rolar).

Mas este exemplo funciona perfeitamente!

@ranihorev, essa abordagem seria a mesma, independentemente da implementação de rolagem customizada. Eles precisam definir a referência para o mesmo elemento que é responsável por rolar e manipular esse evento de rolagem.

react-scrollbars-custom fornece uma API mais agradável usando o padrão de propriedades de renderização para que você possa fazer algo como https://codesandbox.io/s/bvaughnreact-window-react-scrollbars-custom-pjyxs

Eu tentei com react-scrollbars-custom . É menos lento, mas ainda assim. Alguém teve problemas com atrasos ao usar barras de rolagem personalizadas?

Você fez isso? Qual é a solução final?

Se isso ajudar alguém, consegui fazer funcionar com OverlayScrollbars . Você só precisa passar o evento scroll para o elemento correspondente. É assim:

const Overflow = ({ children, onScroll }) => {
  const ofRef = useRef(null);

  useEffect(() => {
    const el = ofRef.current.osInstance().getElements().viewport;

    if (onScroll) el.addEventListener('scroll', onScroll);

    return () => {
      if (onScroll) el.removeEventListener('scroll', onScroll);
    };
  }, [onScroll]);

  return (
    <OverlayScrollbarsComponent
      options={options}
      ref={ofRef}
    >
      {children}
    </OverlayScrollbarsComponent>
  );
};

E então, em seu componente virtualizado:

<FixedSizeGrid
  {...props}
   outerElementType={Overflow}
>

Estou usando isso com FixedSizeGrid mas deve funcionar da mesma forma para listas.

Espero que ajude.

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

Questões relacionadas

marian-c picture marian-c  ·  3Comentários

janhesters picture janhesters  ·  3Comentários

ajayns picture ajayns  ·  3Comentários

bitboxer picture bitboxer  ·  3Comentários

jsu93 picture jsu93  ·  4Comentários