Як зробити пошук користувачів з GitHub без React + RxJS 6 + Recompose

Ця стаття є відповіддю на статтю-переклад «Як зробити пошук користувачів з GitHub використовуючи React + RxJS 6 + Recompose», яка буквально вчора навчила нас як треба використовувати React, RxJS і Recompose разом. Що ж, тепер пропоную подивитися, як це можна реалізувати без оних інструментів.

Disclaimer Багатьом може здатися, що дана стаття містить елементи троллінгу, написана похапцем і по фану… Так от, це так.

Так як це відповідна стаття, я вирішив побудувати її в тому ж покроковому форматі, що і оригінал. Крім того, стаття також покликана порівняти оригінальну реалізацію з описаною тут. Тому в ній присутня велика кількість посилань і цитат з оригіналу. Приступимо.

Ця стаття розрахована на людей, які мають досвід роботи з React і RxJS. Я лише ділюся шаблонами, які я вважав корисними для створення такого UI.
Ця стаття розрахована на людей, які мають досвід роботи з Javascript (ES6), HTML і CSS. Крім того, у своїй реалізації я буду використовувати «зникаючий» фреймворк SvelteJS, але він настільки простий, що вам не обов’язково мати досвід його використання, щоб розуміти код.

Робимо ми все ту ж штуку:

Без класів, роботи з життєвим циклом або setState.
Так, без класів, роботи з життєвим циклом або setState. А також без React, ReactDOM, RxJS, Recompose. І крім того, без componentFromStream, createEventHandler, combineLatest, map, startWith, setObservableConfig, BehaviorSubject, merge, of, catchError, delay, filter, map, pluck, switchMap, tap, {another bullshit}… Коротше ви зрозуміли.

Підготовка

Все що потрібно лежить в моєму REPL прикладі на сайті SvelteJS. Можете gjukzyenb там, або локально, завантаживши ісходники звідти (кнопка з характерною іконкою).

Для початку створимо простий файлик App.html, який буде root-компонентом нашого віджета, з наступним змістом:

<input placeholder="GitHub username">

<style>
 input {
 font-size: 20px;
 border: 1px solid black;
 border-radius: 3px;
 margin-bottom: 10px;
 padding: 10px;
}
</style>

Тут і далі, я використовую стилі з оригінальної статті. Зверніть увагу, що вже прямо зараз вони в scope, тобто застосовуються тільки до даного компоненту і можна сміливо використовувати імена тегів там де це актуально.

Стилі писати буду прямо в компонентах, тому що SFC, а також тому, що REPL не підтримує винос CSS/JS/HTML в різні файли, хоча це легко робиться за допомогою препроцесорів Svelte.

Читайте також  Як зробити пошук користувачів з Github використовуючи Angular

Recompose

Відпочиваємо…

Поточний компонент

… загоряємо…

Конфігурування

… п’ємо каву…

Recompose + RxJS

… поки інші…

Map

… працюють.

Додаємо обробник подій

Не зовсім обробник звичайно, просто біндінг:

<input bind:value=username placeholder="GitHub username">

Ну і визначимо значення username за замовчуванням:

<script>
 export default {
 data() {
 return {
 username: "
};
}
};
</script>

Тепер, якщо ви почнете вводити що-то в полі введення, значення username буде змінюватися.

Проблема яйця і курки

Ні курей, ні яєць, ні проблем з іншими тваринами, ми ж не RxJS використовуємо.

Пов’язуємо разом

Всі вже реактивно і пов’язано. Так що далі п’ємо каву.

Компонент User

Цей компонент у нас буде відповідати за відображення користувача, ім’я якого ми йому передавати. Він буде отримувати value з компонента App і переводити його в AJAX запит.
«Воу-воу-воу, легше» ©

У нас цей компонент буде тупим і просто відображати красиву картку юзера по заздалегідь відомої моделі. Мало звідки можуть приходити дані та/або в якомусь місці інтерфейсу ми захочемо показати цю картку.

Приблизно так буде виглядати компонент User.html:

<div class="github-card user-card">
 <div class="header User" />
 <a class="avatar" href="https://github.com/{login}">
 <img src="{avatar_url}&s=80" alt={name}>
</a>
 <div class="content">
 <h1>{name || login}</h1>
 <ul class="status">
<li>
 <a href="https://github.com/{login}?tab=repositories">
<strong>{public_repos}</strong>Repos
</a>
</li>
<li>
 <a href="https://gist.github.com/{login}">
<strong>{public_gists}</strong>Gists
</a>
</li>
<li>
 <a href="https://github.com/{login}/followers">
<strong>{followers}</strong>Followers
</a>
</li>
</ul>
</div>
</div>

<style>
 /* стилі */
</style>

 

JSX/CSS

CSS просто додали в компонент. Замість JSX у нас HTMLx вбудований в Svelte.

Контейнер

Контейнером виступає будь батьківський компонент для компонента User. В даному випадку це компонент App.

debounceTime

Дебоунсить введення тексту не має сенсу, якщо операція лише перезаписує значення в моделі даних. Так що в самому біндінгу нам це не потрібно.

pluck

 

filter

 

map

 

Підключаємо

Повернемося в App.html і імпортуємо компонент User:

import User from './User.html';

 

Запит даних

GitHub надає API для отримання інформації про користувача:
Для демонстрації, просто напишемо маленький файлик api.js, який буде абстрагувати отримання даних і експортувати відповідну функцію:

import axios from 'axios';

export function getUserCard(username) {
 return axios.get(`https://api.github.com/users/${username}`)
 .then(res => res.data);
}

І точно також імпортуємо цю функцію App.html.

Тепер сформулюємо задачу більш предметною мовою: нам потрібно при зміні значення в моделі даних (username) змінювати інше значення. Назвемо його відповідно user — дані про юзере, які ми отримуємо з API. Реактивність у всій красі.

Читайте також  Зупинися, мить! Знайомимося з нормкор маркетингом

Для цього, напишемо обчислюване властивість Svelte, використовуючи наступну конструкцію у App.html:

<script>
 import { getUserCard } from './api.js';
...
 export default {
...
 computed: {
 user: ({ username }) => username && getUserCard(username)
}
};
</script>

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

В оригінальній статті ця проблема вирішується вбудованої в RxJS функцією debounceTime, яка не дає нам занадто часто запитувати дані. Для нашої ж реалізації можна скористатися standalone рішенням, типу debounce-promise або будь-яким іншим відповідним, благо є з чого вибрати.

<script>
 import debounce from 'debounce-promise';
 import { getUserCard } from './api.js';
...
 const getUser = debounce(getUserCard, 1000);
...
 export default {
...
 computed: {
 user: ({ username }) => username && getUser(username)
}
};
</script>

Отже, ця ліба створює debounce-версію переданої їй функції, яку ми використовуємо в обчислюваному властивості.

switchMap

 

ajax

RxJS надає власну реалізацію ajax яка чудово працює з switchMap!
Так як ми не використовуємо RxJS і тим більше switchMap, ми можемо використовувати будь-яку бібліотеку для роботи з ajax.

Я використовую axios, тому що він зручний для мене, але ви можете використовувати що завгодно та суті справи це не змінює.

Пробуємо

Для початку нам потрібно зареєструвати компонент User для використання його в якості тега шаблону, так як сам по собі імпорт не додає компонент в контекст шаблону:

<script>
 import User from './User.html';
...
 export default {
 components: { User }
...
};
</script>

Незважаючи на гадану зайву писанину, це дозволяє, в тому числі, давати різні імена тегів инстансам одного і того ж компонента, роблячи ваші шаблони більш семантично зрозумілими.

Далі, значення user — це не самі дані, а проміс на ці дані. Тому що ми халявщики і не хочемо робити взагалі ніякої роботи, тільки пити каву з печивом.

Для того, щоб передати реальні дані в компонент User, ми можемо скористатися спеціальною конструкцією для роботи з промісами:

{#await user} <!-- тут це ще промис -->
{:then user} <!-- а тут уже об'єкт з даними. ім'я можна дати будь-яке. -->
 {#if user}
 <User {...user} />
{/if}
{/await}

Має сенс перевірити об’єкт з даними на існування, перш ніж передавати його в компонент User. Spread-оператор тут дозволяє «розщепити» об’єкт на окремі пропсы при створенні екземпляра компоненту User.

Читайте також  Go vs Javascript. На чому писати IoT проекти

Коротше работинг.

Обробка помилок

Спробуйте ввести неіснуюче ім’я користувача.

Наш додаток зламано.
Ваш напевно так, але наш точно ні))) Просто нічого не відбудеться, хоча це звичайно не діло.

catchError

Додамо додатковий блок для обробки rejected-промісу:

{#await user}
{:then user}
 {#if user}
 <User {...user} />
{/if}
{:catch error}
 <Error {...error} />
{/await}

 

Компонент Error

 

<div class="error">
<h2>Oops!</h2>
 <b>{response.status}: {response.data.message}</b>
 <p>Please try searching again.</p>
</div>

Зараз наш UI виглядає набагато краще:
І не кажіть, а головне ніяких зусиль.

Індикатор завантаження

Коротше там далі взагалі єресь почалася, зі всякими там BehaviorSubject-ами і іже з ними. Ми просто додамо індикатор завантаження і не будемо доїти слона:

{#await user}
<h3>Loading...</h3>
{:then user}
 {#if user}
 <User {...user} />
{/if}
{:catch error}
 <Error {...error} />
{/await}

Результат?
Два крихітних logic-less компонента (User і Error) і один керуючий компонент (App), де найбільш складна бізнес-логіка описана в один рядок — створення обчислюваного властивості. Жодних обмазувань вами-об’єктами з ніг до голови і підключень +100500 інструментів, які вам не потрібні.

→ Інтерактивна демка

Пишіть простий і зрозумілий код. Пишіть менше коду, а значить менше працюйте і проводьте більше часу з сім’єю. Живіть!

Всім щастя і здоров’я!

Все 🙂

FYI

Якщо ви вже подивилися приклад в REPL, то напевно звернули увагу на тост з warning’ом зліва внизу:
Compiled, but with 1 warning — check the console for details
Якщо ви не полінуєтеся відкрити консоль, то побачите ось таке повідомлення:
Unused CSS selector
.user-card .Organization {
background-position: top right;
}

Статичний аналізатор Svelte повідомляє нам, що деякі стилі компонентів не використовуються. Крім того, за вашим бажанням або велінням дефолтні налаштування компілятора, невикористовувані стилі буду видалені з підсумкового css-бандла без вашої участі.

Степан Лютий

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

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *