React-window: Использование элемента таблицы html

Созданный на 21 сент. 2018  ·  34Комментарии  ·  Источник: bvaughn/react-window

У вас есть пример использования этого с таблицами? Я пытаюсь заставить его работать, но у меня есть сильное подозрение, что он либо не работает, либо я делаю что-то очень не так.
Я установил для externalTagName значение «table», а для innerTagName значение «tbody», но прокрутки нет.

Вот мой код, не уверен, помогает ли он (items - это список объектов):

 <List
            outerRef={this._list}
            outerTagName="table"
            innerTagName="tbody"
            height={300}
            itemData={items}
            itemSize={() => 30}
            itemCount={items.length}
            itemKey={item => item.uniqueId}>
            {({index, style, data}) => {
              return (
                <tr style={style} key={index}>
                  <td>Item {index}</td>
                </tr>
              );
            }}
          </List>
💬 question

Самый полезный комментарий

@pupudu Я приветствую ваши усилия по открытому исходному

Оказывается, мне удалось добиться этого с помощью очень небольшого фрагмента кода и большого творчества. Код достаточно прост, чтобы любой мог копировать / вставлять и расширять его по своему усмотрению. @bvaughn, выяснив, как это сделать, было бы довольно тривиально добавить в библиотеку, если хотите, но также довольно легко переопределить, если вы знаете, как.

Вкратце :

  • Захватить "верхний" стиль первой строки после рендеринга.
  • Сохраните это значение в React.Context, чтобы мы могли передавать его.
  • Примените значение к компоненту таблицы, который будет перемещаться, а не к самим строкам
  • Добавьте дополнительные слоты для таких вещей, как верхние и нижние колонтитулы.
  • Эргономика библиотеки не позволяет аккуратно передавать вещи, поэтому React.Context - это герой, который преодолевает межкомпонентную коммуникацию.

Песочница рабочего кода: https://codesandbox.io/s/react-window-with-table-elements-d861o

Код для справки

import React from 'react'
import { useState, useRef, useContext } from 'react'
import { FixedSizeList, FixedSizeListProps } from 'react-window'
import { render } from 'react-dom'

/** Context for cross component communication */
const VirtualTableContext = React.createContext<{
  top: number
  setTop: (top: number) => void
  header: React.ReactNode
  footer: React.ReactNode
}>({
  top: 0,
  setTop: (value: number) => {},
  header: <></>,
  footer: <></>,
})

/** The virtual table. It basically accepts all of the same params as the original FixedSizeList.*/
function VirtualTable({
  row,
  header,
  footer,
  ...rest
}: {
  header?: React.ReactNode
  footer?: React.ReactNode
  row: FixedSizeListProps['children']
} & Omit<FixedSizeListProps, 'children' | 'innerElementType'>) {
  const listRef = useRef<FixedSizeList | null>()
  const [top, setTop] = useState(0)

  return (
    <VirtualTableContext.Provider value={{ top, setTop, header, footer }}>
      <FixedSizeList
        {...rest}
        innerElementType={Inner}
        onItemsRendered={props => {
          const style =
            listRef.current &&
            // @ts-ignore private method access
            listRef.current._getItemStyle(props.overscanStartIndex)
          setTop((style && style.top) || 0)

          // Call the original callback
          rest.onItemsRendered && rest.onItemsRendered(props)
        }}
        ref={el => (listRef.current = el)}
      >
        {row}
      </FixedSizeList>
    </VirtualTableContext.Provider>
  )
}

/** The Row component. This should be a table row, and noted that we don't use the style that regular `react-window` examples pass in.*/
function Row({ index }: { index: number }) {
  return (
    <tr>
      {/** Make sure your table rows are the same height as what you passed into the list... */}
      <td style={{ height: '36px' }}>Row {index}</td>
      <td>Col 2</td>
      <td>Col 3</td>
      <td>Col 4</td>
    </tr>
  )
}

/**
 * The Inner component of the virtual list. This is the "Magic".
 * Capture what would have been the top elements position and apply it to the table.
 * Other than that, render an optional header and footer.
 **/
const Inner = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
  function Inner({ children, ...rest }, ref) {
    const { header, footer, top } = useContext(VirtualTableContext)
    return (
      <div {...rest} ref={ref}>
        <table style={{ top, position: 'absolute', width: '100%' }}>
          {header}
          <tbody>{children}</tbody>
          {footer}
        </table>
      </div>
    )
  }
)

/**
 * Render Our Example
 **/
render(
  <VirtualTable
    height={300}
    width="100%"
    itemCount={1000}
    itemSize={36}
    header={
      <thead>
        <tr>
          <th>Index</th>
          <th>Header 2</th>
          <th>Header 3</th>
          <th>Header 4</th>
        </tr>
      </thead>
    }
    row={Row}
    footer={
      <tfoot>
        <tr>
          <td>Footer 1</td>
          <td>Footer 2</td>
          <td>Footer 3</td>
          <td>Footer 4</td>
        </tr>
      </tfoot>
    }
  />,
  document.querySelector('main')
)

Все 34 Комментарий

Не думаю, что это действительно сработает. Насколько мне известно, HTMLTableElement самом деле не поддерживает переполнение так, как это может потребоваться оконному компоненту. Вы _ можете_ изменить стиль на display: block но я все еще не думаю, что это будет работать правильно. ( Вот пример .)

Зачем тебе стол? Я думаю, вы можете добиться того же макета столбцов с помощью встроенных блоков или гибких макетов.

Пара дополнительных вещей:

  • Вы не указываете требуемый параметр от width до List
  • Вам не нужно добавлять key в свои строки
  • itemSize может быть просто 30 а не () => 30 (это будет работать немного лучше)

Спасибо за быстрый ответ.
Мы уже обсуждали его переписывание в div, но поскольку это для существующего довольно большого компонента, мы просто хотели убедиться, прежде чем начинать это переписывание. Я видел некоторые другие комментарии о rc-table, поэтому подумал, что, возможно, это должно работать.
В любом случае, не возражайте против переписывания, я думаю, оно того стоит.

Я получил предупреждения React о ключах без ключа в строке, что также сбивало меня с толку, учитывая, что я устанавливаю itemKey, поэтому не уверен, что там происходит.

Спасибо

Эйнар

21 сентября 2018 года в 16:52 Брайан Вон [email protected] написал:

Не думаю, что это действительно сработает. Насколько мне известно, HTMLTableElement на самом деле не поддерживает переполнение в том смысле, в каком требуется оконный компонент. Вы можете изменить стиль на display: block, но я все равно не думаю, что это будет работать правильно.

Зачем тебе стол? Я думаю, вы можете добиться того же макета столбцов с помощью встроенных блоков или гибких макетов.

Пара дополнительных вещей:

Вы не указываете ширину обязательного параметра для списка
Вам не нужно добавлять ключ в свои строки
itemSize может быть просто 30, а не () => 30 (он будет работать немного лучше)
-
Вы получаете это, потому что вы являетесь автором темы.
Ответьте на это письмо напрямую, просмотрите его на GitHub или отключите чат.

Я получил предупреждения React о ключах без ключа в строке, что также сбивало меня с толку, учитывая, что я устанавливаю itemKey, поэтому не уверен, что там происходит.

Этого не ожидается. Можете ли вы поделиться со мной повторным случаем (через Code Sandbox)?

Ой, извини. Я должен добавить, что этого можно было бы _только_ ожидать, если бы ваша функция itemKey вернула undefined для некоторых элементов - и в этом случае исправление должно быть связано с этой функцией.

