Как сделать поиск пользователей по 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. Можете потыркать там, либо локально, скачав исходники оттуда (кнопка с характерной иконкой).

Для начала создадим простой файлик 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.

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.

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

Обработка ошибок

Попробуйте ввести несуществующее имя пользователя.
…
Наше приложение сломано.

Ваше наверное да, но наше точно нет))) Просто ничего не произойдет, хотя это конечно не дело.

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), где наиболее сложная бизнес-логика описана в одну строку — создание вычисляемого свойства. Никаких обмазываний observable-объектами с ног до головы и подключений +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-бандла без вашего участия.

P/S

Читайте и другие статьи про Svelte, а также заглядывайте в русскоязычный телеграм канал SvelteJS. Будем рады вам!


ссылка на оригинал статьи https://habr.com/post/419653/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *