Как добавить простую функцию поиска в приложение на React без сервера



Книга Как добавить простую функцию поиска в приложение на React без сервера

Функция поиска  —  одна из самых важных особенностей программного приложения. Поисковые сайты типа Google и DuckDuckGo помогают миллионам пользователей бороздить просторы Интернета и за считанные секунды находить то, что они ищут.


Было бы неплохо включить функцию поиска в приложение на React. Но как быть, если не хочется настраивать отдельный сервер исключительно для работы с поиском. Есть ли способ добавить простой поиск в приложение на React?


Да, такой способ существует. Fuse.js  —  это мощная, легковесная поисковая библиотека, предназначенная для ведения поиска во внешней части приложения. Библиотека использует метод нечёткого поиска, который находит не только точные совпадения, но и строки, приблизительно соответствующие заданному шаблону.


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


Что будем создавать


Это руководство поможет вам создать с помощью Fuse.js простое приложение на React с собственной функцией поиска. Приложение будет предоставлять подборку книг для разработчиков программного обеспечения с возможностью применения фильтра функции поиска.


Вот как будет выглядеть итоговый результат:


Мои любимые книги. Отлично подойдут и для разработчиков программного обеспечения

Демоверсия проекта, а также его компоненты выложены на Bit.dev. Изучите её и установите эти компоненты в свой проект.




Первым делом необходимо выполнить начальную загрузку нового приложения на React с помощью Create React App:


npx create-react-app react-fusejs-example

Завершив загрузку, начнём прописывать данные книг в виде массива объектов. Эти данные будут передаваться в приложение и отображаться в виде списка карточек:


Создание списка книг

Создадим новый файл books.json и список книг. Данные будут иметь следующие свойства:


  • Строка title для названий книг.
  • Строка author для автора.
  • Строка image для атрибута src изображения книги.
  • url-адрес для ссылки на покупку или просмотр книги.

Вот файл на случай, если захотите его скопировать:


[
{
"title": "Steve Jobs",
"image": "https://images-na.ssl-images-amazon.com/images/I/41dKkez-1rL._SX326_BO1,204,203,200_.jpg",
"author": "Walter Isaacson",
"url": "https://www.amazon.com/Steve-Jobs-Walter-Isaacson/dp/1451648537"
},
{
"title": "Zero to One",
"image": "https://images-na.ssl-images-amazon.com/images/I/4137OkbPQ4L._SX331_BO1,204,203,200_.jpg",
"author": "Peter Thiel, Blake Masters",
"url": "https://www.amazon.com/Zero-One-Notes-Startups-Future/dp/0804139296"
},
{
"title": "The Pragmatic Programmer",
"image": "https://images-na.ssl-images-amazon.com/images/I/51cUVaBWZzL._SX380_BO1,204,203,200_.jpg",
"author": "David Thomas, Andrew Hunt",
"url": "https://www.amazon.com/Pragmatic-Programmer-journey-mastery-Anniversary/dp/0135957052"
},
{
"title": "The Unicorn Project",
"image": "https://images-na.ssl-images-amazon.com/images/I/51A4T36jisL._SX334_BO1,204,203,200_.jpg",
"author": "Gene Kim",
"url": "https://www.amazon.com/Unicorn-Project-Developers-Disruption-Thriving/dp/1942788762"
},
{
"title": "The Passionate Programmer",
"image": "https://images-na.ssl-images-amazon.com/images/I/51m3yzmDFCL._SX331_BO1,204,203,200_.jpg",
"author": "Chad Fowler",
"url": "https://www.amazon.com/Passionate-Programmer-Remarkable-Development-Pragmatic-ebook/dp/B00AYQNR5U"
},
{
"title": "Hatching Twitter",
"image": "https://m.media-amazon.com/images/I/51YUkI5ZQ-L.jpg",
"author": "Nick Bilton",
"url": "https://www.amazon.com/Hatching-Twitter-Story-Friendship-Betrayal-ebook/dp/B00CDUVSQ0"
},
{
"title": "How Google Works",
"image": "https://images-na.ssl-images-amazon.com/images/I/31Xc+yFta0L._SX327_BO1,204,203,200_.jpg",
"author": "Eric Schmidt, Jonathan Rosenberg",
"url": "https://www.amazon.com/How-Google-Works-Eric-Schmidt/dp/1455582328"
},
{
"title": "Elon Musk",
"image": "https://m.media-amazon.com/images/I/51tw6UjHpDL.jpg",
"author": "Ashlee Vance",
"url": "https://www.amazon.com/Elon-Musk-SpaceX-Fantastic-Future-ebook/dp/B00KVI76ZS"
},
{
"title": "Six Easy Pieces",
"image": "https://m.media-amazon.com/images/I/51E53HCUKVL.jpg",
"author": "Richard P. Feynman",
"url": "https://www.amazon.com/Six-Easy-Pieces-Essentials-Explained-ebook/dp/B004OVEYNU"
},
{
"title": "Sapiens",
"image": "https://m.media-amazon.com/images/I/51Sn8PEXwcL.jpg",
"author": "Yuval Noah Harari",
"url": "https://www.amazon.com/Sapiens-Humankind-Yuval-Noah-Harari-ebook/dp/B00ICN066A"
}
]