@bvaughn Я не нахожу ничего конкретного о том, что поддержка переполнения является ограничивающим фактором. Любопытно, я хотел бы знать, что это может быть. Я, конечно, понимаю, что поддержка основных функций для обоих может быть утомительной, но это выглядит только так, как будто элемент базового списка - это все, что на данный момент не может быть выбрано разработчиком. Что касается меня и всех, кто может использовать response-window со сценариями с фиксированным заголовком, было бы неплохо получить встроенную поддержку таблиц, встроенную в макеты таблиц. Использование display: table требует "некоторых" накладных расходов разработчика и является разовым макетом для окружающих приложений, которые случайно могут быть построены с использованием таблиц. Я всегда мог настроить переполнение или другие свойства через css (я все равно делаю это в текущей реализации). О возможности elementType не может быть и речи?

@bvaughn Я не нахожу ничего конкретного о том, что поддержка переполнения является ограничивающим фактором.

Возможно, я использовал не лучшую формулировку, но если вы посмотрите на песочницу, с которой я связан ( rrn61wkzwm ), вы увидите неожиданное поведение.

Вот еще один пример HTML: lx65871p69

HTMLSelectElement ( <tbody> ) игнорирует такие стили, как height и overflow что делает его бесполезным для работы с окнами. Вы можете «исправить» это, изменив режим отображения ( display: block ), но это лишает смысла использование HTMLTableElement потому что это нарушает поведение изменения размера столбца.

Это поведение изменения размера столбца в любом случае нарушено для случая использования окон, потому что размер зависит от содержимого в столбцах (которое будет меняться при прокрутке пользователем).

но это только выглядит так, как будто элемент базового списка - это все, что на данный момент не может быть выбрано разработчиком ... Не может быть и речи о поддержке elementType?

Вы уже можете указать тип тега, если действительно хотите ( 2j0z718mwy ), хотя я не вижу, что это дает вам.

Что касается меня и всех, кто может использовать response-window со сценариями с фиксированным заголовком, было бы неплохо получить встроенную поддержку таблиц, встроенную в макеты таблиц.

Использование HTMLTableElement было бы проблематичным по причинам, которые я объяснил выше. Если вам нужен макет таблицы / сетки с фиксированным заголовком, вы можете сделать это, используя один из обычных компонентов списка: pk78pvwnkx

Зачем тебе стол? Я думаю, вы можете добиться того же макета столбцов с помощью встроенных блоков или гибких макетов.

Может быть, для некоторой доступности требуется структура таблицы (например, для лучшего чтения экрана по сравнению с несемантическими div)? Я здесь предполагаю.

В этом случае я бы переопределил стиль отображения CSS и любые другие стили, присущие элементам таблицы.

Мой опыт создания сложного табличного компонента (с фиксированными / закрепленными заголовками сверху и слева, запутанной структурой colspan / rowspan и моделью данных для описания структуры, стоящей за ней) около девяти лет назад (тогда у нас не было React, никаких проблем с доступностью, и, насколько я помню, никакой виртуализации), мой коллега, который отвечал за этот компонент, пытался реализовать его в правильных элементах таблицы, боролся с тем, чтобы заставить макет вести себя определенным образом, а затем решил реализовать макет таблицы на чистом JavaScript из абсолютно позиционированные и размерные блоки div. Это решение сработало отлично: никаких странных переходов по макету, плавная прокрутка (не по строке / столбцу) и т. Д.

Может быть, для некоторой доступности требуется структура таблицы (например, для лучшего чтения экрана по сравнению с несемантическими div)? Я здесь предполагаю.

Для этого можно использовать роли Арии 😄

Очень тщательно, спасибо. Вы только что спасли меня, когда я спустился в кроличью нору, чтобы попытаться создать прототип, который решал бы одну проблему только для создания других.

Привет
Я попытался найти существующий поток вместо того, чтобы создавать новый. Надеюсь, я нахожусь в хорошем месте ...

