Как лучше реализовать поиск по большому объему данных ? (REACT/NEXTJS)
Всем привет ) Сейчас занимаюсь реализацией сайта словаря, где около 2000 слов. И мне необходимо реализовать поиск. Функционал - Пользователь печатает слово в поисковик и сразу без нажатие на какие либо кнопки ему подтягиваться поисковая выдача. Проблема - я решил не нагружать сервер и получить по api все слова сразу и с помощью модуля fuse.js искать эти слова. Но к сожалению из за того что это слишком большой объем данных, сайт начинает логать то есть ввод слов в input происходит с задержкой так как при каждой вбитой букве в начале input выходит результат около 800 - 1000 слов. При уменьшение порогового значение в опциях fuse.js на совпадение лаги пропадают. Но к сожалению такой алгоритм поиска нам не походит так как пользователь может ошибиться или изменить окончание.
Можно было бы начинать поиск только при нажатии на какую нибудь кнопку, но заказчику это не подходит.
Может быт у кого нибудь был опыт с подобными проектами ? Или вы знаете как можно решить данную проблему ?
Спасибо
//Search----------------------------------------------
import regexifyString from 'regexify-string';
import { useRouter } from 'next/router';
import Link from 'next/link';
import React, { useEffect, useMemo, useState } from 'react';
import { getAll } from '@/http/catalog/search/search';
import Fuse from 'fuse.js';
import SearchList from './SearchList';
export default function Search({ className = "", setListWords}) {
const { locale } = useRouter();
const [ list, setList ] = useState([]);
const [ listSearch, setListSearch ] = useState([]);
const [ word, setWord ] = useState('');
const localContent = require('@/locales/catalog/locales')[locale];
useEffect(() => {
getAll()
.then(res => {
setList(res.data);
console.log(res)
})
.catch(err => {
console.error(err);
});
}, []);
function clearSearch() {
setListSearch([]);
setWord("");
}
function addWord({ word, id }) {
clearSearch();
let result = JSON.parse(localStorage.getItem('words-dictionary')) || [];
let valid = true;
for(let item of result) {
if(item.id == id) {
valid = false;
break;
}
}
if(valid) {
localStorage.setItem('words-dictionary', JSON.stringify([...result, {id, word}]));
}
setListWords(JSON.parse(localStorage.getItem('words-dictionary')) || []);
}
function onSearch(e) {
setWord(e.target.value);
const options = {
includeScore: true,
threshold: 0.4,
keys: ['word', 'description']
}
const fuse = new Fuse(list, options);
const result = fuse.search(e.target.value);
setListSearch(result);
}
return (
<>
<div className={'search-btn d-flex align-items-center flex-column ' + className}>
<div className="search-btn__button w-100">
<svg className="search-btn__icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 48"><path className="st0" d="M4,22.1C4,16.6,8.4,12.1,13.9,12s10.1,4.4,10.1,9.9c0.1,5.5-4.4,10.1-9.9,10.1S4.1,27.6,4,22.1z M22.5,21.9c-0.1-4.7-3.9-8.5-8.6-8.4s-8.5,3.9-8.4,8.6s3.9,8.5,8.6,8.4S22.6,26.6,22.5,21.9z"/><path className="st0" d="M0.1,35.5c0-0.2,0-0.4,0.2-0.6L7,28.1c0.3-0.3,0.7-0.3,1,0c0.3,0.3,0.3,0.7,0,1l-6.7,6.8c-0.3,0.3-0.7,0.3-1,0C0.2,35.9,0.1,35.7,0.1,35.5z"/>
</svg>
<input className="search-btn__search w-100 rounded-0" value={word} type="text" onChange={(e) => onSearch(e)} placeholder={localContent.searchBtn} />
</div>
<SearchList list={listSearch} addWord={addWord}/>
</div>
</>
)
}
// SearchList----------------------------------------------
export default function SearchList({ list = [], addWord }) {
return (
<>
{ list.length ?
<div className="search-btn__list search-list card-2 rounded-0">
{
list.map(({ item }, index) => {
return (
<div className={list.length - 1 !== index ? 'mb-2-1 search-item' : 'search-item'} key={item.id}>
<div className="search-item__word animate-hover" type="button" onClick={() => addWord({ word: item.word, id: item.id })}>{item.word} — </div>
<div className="search-item__description" dangerouslySetInnerHTML= {{ __html: item.description}}></div>
{ list.length - 1 !== index &&
<div className="line mt-2-1"></div>
}
</div>
)
})
}
</div>
: null
}
</>
)
}
Ответы (2 шт):
можно воспользоваться уже готовыми инструментами, например ElasticSearch который поможет решить вашу проблему.
Пользователь печатает слово в поисковик и сразу без нажатие на какие либо кнопки ему подтягиваться поисковая выдача
Также ElasticSearch может решить данную проблему. Ссылка на статью
Прочитал внимательно формулировку задачи.
Смотрите, лаги у Вас происходят именно из за "бездумного" использования фреймворка.
Наверняка этот fuse строит внутри себя какую то охренительную структуру данных, еще и с DOM поди интегрированную.
Напишите это на чистом JS с использованием одного текстбокса и обработкой печати символа в этом чекбоксе. Это уровень... ну, в общем, начальный.
Думаю, при этом лаги исчезнут, а программа будет выполняться на любом процессоре любой архитектуры с любым количеством памяти.
Ну, и наконец, посчитаем немного: пускай у нас длинные слова, до 50 букв, то есть могут занимать до 100 байт. Пускай это даже слова с переводами - дадим на перевод каждого слова еще 100 байт. В сумме получится... (100+100)*2000 = 400 000, то есть 400 КИЛОбайт. Средний размер фоточки котика, котрую сейчас снимает телефон - примерно в 10 раз больше.
Так что - нужно лишь написать все на JS, и всё залетает!
А, еще я Вам расскажу грязный хак, который позволяет немного сэкономить запросы.
Вот есть у нас текстбокс. Пользователь напечатал там символ, в результате изменился фильтр, по которому надо искать... Вот как только пользователь его напечатал - не надо ничего искать. Надо зупустить таймер на какую то маленькую величину, например, на 1/10 секунды. Если за это время пользователь напечатает еще один символ - то в фильтре уже 2 символа поменялись... В общем, маленькая пауза в ожидании "а не напечатает ли этот тормоз еще что то прямо сейчас?" спасёт Вас от лишних поисков.