Je pense que tout le monde sait déjà comment écrire une recherche sur les utilisateurs de GitHub sur React , Svelte , Angular ou sans eux . Eh bien, comment faire sans Vue? Il est temps de combler cette lacune.

Donc, aujourd'hui, nous allons créer la même application en utilisant Vue, écrire des tests pour elle sur Cypress et affecter légèrement Vue CLI 3.
Il y a des gifs dans le post
La préparation
Pour commencer, installez l'interface CLI de la dernière version:
npm i -g @vue/cli
Et lancez la création du projet:
vue create vue-github-search
Suivez les étapes du générateur. Pour notre projet, j'ai choisi le mode manuel et la configuration suivante:

Modules supplémentaires
Nous utiliserons le stylet comme styles, nous avons donc besoin d'un stylet et d'un chargeur de stylet. Nous avons également besoin d' Axios pour les requêtes réseau et de Lodash , à partir desquelles nous prendrons la fonction anti-rebond.
Accédez au dossier du projet et installez les packages nécessaires:
cd vue-github-search npm i stylus stylus-loader axios lodash
Vérifier
Nous démarrons le projet et nous assurons que tout fonctionne:
npm run serve
Toutes les modifications du code seront instantanément appliquées dans le navigateur sans recharger la page.
Boutique
Commençons par écrire le magasin vuex, où toutes les données d'application seront. Nous avons juste besoin de ne rien stocker: une requête de recherche, des données utilisateur et un indicateur du processus de chargement.
Ouvrez store.js
et décrivez l'état initial de l'application et les mutations nécessaires:
... 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 } });
Ajoutez des actions pour charger les données de l'API GitHub et pour modifier la requête de recherche (nous en avons besoin pour la chaîne de recherche). En conséquence, notre magasin prendra la forme suivante:
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); } } });
Chaîne de recherche
Créez un nouveau composant Search.vue
dans le dossier des components
. Ajoutez une propriété calculée pour associer le composant au magasin. Lorsque la requête de recherche change, nous appelons la recherche avec anti-rebond.
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>
Nous allons maintenant connecter notre chaîne de recherche au composant principal d' App.vue
et supprimer simultanément les lignes supplémentaires créées par le générateur.
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>
Voyons le résultat dans le navigateur, en nous assurant que tout fonctionne avec vue-devtools:

Comme vous pouvez le voir, nous avons toute la logique d'application prête! Nous entrons le nom d'utilisateur, la demande est exécutée et les données de profil sont stockées dans le magasin.
Profil utilisateur
Créez le composant User.vue
et ajoutez une logique pour indiquer la charge, afficher le profil et une erreur lorsque l'utilisateur n'est pas trouvé. Ajoutez également des animations de transition.
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>
Connectez notre composant dans App.vue
et profitez du résultat:
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>

Les tests
Nous écrirons des tests simples pour notre application.
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'); }); });
Exécutez les tests avec la commande
npm run test:e2e
Dans la fenêtre qui s'ouvre, cliquez sur le bouton Exécuter toutes les spécifications et vérifiez que les tests réussissent:

Assemblage
Vue CLI 3 prend en charge le nouveau mode de création d'application, le mode moderne. Il crée 2 versions de scripts: léger pour les navigateurs modernes qui prennent en charge les dernières fonctionnalités JavaScript, et une version complète avec tous les polyphiles nécessaires pour les plus anciens. Le charme principal est que nous n'avons absolument pas besoin de nous soucier du déploiement d'une telle application. Ça marche juste. Si le navigateur prend en charge <script type="module">
, il affichera la version légère elle-même. Comment cela fonctionne, vous pouvez en lire plus dans cet article .
Ajoutez l'indicateur modern à package.json
dans la commande build:
"build": "vue-cli-service build --modern"
Monter un projet:
npm run build
Regardons les tailles des scripts résultants:
8.0K ./app-legacy.cb7436d4.js 8.0K ./app.b16ff4f7.js 116K ./chunk-vendors-legacy.1f6dfb2a.js 96K ./chunk-vendors.a98036c9.js
Comme vous pouvez le voir, la nouvelle méthode réduit vraiment la taille de l'assemblage. La différence sera encore plus visible sur les grands projets, donc la fonctionnalité mérite certainement l'attention.
Code
Github
Démo
C'est tout, merci d'avoir regardé!