Есть ли способ создать своего рода «colspan» для слияния двух ячеек для FixedSizeGrid (вероятно, имеет больше смысла в моем контексте)?
Этот вопрос основан на другом обсуждении, начатом здесь , но для большей ясности я предпочитаю публиковать здесь.

Есть ли способ создать своего рода «colspan» для объединения двух ячеек для FixedSizeGrid (вероятно, имеет больше смысла в моем контексте)?

Нет. Это не поддерживается.

@einarq @BlaineBradbury @sompylasar Если вам все еще интересно, мне удалось получить оконную работу с тегами таблиц. Фактически, я закончил тем, что создал библиотеку, оборачивающую окно реакции. Вы можете увидеть живую демонстрацию здесь: https://window-table.netlify.com/

Причина, по которой мне нужны теги таблиц, была в основном для стилизации. Стилизовать обычную таблицу html с помощью tw-bootstrap довольно просто, и поэтому я хотел сделать то же самое с таблицей с окнами.

У меня возникает соблазн пометить и спросить Брайана, что он думает об этой реализации (поскольку он упомянул, что теги таблицы html не будут работать). Но он уже достаточно мне помог. Так что я оставлю это вам, ребята.

Спасибо.

Интересный подход @pupudu. Я не знаю, возникнут ли какие-либо проблемы с доступностью из-за наличия отдельных тегов <table> для заголовков и строк, но в противном случае отличная идея.

Я пытаюсь использовать react-datasheet с react-window , в основном это работает:

function sheetRenderer(props) {
  const Row = ({ index, style }) => (
    React.cloneElement(props.children[index], { style })
  );
  return (
    <table className={props.className}>
        <tbody>
          <FixedSizeList
              height={150}
              itemCount={1000}
              itemSize={35}
              width={300}
          >
            {Row}
          </FixedSizeList>
        </tbody>
    </table>
  )
}

export default function EntityTable(props: IEntityTableProps) {
  const initialGrid = useMemo(() => {
    return getTableGridFromCSV(props.entityListCSV);
  }, [props.entityListCSV]);
  const [modifiedGrid, updateModifiedGrid] = useImmer(initialGrid);
  return (
    <ReactDataSheet
      data={modifiedGrid}
      sheetRenderer={sheetRenderer}
      valueRenderer={cell => cell.value}
      onCellsChanged={changes => {
        updateModifiedGrid(draft => {
          changes.forEach(({ cell, row, col, value }) => {
            draft[row][col].value = value || '';
          });
        });
      }}
    />
  );
}

function getTableGridFromCSV(csv: string) {
  return csv.split('\n').map(row =>
    row.split(',').map((cell, index) => {
      if (index === 0) {
        return {
          value: cell,
          forceComponent: true,
          component: <button onClick={() => console.log(cell)}>{cell}</button>,
        };
      }
      if (index === 2) {
        return {
          value: cell,
        };
      }
      return {
        value: cell,
        component: <span>{cell}</span>,
      };
    }),
  );
}

Он отображает, единственная проблема - Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. index.js:1437 Warning: validateDOMNesting(...): <div> cannot appear as a child of <tbody>.

Эй, я пробую это с помощью React-Table и замечаю, что мне нужно использовать div вместо элементов таблицы. Проблема в том, что библиотеки стилей, такие как material / bootstrap, полагаются на эти элементы. Так что изменить схему моей таблицы на div, а не на элементы - гораздо сложнее. Можно ли вообще использовать элементы таблицы для различий или мне нужно создать свой собственный стиль сейчас?

@simkessy Если вы еще не заметили, вам следует проверить window-table. Он позволяет использовать теги таблиц HTML, позволяя повторно использовать существующие стили. Однако следует признать, что таблица не такая продвинутая, как таблица реакции.
https://window-table.netlify.com/

@pupudu Я приветствую ваши усилия по открытому исходному

Оказывается, мне удалось добиться этого с помощью очень небольшого фрагмента кода и большого творчества. Код достаточно прост, чтобы любой мог копировать / вставлять и расширять его по своему усмотрению. @bvaughn, выяснив, как это сделать, было бы довольно тривиально добавить в библиотеку, если хотите, но также довольно легко переопределить, если вы знаете, как.

Вкратце :

  • Захватить "верхний" стиль первой строки после рендеринга.
  • Сохраните это значение в React.Context, чтобы мы могли передавать его.
  • Примените значение к компоненту таблицы, который будет перемещаться, а не к самим строкам
  • Добавьте дополнительные слоты для таких вещей, как верхние и нижние колонтитулы.
  • Эргономика библиотеки не позволяет аккуратно передавать вещи, поэтому React.Context - это герой, который преодолевает межкомпонентную коммуникацию.

Песочница рабочего кода: https://codesandbox.io/s/react-window-with-table-elements-d861o

Код для справки

import React from 'react'
import { useState, useRef, useContext } from 'react'
import { FixedSizeList, FixedSizeListProps } from 'react-window'
import { render } from 'react-dom'

/** Context for cross component communication */
const VirtualTableContext = React.createContext<{
  top: number
  setTop: (top: number) => void
  header: React.ReactNode
  footer: React.ReactNode
}>({
  top: 0,
  setTop: (value: number) => {},
  header: <></>,
  footer: <></>,
})

/** The virtual table. It basically accepts all of the same params as the original FixedSizeList.*/
function VirtualTable({
  row,
  header,
  footer,
  ...rest
}: {
  header?: React.ReactNode
  footer?: React.ReactNode
  row: FixedSizeListProps['children']
} & Omit<FixedSizeListProps, 'children' | 'innerElementType'>) {
  const listRef = useRef<FixedSizeList | null>()
  const [top, setTop] = useState(0)

  return (
    <VirtualTableContext.Provider value={{ top, setTop, header, footer }}>
      <FixedSizeList
        {...rest}
        innerElementType={Inner}
        onItemsRendered={props => {
          const style =
            listRef.current &&
            // @ts-ignore private method access
            listRef.current._getItemStyle(props.overscanStartIndex)
          setTop((style && style.top) || 0)

          // Call the original callback
          rest.onItemsRendered && rest.onItemsRendered(props)
        }}
        ref={el => (listRef.current = el)}
      >
        {row}
      </FixedSizeList>
    </VirtualTableContext.Provider>
  )
}

/** The Row component. This should be a table row, and noted that we don't use the style that regular `react-window` examples pass in.*/
function Row({ index }: { index: number }) {
  return (
    <tr>
      {/** Make sure your table rows are the same height as what you passed into the list... */}
      <td style={{ height: '36px' }}>Row {index}</td>
      <td>Col 2</td>
      <td>Col 3</td>
      <td>Col 4</td>
    </tr>
  )
}

/**
 * The Inner component of the virtual list. This is the "Magic".
 * Capture what would have been the top elements position and apply it to the table.
 * Other than that, render an optional header and footer.
 **/
const Inner = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
  function Inner({ children, ...rest }, ref) {
    const { header, footer, top } = useContext(VirtualTableContext)
    return (
      <div {...rest} ref={ref}>
        <table style={{ top, position: 'absolute', width: '100%' }}>
          {header}
          <tbody>{children}</tbody>
          {footer}
        </table>
      </div>
    )
  }
)

/**
 * Render Our Example
 **/
render(
  <VirtualTable
    height={300}
    width="100%"
    itemCount={1000}
    itemSize={36}
    header={
      <thead>
        <tr>
          <th>Index</th>
          <th>Header 2</th>
          <th>Header 3</th>
          <th>Header 4</th>
        </tr>
      </thead>
    }
    row={Row}
    footer={
      <tfoot>
        <tr>
          <td>Footer 1</td>
          <td>Footer 2</td>
          <td>Footer 3</td>
          <td>Footer 4</td>
        </tr>
      </tfoot>
    }
  />,
  document.querySelector('main')
)

@jamesmfriedman Люблю подход. Действительно интересная идея использования контекста для достижения этой цели. Мне нравится, что вы визуализируете только одну таблицу, в отличие от моего подхода, когда я визуализирую две, одну для заголовка и одну для тела. Это вызвало большую путаницу (не ожидал этого) у пользователей оконных таблиц.

Я постараюсь использовать эти идеи в оконном столе, если вы не против. Я думаю, что самой большой проблемой было бы выяснить, как автоматически изменять размер строк на основе содержимого (что было самой большой проблемой для windo-table, по крайней мере).

@jamesmfriedman, что выглядит очень многообещающим. @bvaughn есть ли у вас какие-либо опасения по поводу этого подхода?

@jamesmfriedman похоже, что заголовок прокручивается вместе с контентом. Можно ли сделать его липким?

На самом деле это возможно, а не только для данной реализации. Просто поищите в Google липкие заголовки с элементами таблицы HTML. Я смутно припоминаю, как это можно сделать - на самом деле сделать позицию элемента TH "липкой".

@jamesmfriedman большое спасибо за ваш пример 👍 это очень помогло @marink вот пример липких заголовков https://codesandbox.io/s/react-window-with-table-elements-jj70e вам нужно добавить style={{ position: 'sticky', top: 0 }}> в теги th

@jamesmfriedman Большое спасибо за пример. Я собираюсь изучить его использование. Но я думаю, что можно было бы использовать top: 0 и position sticky вместо пересчета top и иметь абсолютную позицию в элементе table. Потому что, если вы используете position sticky для получения липких заголовков на th элементах, тогда все будет испорчено при быстрой прокрутке.

Итак, строка 79 будет <table style={{ top: 0, position: 'sticky', width: '100%' }}>

Это также устраняет проблему прокрутки, возникающую при продолжении прокрутки, когда вы находитесь внизу.

Еще одна веская причина для использования HTML-таблиц - поддержка IE11. Я использую FixedSizeList а затем стилизую его с помощью CSS-сетки. Однако, когда вам нужно добавить другие функции, такие как адаптивные столбцы, столбцы разных размеров и т. Д., И заставить все это работать вместе с IE11, вы получите огромное количество кода. В то время как таблицы HTML по умолчанию работают без кода.

IE11 - это не то, что вам больше нужно поддерживать :)

Каждый разработчик FE желает смерти IE11. Но, к сожалению, есть компании / люди, которые не могут сдвинуться с мертвой точки по причинам, которые мы даже не можем себе представить. Надеюсь, это всего на несколько лет вперед. А пока нам нужно будет поддерживать IE11, нравится нам это или нет.

Мне тоже было трудно проглотить эту пилюлю.

@ olafur164 спасибо за ваше исправление, что липкие заголовки не ломаются при быстрой прокрутке. Знаете ли вы, есть ли способ сохранить "липкий" вид, поскольку теперь он прокручивает строку за раз, а не плавно?

Позвольте мне поделиться своим опытом с реакцией-виртуализацией (даже на странице проблем с реактивным окном ) и некоторой библиотекой пользовательского интерфейса материалов, называемой https://github.com/mbrn/ материал-таблица / issues / 891 # issuecomment -681986647. Есть еще некоторые проблемы, такие как проблемы со стилем и предупреждение validateDOMNesting(...) .

@jamesmfriedman большое спасибо за ваш пример 👍 это очень помогло @marink вот пример липких заголовков. вам нужно добавить style={{ position: 'sticky', top: 0 }}> в теги th

Привет, @JCofman , я попробовал исправить CSS, но у меня это не работает. Вот пример песочницы кода - https://codesandbox.io/s/react-window-with-table-elements-forked-huti6?file=/src/index.tsx : 514-542

