Реагируйте: создайте средство просмотра ДНК для очень длинной последовательности

Я хотел бы создать свой собственный просмотрщик последовательностей ДНК в приложении React TS (чтобы учиться и практиковаться в кодировании). Я получаю последовательность с моего сервера Flask в виде длинной строки, очень хорошо. но если последовательность очень большая (более 4 миллионов), мое приложение вылетает. Потому что это не просто отображение текста, а еще и правило под буквой и присвоение каждой букве (A,C,G или T) разного цвета - правило является обязательным, отображения разных цветов нет, если это делает приложение для замедления - (см. картинку)

,

Мой текстовый код рендеринга:

const renderColoredText = (text: string) => {
       return text.split('').map((char, index) => {
         let color = 'black';
   
         switch (char.toLowerCase()) {
           case 'a':
             color = 'primary';
             break;
           case 'b':
             color = 'secondary';
             break;
           case 'c':
             color = 'warning';
             break;
           default:
             break;
         }

         const borderBottom = index % 10 === 9 ? '3px solid lightblue' : '1px solid lightblue'
         const padding = '5px'

       return (
           <Box
               key = {index}
               sx = {{
               display: 'inline-block',
               borderBottom,
               // color: getNucleotideColor(nucleotide),
               paddingTop: '2px',
               position: 'relative',
               marginBottom: '20px', 
               }}
           >
           {char}
           {
               index % 10 === 9 && (
           <Box
               sx = {{
               position: 'absolute',
               top: '110%', // Below the nucleotide
               left: '50%',
               transform: 'translateX(-50%)',
               fontSize: '12px',
               color: 'black',
               }}
           >
               {index + 1}
           </Box>
               )}
       </Box>

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

Мой интервальный код:

const chunkSize = 10000; 
   const updateInterval = 1000; 


   useEffect(() => {
       const currentUser = getUserToken()
       if (!currentUser || !currentUser._id) 
       return;

       getWorkspaceInput(currentUser._id)
       .then(data => {
           let currentIndex = 0;

           
           const intervalId = setInterval(() => {
               const nextChunk = data.input.slice(currentIndex, currentIndex + chunkSize);
               setSequence(prevSequence => prevSequence + nextChunk);
               currentIndex += chunkSize;
                   if (currentIndex >= data.input.length) {
                   clearInterval(intervalId);
               }
           }, updateInterval);
   
           return () => {
               clearInterval(intervalId);
           };
       })


   },[])

Рендер:

 const [sequence, setSequence] = useState('')

 return (
       <MainContainer title = {'Sequence Viewer'} >
           <Box sx = {{ maxWidth: '100%' }}>
               <Typography 
                   sx = {{ 
                       wordWrap: 'break-word',
                       letterSpacing: '2px',
                       paddingTop:'1.5rem'
                       }}
                   >{renderColoredText(sequence)}
               </Typography>
           </Box>         
       </MainContainer>
      
   )

Есть ли у вас идеи, как это сделать?

Спасибо

покажите, как используется renderColoredText. У меня есть подозрение, что при каждом рендеринге все это размонтируется и перемонтируется.

Iorweth333 10.06.2024 15:13
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
1
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я поставил перед собой задачу создать этот секвенсор, но оказалось, что никакая оптимизация в чистом React не поможет. Даже если заключить каждую букву в простой span, это уже слишком, если длина последовательности составляет 5 000 000.

Единственное, что, как я знаю, может помочь, это виртуализация. Эту идею можно сузить до: давайте визуализируем только то, что видно в данный момент, а остальное отключим.

К сожалению, он имеет множество ограничений, в основном связанных с возможностью организации контента в список строк и знанием размеров каждой строки.

Найдите «React virtualization» в Google, чтобы узнать, как это сделать. Хотя переписать онлайн-руководство в этом SO-ответе — слишком многого, я приведу несколько примеров кода, которые, по моему мнению, будут полезны.

Я бы не рекомендовал разбивать всю последовательность на список. Советую везде передавать всю последовательность вместе с beginIndex и endIndex и использовать substring. Таким образом, вы сможете создать компонент, который извлекает только ту часть, которая ему нужна:

const CHUNK_SIZE = 10;

const ROW_HEIGHT = 50;

const ROW_WIDTH = 1100;

function DnaSequenceRow(props) {
  const row = React.useMemo(() => {
    return props.sequence.substring(props.beginIndex, props.endIndex);
  }, [props.beginIndex, props.endIndex, props.sequence]);

  const chunksOfTen = React.useMemo(() => divideIntoSubsequences(row, CHUNK_SIZE),
    [row],
  );

  return (
    <div style = {{
      //size probably has to be arbitrarily set for the sake of virtualization
      height: ROW_HEIGHT,
      width:  ROW_WIDTH,
    }}
    >
      {chunksOfTen.map((ten, indexWithinRow) => (
        <SequenceOfTen
          key = {indexWithinRow}
          sequenceOfTen = {ten}
          index = {props.beginIndex + (indexWithinRow * CHUNK_SIZE)}/>
      ))}
    </div>
  );
}

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

Для удобства чтения я также выделил кусок из десяти нуклеотидов в отдельный компонент:

function SequenceOfTen(props) {
  return (
    <>
      <Box
        sx = {{
          display:      'inline-block',
          borderBottom: '3px solid lightblue',
          // color: getNucleotideColor(nucleotide),
          paddingTop:   '2px',
          position:     'relative',
          marginBottom: '20px',
        }}
      >
        {props.sequenceOfTen[0]}
        <Box
          sx = {{
            position:  'absolute',
            top:       '110%', // Below the nucleotide
            left:      '50%',
            transform: 'translateX(-50%)',
            fontSize:  '12px',
            color:     'black',
          }}
        >
          {props.index}
        </Box>
      </Box>
      <Box
        sx = {{
          display:      'inline-block',
          borderBottom: '1px solid lightblue',
          // color: getNucleotideColor(nucleotide),
          paddingTop:   '2px',
          position:     'relative',
          marginBottom: '20px',
        }}
      >
        {props.sequenceOfTen.substring(1)}
      </Box>
    </>
  );
}

Я использовал useMemo, чтобы избежать пересчета строк и фрагментов при каждом рендеринге, и обернул компоненты React.memo, чтобы избежать ненужных повторных рендерингов. Разделение на куски основано на алгоритме, который я нашел здесь: https://stackoverflow.com/a/29202760/12003949

export function divideIntoSubsequences(sequence, subsequenceLength) {
  const numberOfSubsequences = Math.ceil(sequence.length / subsequenceLength);
  const subsequences         = new Array(numberOfSubsequences);
  for (let i = 0, o = 0; i < numberOfSubsequences; ++i, o += subsequenceLength) {
    subsequences[i] = sequence.substring(o, o + subsequenceLength);
  }
  return subsequences;
}

Оберните вокруг этого какую-нибудь библиотеку виртуализации, и она должна работать как шарм. Удачи!

Так лучше, спасибо! Но все же, когда я имею дело с последовательностями из миллионов символов, это работает не очень хорошо. Но, как вы сказали, я думаю, что у React есть ограничения. А еще браузер

DMN 23.06.2024 10:11

Вы внедрили виртуализацию?

Iorweth333 23.06.2024 12:11

Сейчас это работает, я плохо реализовал реагирующие окна. Большое спасибо!

DMN 25.06.2024 07:39

пожалуйста, пометьте ответ как принятый

Iorweth333 25.06.2024 09:26

Другие вопросы по теме