Теперь, когда есть данные о книгах, нужен компонент, который будет принимать эти данные и отображать их в виде простой карточки в приложении на React:


Компонент из одной карточки

Создание компонента «Card»


У компонента Card будет четыре свойства для обработки передаваемых в него данных:


<Card  
image="https://images-na.ssl-images-amazon.com/images/I/41dKkez-1rL._SX326_BO1,204,203,200_.jpg"
title="Steve Jobs"
author="Walter Isaacson"
url="https://www.amazon.com/Steve-Jobs-Walter-Isaacson/dp/1451648537"
/>

Вот JSX-структура компонента:


const Card = ({image, title, author, url}) => {
return (
<div className="CardWrapper">
<div className="ColImg">
<img className="Img" src={image} alt={title} />
</div>
<div className="ColDetail">
<div className="Header">
<div className="BookTitle">{title}</div>
</div>
<div className="Description">{author}</div>
<a className="Link" href={url}>
Learn more
</a>
</div>
</div>
);
};

.BookTitle {
font-size: 20px;
}

.Description {
color: #757575;
font-size: 14px;
margin-bottom:10px;
}

.Link {
font-size: 14px;
}

А вот и стилевое оформление компонента:


.CardWrapper {
flex: 0 1 300px;
margin: 12px;
overflow: hidden;
padding: 16px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05),
0 0px 10px rgba(0, 0, 0, 0.08);
border-radius: 5px;
flex-wrap: wrap;
min-width: 300px;
}.

Header {
width: 100%;
display: flex;
margin-bottom: 10px;
}.

ColImg {
width: 30%;
float: left;
}

.ColDetail {
width: 70%;
float: left;
}

.Img {
height: 100px;
}

Можете просто загрузить компонент из этих коллекций на Bit:


npm install @bit/nsebhastian.react_fusejs.card

Затем просто импортируем компонент в файл App.js. Нужно лишь применить map() к массиву JSON в компоненте Card. Добавим <div> с именем класса Container, чтобы улучшить интерфейс:


import React from "react";
import "./App.css";
import books from "./books.json";
import Card from "@bit/nsebhastian.react_fusejs.card";function App() {
return (
<div className="Container">
{books.map((item) => (
<Card {...item} key={item.name} />
))}
</div>
);
}export default App;

Вот CSS для класса Container:


.Container {
width: 80%;
margin: 0 auto;
margin-top: 45px;
display: flex;
flex-wrap: wrap;
}

Список карточек завершён. Перейдём теперь к созданию компонента поиска Search.



Компонент Search состоит всего лишь из одного текстового ввода с двумя свойствами:


  • placeholder для замещающего текста в поле ввода;
  • функция onChange, которая запускается, когда пользователь вводит что-то в поле ввода.

Напишем JSX-элемент для этого компонента:


const SearchBar = ({onChange, placeholder}) => {
return (
<div className="Search">
<span className="SearchSpan">
<FaSearch />
</span>
<input
className="SearchInput"
type="text"
onChange={onChange}
placeholder={placeholder}
/>
</div>
);
};

Затем напишем для него CSS:


.Search {
width: 400px;
margin: 0 auto;
position: relative;
display: flex;
}

.SearchSpan {
width: 15%;
border: 1px solid #1C76D2;
background: #1C76D2;
padding-top: 4px;
text-align: center;
color: #fff;
border-radius: 5px 0 0 5px;
font-size: 20px;
}

.SearchInput {
width: 85%;
border: 3px solid #1C76D2;
border-left: none;
padding: 5px;
border-radius: 0 5px 5px 0;
outline: none;
}

Воспользуемся этим компонентом, выложенным на Bit. Установим его с помощью NPM:


npm install @bit/nsebhastian.react_fusejs.search-bar

Затем импортируем компонент в файл App.js. Поместим компонент <SearchBar> и остальную часть кода в один элемент <div>:


import React from "react";
import "./App.css";
import books from "./books.json";
import Card from "@bit/nsebhastian.react_fusejs.card";
import SearchBar from "@bit/nsebhastian.react_fusejs.search-bar";function App() {
return (
<div>
<h1 className="Title">My Favorite books</h1>
<SearchBar
placeholder="Search"
onChange={(e) => console.log(e.target.value)}
/> <div className="Container">
{books.map((item) => (
<Card {...item} key={item.name} />
))}
</div>
</div>
);
}

Добавим единый CSS в App.css для выравнивания по центру элемента <h1> с классом Title:


.Title {
text-align: center;
}

Теперь все компоненты для этого демоприложения готовы. Остаётся только интегрировать Fuse.js в приложение на React.


Интеграция Fuse.js в приложение на React


Для начала интеграции Fuse.js в приложение на React нужно выполнить в проекте NPM-установку соответствующего пакета:


npm install fuse.js

Затем нужно импортировать библиотеку и создать новый экземпляр Fuse.js. Экземпляр принимает два задаваемых параметра:


  • data для выполнения поиска. Это может быть строковый или объектный массив.
  • Дополнительные опции для изменения поискового поведения.

Это означает, что в качестве первого аргумента нужно передать массив books. А второго  —  массив keys, содержащий title и author. Так функция поиска не будет искать строки image и url.


import Fuse from "fuse.js";

const fuse = new Fuse(books, {
keys: ["title", "author"],
});

Далее нужно вызвать функцию fuse.search() с любым поисковым шаблоном. Поиск вернёт список совпадений, и снова в виде массива:


const matches = fuse.search("Elon Musk")

// возвращает [{ item: object }]

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


  • когда пользователь вводит что-то в строку поиска, приложение выполняет поиск по массиву;
  • когда есть какое-то совпадение, приложение возвращает только те книги, которые соответствуют поисковому шаблону;
  • когда совпадений нет, приложение возвращает пустой экран;
  • когда пользователь очищает строку поиска, приложение показывает все данные без применения фильтра.

Для достижения такого поведения нужно поместить массив books в состояние React:


const [data, setData] = useState(books);

Когда пользователь вводит что-то в строку поиска, должна запускаться функция, которая выполнит поиск. Назовём эту функцию searchData и передадим в неё входное значение:


<SearchBar
placeholder="Search"
onChange={(e) => searchData(e.target.value)}
/>

Пора создать функцию. Первым делом проверим поисковый шаблон, переданный в функцию. Если он пуст, поместим состояние обратно в массив books и завершим выполнение функции с помощью оператора return:


const searchData = (pattern) => {
if (!pattern) {
setData(books);
return;
}

Затем выполним поиск по заданному шаблону:


const fuse = new Fuse(data, {
keys: ["title", "author"],
});

const result = fuse.search(pattern);

Создадим новый массив matches для хранения любого совпадения, возвращаемого функцией Fuse. Если результат пустой, данные устанавливаются в виде пустого массива:


const matches = [];
if (!result.length) {
setData([]);
} else {
result.forEach(({item}) => {
matches.push(item);
});
setData(matches);
}

Если result непустой, вызываем функцию forEach для итеративного обхода по result и добавляем каждый элемент в массив. Затем обновим состояние с помощью массива matches:


if (!result.length) {
setData([]);
} else {
result.forEach(({item}) => {
matches.push(item);
});
setData(matches);
}

И вот приложение готово. Если вы пропустили какой-то этап, сравните свой код с демоверсией.


Теперь вы можете найти книгу в своём списке любимых книг. Красота!


Заключение


Каким бы ни было ваше приложение, важно помочь его пользователям найти то, что они ищут. Fuse.js  —  это простая в использовании поисковая библиотека, не требующая никакой настройки бэкенда, а её метод нечёткого поиска использует функцию поиска с защитой от ошибок.


Спасибо за внимание!


632   0  

Comments

    Ничего не найдено.