Есть две проблемы -

  1. заголовок не прилипает к верхнему краю, несмотря на наличие стилей.
  2. заголовок мигает / мерцает при сильной прокрутке вверх.
    [Действия по воспроизведению - прокрутите вниз на 50%, затем быстро прокрутите вверх и наблюдайте за движением заголовка].

Заранее спасибо :)

@jamesmfriedman Люблю подход. Действительно интересная идея использования контекста для достижения этой цели. Мне нравится, что вы визуализируете только одну таблицу, в отличие от моего подхода, когда я визуализирую две, одну для заголовка и одну для тела. Это вызвало большую путаницу (не ожидал этого) у пользователей оконных таблиц.

Я постараюсь использовать эти идеи в оконном столе, если вы не против. Я думаю, что самой большой проблемой было бы выяснить, как автоматически изменять размер строк на основе содержимого (что было самой большой проблемой для windo-table, по крайней мере).

В итоге я нашел очень похожее решение, потому что я использую display: grid с display: contents для строк, поэтому в основном придерживаюсь ограничения table > tr elements.

Вы использовали context + refs для вызова внутреннего метода, однако в этом нет необходимости, поскольку то же самое можно сделать, просмотрев значение style.top первого дочернего элемента:

const Inner = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
  function Inner({ children, ...rest }, ref) {
    const { header, footer } = useContext(VirtualTableContext)
    const {style} = children[0].props;
    return (
      <div {...rest} ref={ref}>
        <table style={style}>
          {header}
          <tbody>{children}</tbody>
          {footer}
        </table>
      </div>
    )
  }
)

Опора стиля задается библиотекой, когда она выполняет createElement для каждой строки, и поскольку ваш конкретный компонент Row не назначает style tr , вы не передавая их тоже вниз.

Недавно я задумался о чем-то подобном. Что, если бы мы переместили все тело таблицы с заполнением, используя псевдоэлементы (после / до) через настраиваемые свойства css 🤔

В примере с использованием реакции-виртуального https://codesandbox.io/s/poc-react-virtual-table-jyz0m

Мне абсолютно нужна таблица, чтобы я мог достичь желаемого UX, и меня не устраивали обходные пути, чтобы заставить ее работать с react-window .

В итоге я просто извлек логику вирутализации на крючок и реализовал ее с двумя таблицами, одна для заголовков, а вторая для содержимого, как и то, что было опубликовано выше. Разница в моем подходе заключается в том, что заголовки второй таблицы все еще присутствуют в разметке, но визуально скрыты, поэтому она остается доступной, тогда как заголовки первой таблицы предназначены только для зрячих пользователей и позиционно закреплены. Работало как шарм, и код был значительно проще.

Просто выбросить его там, на случай, если кто-то захочет это попробовать.

@piecyk Это отличный https://virtuoso.dev/grouped-by-first-letter/

Я должен добавить, что позиционирование всего контейнера с помощью absolute / top является эффективным, но при выполнении аудита производительности с Chrome я вижу проблемы CLS, которые могут повлиять на FPS. Я не думаю, что влияние заметно, вы можете увидеть, насколько быстрым и плавным оно может быть на этом примере: https://b6udg.csb.app/ (хотя это контейнер большого размера). Однако с padding-top / bottom CLS НЕТ!

Я попробую сделать это сам и скоро измерим разницу.

@pupudu Я приветствую ваши усилия по открытому исходному

Оказывается, мне удалось добиться этого с помощью очень небольшого фрагмента кода и большого творчества. Код достаточно прост, чтобы любой мог копировать / вставлять и расширять его по своему усмотрению. @bvaughn, выяснив, как это сделать, было бы довольно тривиально добавить в библиотеку, если хотите, но также довольно легко переопределить, если вы знаете, как.

_Кратко об этом_:

  • Захватить "верхний" стиль первой строки после рендеринга.
  • Сохраните это значение в React.Context, чтобы мы могли передавать его.
  • Примените значение к компоненту таблицы, который будет перемещаться, а не к самим строкам
  • Добавьте дополнительные слоты для таких вещей, как верхние и нижние колонтитулы.
  • Эргономика библиотеки не позволяет аккуратно передавать вещи, поэтому React.Context - это герой, который преодолевает межкомпонентную коммуникацию.

Песочница рабочего кода: https://codesandbox.io/s/react-window-with-table-elements-d861o

Код для справки

import React from 'react'
import { useState, useRef, useContext } from 'react'
import { FixedSizeList, FixedSizeListProps } from 'react-window'
import { render } from 'react-dom'

/** Context for cross component communication */
const VirtualTableContext = React.createContext<{
  top: number
  setTop: (top: number) => void
  header: React.ReactNode
  footer: React.ReactNode
}>({
  top: 0,
  setTop: (value: number) => {},
  header: <></>,
  footer: <></>,
})

/** The virtual table. It basically accepts all of the same params as the original FixedSizeList.*/
function VirtualTable({
  row,
  header,
  footer,
  ...rest
}: {
  header?: React.ReactNode
  footer?: React.ReactNode
  row: FixedSizeListProps['children']
} & Omit<FixedSizeListProps, 'children' | 'innerElementType'>) {
  const listRef = useRef<FixedSizeList | null>()
  const [top, setTop] = useState(0)

  return (
    <VirtualTableContext.Provider value={{ top, setTop, header, footer }}>
      <FixedSizeList
        {...rest}
        innerElementType={Inner}
        onItemsRendered={props => {
          const style =
            listRef.current &&
            // @ts-ignore private method access
            listRef.current._getItemStyle(props.overscanStartIndex)
          setTop((style && style.top) || 0)

          // Call the original callback
          rest.onItemsRendered && rest.onItemsRendered(props)
        }}
        ref={el => (listRef.current = el)}
      >
        {row}
      </FixedSizeList>
    </VirtualTableContext.Provider>
  )
}

/** The Row component. This should be a table row, and noted that we don't use the style that regular `react-window` examples pass in.*/
function Row({ index }: { index: number }) {
  return (
    <tr>
      {/** Make sure your table rows are the same height as what you passed into the list... */}
      <td style={{ height: '36px' }}>Row {index}</td>
      <td>Col 2</td>
      <td>Col 3</td>
      <td>Col 4</td>
    </tr>
  )
}

/**
 * The Inner component of the virtual list. This is the "Magic".
 * Capture what would have been the top elements position and apply it to the table.
 * Other than that, render an optional header and footer.
 **/
const Inner = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
  function Inner({ children, ...rest }, ref) {
    const { header, footer, top } = useContext(VirtualTableContext)
    return (
      <div {...rest} ref={ref}>
        <table style={{ top, position: 'absolute', width: '100%' }}>
          {header}
          <tbody>{children}</tbody>
          {footer}
        </table>
      </div>
    )
  }
)

/**
 * Render Our Example
 **/
render(
  <VirtualTable
    height={300}
    width="100%"
    itemCount={1000}
    itemSize={36}
    header={
      <thead>
        <tr>
          <th>Index</th>
          <th>Header 2</th>
          <th>Header 3</th>
          <th>Header 4</th>
        </tr>
      </thead>
    }
    row={Row}
    footer={
      <tfoot>
        <tr>
          <td>Footer 1</td>
          <td>Footer 2</td>
          <td>Footer 3</td>
          <td>Footer 4</td>
        </tr>
      </tfoot>
    }
  />,
  document.querySelector('main')
)

Спасибо за это, пробовал ли кто-нибудь (с успехом) использовать это с компонентом InfiniteLoader ? ... Я пробовал пинать его, но у меня проблемы с внутренней "оболочкой", которую я Предположим, потому что бесконечный загрузчик передает неожиданные реквизиты в innerElementType.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги