Я хотел бы создать свой собственный просмотрщик последовательностей ДНК в приложении 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>
)
Есть ли у вас идеи, как это сделать?
Спасибо





Я поставил перед собой задачу создать этот секвенсор, но оказалось, что никакая оптимизация в чистом 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 есть ограничения. А еще браузер
Вы внедрили виртуализацию?
Сейчас это работает, я плохо реализовал реагирующие окна. Большое спасибо!
пожалуйста, пометьте ответ как принятый
покажите, как используется
renderColoredText. У меня есть подозрение, что при каждом рендеринге все это размонтируется и перемонтируется.