Как сделать поиск пользователей по GitHub, используя Vue

от автора

Думаю, все уже знают о том, как написать поиск по пользователям GitHub на React, Svelte, Angular, или вообще без них. Ну и как же тут обойтись без Vue? Самое время заполнить этот пробел.

image

Итак, сегодня мы создадим то самое приложение с использованием Vue, напишем для него тесты на Cypress и немного затронем Vue CLI 3.

В посте есть гифки

Подготовка

Для начала установим Vue CLI последней версии:

npm i -g @vue/cli

И запустим создание проекта:

vue create vue-github-search

Следуем по шагам генератора. Для нашего проекта я выбрал Manual mode и следующую конфигурацию:

image

Дополнительные модули

В качестве стилей будем использовать Stylus, поэтому нам понадобятся stylus и stylus-loader. Ещё нам понадобится Axios для запросов в сеть и Lodash, из которого мы возьмем функцию debounce.

Перейдем в папку проекта и установим необходимые пакеты:

cd vue-github-search npm i stylus stylus-loader axios lodash

Проверяем

Запускаем проект и убеждаемся что все работает:

 npm run serve

Все изменения в коде будут мгновенно применяться в браузере без перезагрузки страницы.

Store

Начнём с того, что напишем vuex store, где будут все данные приложения. Хранить нам нужно всего-то ничего: поисковый запрос, данные пользователя и флаг процесса загрузки.
Откроем store.js и опишем изначальное состояние приложения и необходимые мутации:

... const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER';  export default new Vuex.Store({   state: {     searchQuery: '',     loading: false,     user: null   },   mutations: {     [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery,     [SET_LOADING]: (state, loading) => state.loading = loading,     [SET_USER]: (state, user) => state.user = user,     [RESET_USER]: state => state.user = null   } });

Добавим action’ы для загрузки данных с GitHub API и для изменения поискового запроса (понадобиться нам для поисковой строки). В итоге наш store примет такой вид:

store.js

import Vue from 'vue'; import Vuex from 'vuex'; import axios from 'axios';  Vue.use(Vuex);  const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER';  export default new Vuex.Store({   state: {     searchQuery: '',     loading: false,     user: null   },   mutations: {     [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery,     [SET_LOADING]: (state, loading) => state.loading = loading,     [SET_USER]: (state, user) => state.user = user,     [RESET_USER]: state => state.user = null   },   actions: {     setSearchQuery({commit}, searchQuery) {       commit(SET_SEARCH_QUERY, searchQuery);     },     async search({commit, state}) {       commit(SET_LOADING, true);       try {         const {data} = await axios.get(`https://api.github.com/users/${state.searchQuery}`);         commit(SET_USER, data);       } catch (e) {         commit(RESET_USER);       }       commit(SET_LOADING, false);     }   } });

Строка поиска

Создадим новый компонент Search.vue в папке components. Добавим computed-свойство, чтобы связать компонент со store. При изменениях поискового запроса будем вызывать поиск с debounce.

Search.vue

<template>   <input v-model="query" @input="debouncedSearch" placeholder="Enter username" /> </template>  <script> import {mapActions, mapState} from 'vuex'; import debounce from 'lodash/debounce';  export default {   name: 'search',   computed: {     ...mapState(['searchQuery']),     query: {       get() {         return this.searchQuery;       },       set(val) {         return this.setSearchQuery(val);       }     }   },   methods: {     ...mapActions(['setSearchQuery', 'search']),     debouncedSearch: debounce(function () {       this.search();     }, 500)   } }; </script>  <style lang="stylus" scoped> input   width 100%   font-size 16px   text-align center </style>

Теперь подключим нашу строку поиска в главный компонент App.vue и попутно удалим лишние строки, созданные генератором.

App.vue

<template>   <div id="app">     <Search />   </div> </template>  <script> import Search from './components/Search';  export default {   name: 'app',   components: {     Search   } }; </script>  <style lang="stylus"> #app   font-family 'Avenir', Helvetica, Arial, sans-serif   font-smoothing antialiased   margin 10px </style>

Посмотрим результат в браузере, убедившись что всё работает с помощью vue-devtools:

image

Как видим, у нас уже готова вся логика приложения! Мы вводим имя пользователя, выполняется запрос и данные профиля сохраняются в store.

Профиль пользователя

Создадим компонент User.vue и добавим логику для индикации загрузки, отображения профиля и ошибку, когда пользователь не найден. Также добавим анимацию переходов.

User.vue

<template>   <div class="github-card">     <transition name="fade" mode="out-in">       <div v-if="loading" key="loading">         Loading       </div>       <div v-else-if="user" key="user">         <div class="background" :style="{backgroundImage: `url(${user.avatar_url})`}" />         <div class="content">           <a class="avatar" :href="`https://github.com/${user.login}`" target="_blank">             <img :src="user.avatar_url" :alt="user.login" />           </a>           <h1>{{user.name || user.login}}</h1>           <ul class="status">             <li>               <a :href="`https://github.com/${user.login}?tab=repositories`" target="_blank">                 <strong>{{user.public_repos}}</strong>                 <span>Repos</span>               </a>             </li>             <li>               <a :href="`https://gist.github.com/${user.login}`" target="_blank">                 <strong>{{user.public_gists}}</strong>                 <span>Gists</span>               </a>             </li>             <li>               <a :href="`https://github.com/${user.login}/followers`" target="_blank">                 <strong>{{user.followers}}</strong>                 <span>Followers</span>               </a>             </li>           </ul>         </div>       </div>       <div v-else key="not-found">         User not found       </div>     </transition>   </div> </template>  <script> import {mapState} from 'vuex';  export default {   name: 'User',   computed: mapState(['loading', 'user']) }; </script>  <style lang="stylus" scoped> .github-card   margin-top 50px   padding 20px   text-align center   background #fff   color #000   position relative   h1     margin 16px 0 20px     line-height 1     font-size 24px     font-weight 500   .background     filter blur(10px) opacity(50%)     z-index 1     position absolute     top 0     left 0     right 0     bottom 0     background-size cover     background-position center     background-color #fff   .content     position relative     z-index 2     .avatar       display inline-block       overflow hidden       background #fff       border-radius 100%       text-decoration none       img         display block         width 80px         height 80px     .status       background white     ul       text-transform uppercase       font-size 12px       color gray       list-style-type none       margin 0       padding 0       border-top 1px solid lightgray       border-bottom 1px solid lightgray       zoom 1       &:after         display block         content ''         clear both     li       width 33%       float left       padding 8px 0       box-shadow 1px 0 0 #eee       &:last-of-type         box-shadow none     strong       display block       color #292f33       font-size 16px       line-height 1.6     a       color #707070       text-decoration none       &:hover         color #4183c4  .fade-enter-active, .fade-leave-active   transition opacity .5s .fade-enter, .fade-leave-to   opacity 0 </style>

Подключим наш компонент в App.vue и насладимся результатом:

App.vue

<template>   <div id="app">     <Search />     <User />   </div> </template>  <script> import Search from './components/Search'; import User from './components/User';  export default {   name: 'app',   components: {     User,     Search   } }; </script>  <style lang="stylus"> #app   font-family 'Avenir', Helvetica, Arial, sans-serif   font-smoothing antialiased   margin 10px </style>

image

Тесты

Напишем простые тесты для нашего приложения.

tests/e2e/specs/test.js

describe('Github User Search', () => {   it('has input for username', () => {     cy.visit('/');     cy.get('input');   });   it('has "User not found" caption', () => {     cy.visit('/');     cy.contains('User not found');   });   it("finds Linus Torvalds' GitHub page", () => {     cy.visit('/');     cy.get('input').type('torvalds');     cy.contains('Linus Torvalds');     cy.get('img');     cy.contains('span', 'Repos');     cy.contains('span', 'Gists');     cy.contains('span', 'Followers');   });   it("doesn't find nonexistent page", () => {     cy.visit('/');     cy.get('input').type('_some_random_name_6m92msz23_2');     cy.contains('User not found');   }); });

Запустим тесты командой

npm run test:e2e

В открывшемся окне нажимаем кнопку Run all specs и видим, что тесты проходят:

image

Сборка

Vue CLI 3 поддерживает новый режим сборки приложения, modern mode. Он создает 2 версии скриптов: облегченную для современных браузеров, которые поддерживают последние фичи JavaScript, и полную версию со всеми необходимыми полифилами для более старых. Главная прелесть заключается в том, что нам абсолютно не нужно заморачиваться с деплоем такого приложения. Это просто работает. Если браузер поддерживает <script type="module">, он сам подтянет облегченную сборку. Как это работaет, можно подробнее почитать в этой статье.

Добавим в package.json флаг modern к команде сборки:

"build": "vue-cli-service build --modern"

Собираем проект:

npm run build

Посмотрим на размеры итоговых скриптов:

8.0K    ./app-legacy.cb7436d4.js 8.0K    ./app.b16ff4f7.js 116K    ./chunk-vendors-legacy.1f6dfb2a.js  96K    ./chunk-vendors.a98036c9.js

Как видим, новый метод действительно уменьшает размер сборки. Разница будет еще более заметна на крупных проектах, поэтому фича однозначно заслуживает внимания.

Код

GitHub

На этом все, спасибо за внимание!


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


Комментарии

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

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