Développer des applications plus rapides sur Vue.js

JavaScript est l'âme des applications Web modernes. Il s'agit du principal ingrédient du développement frontal. Il existe différents cadres JavaScript pour créer des interfaces de projet Web. Vue.js est l'un de ces cadres, qui peut être attribué à des solutions assez populaires.

Vue.js est un framework progressif pour créer des interfaces utilisateurs. Sa bibliothèque principale vise principalement à créer la partie visible des interfaces. D'autres bibliothèques peuvent être facilement intégrées dans un projet basé sur Vue si nécessaire. De plus, avec l'aide de Vue.js et avec la participation d'outils modernes et de bibliothèques auxiliaires, il est possible de créer des applications complexes d'une seule page.



Cet article décrira le processus de création d'une simple application Vue.js conçue pour fonctionner avec des notes sur certaines tâches. Voici le référentiel frontend du projet. Voici le référentiel de son backend. En cours de route, nous analyserons certaines fonctionnalités puissantes de Vue.js et des outils auxiliaires.

Création de projet


Avant de passer au développement, créons et configurons le projet de base de notre application de gestion des tâches.

  1. Créez un nouveau projet à l'aide de l'interface de ligne de commande Vue.js 3:

    vue create notes-app
  2. Ajoutez le fichier package.jsonsuivant au projet :

    {
      "name": "notes-app",
      "version": "0.1.0",
      "private": true,
      "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint"
      },
      "dependencies": {
        "axios": "^0.19.1",
        "buefy": "^0.8.9",
        "core-js": "^3.4.4",
        "lodash": "^4.17.15",
        "marked": "^0.8.0",
        "vee-validate": "^3.2.1",
        "vue": "^2.6.10",
        "vue-router": "^3.1.3"
      },
      "devDependencies": {
        "@vue/cli-plugin-babel": "^4.1.0",
        "@vue/cli-plugin-eslint": "^4.1.0",
        "@vue/cli-service": "^4.1.0",
        "@vue/eslint-config-prettier": "^5.0.0",
        "babel-eslint": "^10.0.3",
        "eslint": "^5.16.0",
        "eslint-plugin-prettier": "^3.1.1",
        "eslint-plugin-vue": "^5.0.0",
        "prettier": "^1.19.1",
        "vue-template-compiler": "^2.6.10"
      }
    }
  3. Définissez les dépendances décrites dans package.json:

    npm install

Maintenant, une fois la base de données d'application prête, nous pouvons passer à l'étape suivante de son utilisation.

Acheminement


Le routage est l'une des grandes fonctionnalités des applications Web modernes. Le routeur peut être intégré à l'application Vue.js à l'aide de la bibliothèque vue-router. Il s'agit du routeur officiel des projets Vue.js. Parmi ses caractéristiques, on note les suivantes:

  • Itinéraires / vues imbriqués.
  • Configuration modulaire du routeur.
  • Accès aux paramètres de route, demandes, modèles.
  • Animation de transitions de représentations basées sur les capacités de Vue.js.
  • Contrôle de navigation pratique.
  • Prise en charge du style automatique des liens actifs.
  • Prise en charge de l'historique HTML5-API, possibilité d'utiliser des hachages d'URL, basculez automatiquement en mode de compatibilité IE9.
  • Comportement de défilement de page personnalisable.

Pour implémenter le routage dans notre application, créez, dans un dossier router, un fichier index.js. Ajoutez-y le code suivant:

import Vue from "vue";
import VueRouter from "vue-router";
import DashboardLayout from "../layout/DashboardLayout.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/home",
    component: DashboardLayout,
    children: [
      {
        path: "/notes",
        name: "Notes",
        component: () =>
          import(/* webpackChunkName: "home" */ "../views/Home.vue")
      }
    ]
  },
  {
    path: "/",
    redirect: { name: "Notes" }
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

Prenons un objet routesqui inclut une description des itinéraires pris en charge par l'application. Les itinéraires imbriqués sont utilisés ici.

L'objet childrencontient des itinéraires imbriqués qui seront affichés sur la page d'application qui représente son panneau de configuration (fichier DashboardLayout.vue). Voici le modèle de cette page:

<template>
  <span>
    <nav-bar />
    <div class="container is-fluid body-content">
      <router-view :key="$router.path" />
    </div>
  </span>
</template>

Dans ce code, le plus important est la balise router-view. Il joue le rôle d'un conteneur qui contient tous les composants correspondant à l'itinéraire affiché.

Notions de base sur les composants


Les composants sont le composant principal des applications Vue.js. Ils nous donnent l'opportunité d'adopter une approche modulaire du développement, ce qui signifie diviser les pages DOM en plusieurs petits fragments qui peuvent être réutilisés sur différentes pages.

Lors de la conception de composants, afin de les rendre évolutifs et adaptés à la réutilisation, il y a quelques points importants à considérer:

  1. Identifiez une fonctionnalité distincte qui peut être sélectionnée dans le projet en tant que composant.
  2. Ne surchargez pas le composant avec des capacités qui ne correspondent pas à sa fonctionnalité principale.
  3. N'incluez que le code qui sera utilisé pour assurer son propre fonctionnement. Par exemple, il s'agit d'un code qui garantit le fonctionnement des liaisons de données standard pour un certain composant, comme l'année, le sexe de l'utilisateur, etc.
  4. N'ajoutez pas de code au composant qui permet de travailler avec des mécanismes externes au composant, par exemple, avec certaines API.

Ici, à titre d'exemple simple, vous pouvez considérer la barre de navigation - un composant NavBarqui ne contient que des descriptions des structures DOM liées aux outils de navigation pour l'application. Le code du composant est contenu dans le fichier NavBar.vue:

<template>
  <nav class="navbar" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="/home/notes">
        <img align="center" src="@/assets/logo.png" width="112" height="28">
      </a>

      <a
        role="button"
        class="navbar-burger burger"
        aria-label="menu"
        aria-expanded="false"
        data-target="navbarBasicExample"
      >
        <span aria-hidden="true" />
        <span aria-hidden="true" />
        <span aria-hidden="true" />
      </a>
    </div>
  </nav>
</template>

Voici comment ce composant est utilisé dans DashboardLayout.vue:

<template>
  <span>
    <nav-bar />
    <div class="container is-fluid body-content">
      <router-view :key="$router.path" />
    </div>
  </span>
</template>

<script>
import NavBar from "@/components/NavBar";
export default {
  components: {
    NavBar
  }
};
</script>

<style scoped></style>

Interaction des composants


Dans toute application Web, la bonne organisation des flux de données est extrêmement importante. Cela vous permet de manipuler et de gérer efficacement les données d'application.

Lors de l'utilisation de l'approche par composants, lors de la séparation du balisage et du code d'application en petites parties, le développeur demande comment transférer et traiter les données utilisées par divers composants. La réponse à cette question est l'organisation de l'interaction des composants.

L'interaction des composants dans un projet Vue.js peut être organisée à l'aide des mécanismes suivants:

  1. Les propriétés (accessoires) sont utilisées lors du transfert de données des composants parents vers les composants enfants.
  2. La méthode $ emit () est utilisée lors du transfert de données des composants enfants vers les composants parents.
  3. Le bus d'événements global (EventBus) est utilisé lorsque des structures de composants avec imbrication profonde sont utilisées, ou lorsqu'il est nécessaire, à l'échelle mondiale, d'organiser un échange entre composants selon le modèle éditeur / abonné.

Afin de comprendre le concept d'interaction des composants dans Vue.js, nous ajoutons deux composants au projet:

  • Composant Addqui sera utilisé pour ajouter de nouvelles tâches au système et pour modifier des tâches existantes.
  • Un composant NoteViewerconçu pour afficher des informations sur une tâche.

Voici le fichier composant Add( Add.vue):

<template>
  <div class="container">
    <div class="card note-card">
      <div class="card-header">
        <div class="card-header-title title">
          <div class="title-content">
            <p v-if="addMode">
              Add Note
            </p>
            <p v-else>
              Update Note
            </p>
          </div>
        </div>
      </div>
      <div class="card-content">
        <div class="columns">
          <div class="column is-12">
            <template>
              <section>
                <b-field label="Note Header">
                  <b-input
                    v-model="note.content.title"
                    type="input"
                    placeholder="Note header"
                  />
                </b-field>
                <b-field label="Description">
                  <b-input
                    v-model="note.content.description"
                    type="textarea"
                    placeholder="Note Description"
                  />
                </b-field>
                <div class="buttons">
                  <b-button class="button is-default" @click="cancelNote">
                    Cancel
                  </b-button>
                  <b-button
                    v-if="addMode"
                    class="button is-primary"
                    @click="addNote"
                  >
                    Add
                  </b-button>
                  <b-button
                    v-else
                    class="button is-primary"
                    @click="updateNote"
                  >
                    Update
                  </b-button>
                </div>
              </section>
            </template>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    addMode: {
      type: Boolean,
      required: false,
      default() {
        return true;
      }
    },
    note: {
      type: Object,
      required: false,
      default() {
        return {
          content: {
            title: "",
            description: "",
            isComplated: false
          }
        };
      }
    }
  },
  methods: {
    addNote() {
      this.$emit("add", this.note);
    },
    updateNote() {
      this.$emit("update", this.note);
    },
    cancelNote() {
      this.$emit("cancel");
    }
  }
};
</script>

<style></style>

Voici le fichier composant NoteViewer( NoteViewer.vue):

<template>
  <div class="container">
    <div class="card note-card">
      <div class="card-header">
        <div class="card-header-title title">
          <div class="column is-6">
            <p>Created at {{ note.content.createdAt }}</p>
          </div>
          <div class="column is-6 ">
            <div class="buttons is-pulled-right">
              <button
                v-show="!note.content.isCompleted"
                class="button is-success is-small "
                title="Mark Completed"
                @click="markCompleted"
              >
                <b-icon pack="fas" icon="check" size="is-small" />
              </button>
              <button
                v-show="!note.content.isCompleted"
                class="button is-primary is-small"
                title="Edit Note"
                @click="editNote"
              >
                <b-icon pack="fas" icon="pen" size="is-small" />
              </button>
              <button
                class="button is-primary is-small "
                title="Delete Note"
                @click="deleteNote"
              >
                <b-icon pack="fas" icon="trash" size="is-small" />
              </button>
            </div>
          </div>
        </div>
      </div>
      <div
        class="card-content"
        :class="note.content.isCompleted ? 'note-completed' : ''"
      >
        <strong>{{ note.content.title }}</strong>
        <p>{{ note.content.description }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "NoteViewer",
  props: {
    note: {
      type: Object,
      required: true
    }
  },
  methods: {
    editNote() {
      this.$emit("edit", this.note);
    },
    deleteNote() {
      this.$emit("delete", this.note);
    },
    markCompleted() {
      this.$emit("markCompleted", this.note);
    }
  }
};
</script>

<style></style>

Maintenant que les composants sont créés, nous allons examiner leurs sections <script>.

Le site a propsannoncé quelques objets avec leurs types. Ce sont les objets que nous allons transmettre au composant lorsqu'il sera affiché sur une page de l'application.

De plus, faites attention aux parties du code où la méthode est utilisée $emit(). Avec lui, un composant enfant génère des événements par lesquels les données sont transmises au composant parent.

Parlons de l'utilisation des composants dans l'application Addet NoteViewer. Décrivons dans le fichier Home.vueci-dessous les mécanismes de transmission des données vers ces composants et les mécanismes d'écoute des événements générés par eux:

<template>
  <div class="container">
    <div class="columns">
      <div class="column is-12">
        <button
          class="button is-primary is-small is-pulled-right"
          title="Add New Note"
          @click="enableAdd()"
        >
          <b-icon pack="fas" icon="plus" size="is-small" />
        </button>
      </div>
    </div>
    <div class="columns">
      <div class="column is-12">
        <note-editor
          v-show="enableAddNote"
          :key="enableAddNote"
          @add="addNote"
          @cancel="disableAdd"
        />

        <div v-for="(note, index) in data" :key="index">
          <note-viewer
            v-show="note.viewMode"
            :note="note"
            @edit="editNote"
            @markCompleted="markCompletedConfirm"
            @delete="deleteNoteConfirm"
          />

          <note-editor
            v-show="!note.viewMode"
            :add-mode="false"
            :note="note"
            @update="updateNote"
            @cancel="cancelUpdate(note)"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
// @ is an alias to /src
// import NoteEditor from "@/components/NoteEditor.vue";
import NoteEditor from "@/components/Add.vue";
import NoteViewer from "@/components/NoteViewer.vue";
export default {
  name: "Home",
  components: {
    // NoteEditor,
    NoteEditor,
    NoteViewer
  },
  data() {
    return {
      enableAddNote: false,
      data: []
    };
  },
  mounted() {
    this.getNotes();
  },
  methods: {
    enableAdd() {
      this.enableAddNote = true;
    },
    disableAdd() {
      this.enableAddNote = false;
    },
    async getNotes() {
      this.data = [];
      const data = await this.$http.get("notes/getall");
      data.forEach(note => {
        this.data.push({
          content: note,
          viewMode: true
        });
      });
    },
    async addNote(note) {
      await this.$http.post("notes/create", note.content);
      this.disableAdd();
      await this.getNotes();
    },
    editNote(note) {
      note.viewMode = false;
    },
    async updateNote(note) {
      await this.$http.put(`notes/${note.content.id}`, note.content);
      note.viewMode = true;
      await this.getNotes();
    },
    cancelUpdate(note) {
      note.viewMode = true;
    },
    markCompletedConfirm(note) {
      this.$buefy.dialog.confirm({
        title: "Mark Completed",
        message: "Would you really like to mark the note completed?",
        type: "is-warning",
        hasIcon: true,
        onConfirm: async () => await this.markCompleted(note)
      });
    },
    async markCompleted(note) {
      note.content.isCompleted = true;
      await this.$http.put(`notes/${note.content.id}`, note.content);
      await this.getNotes();
    },
    deleteNoteConfirm(note) {
      this.$buefy.dialog.confirm({
        title: "Delete note",
        message: "Would you really like to delete the note?",
        type: "is-danger",
        hasIcon: true,
        onConfirm: async () => await this.deleteNote(note)
      });
    },
    async deleteNote(note) {
      await this.$http.delete(`notes/${note.content.id}`);
      await this.getNotes();
    }
  }
};
</script>

Maintenant, si vous regardez attentivement ce code, vous remarquerez que le composant Addportant le nom note-editorest appliqué deux fois. Une fois - pour ajouter une note, une deuxième fois - pour mettre à jour son contenu.

De plus, nous réutilisons le composant NoteViewerprésenté ici comme note-viewer, listant avec son aide une liste de notes chargées à partir de la base de données, que nous itérons à travers l'attribut v-for.

Il convient encore de prêter attention à un événement @cancelqui est utilisé dans l'élément note-editor, qui est pour les opérations Addet Updategéré différemment, malgré le fait que ces opérations sont implémentées sur la base du même composant.

<!-- Add Task -->
<note-editor v-show="enableAddNote"
:key="enableAddNote"
@add="addNote"
@cancel="disableAdd" />
<!-- Update Task -->
<note-editor v-show="!note.viewMode"
:add-mode="false"
:note="note"
@update="updateNote"
@cancel="cancelUpdate(note)" />

C'est ainsi que les problèmes de mise à l'échelle peuvent être évités. Le fait est que s'il y a une probabilité de changement dans la mise en œuvre d'un certain mécanisme, alors dans une telle situation, le composant génère simplement l'événement correspondant.

Lorsque nous travaillons avec des composants, nous utilisons l'injection de données dynamiques. Par exemple, l'attribut :notec note-viewer.

C'est tout. Nos composants peuvent désormais échanger des données.

Utilisation de la bibliothèque Axios


Axios est une bibliothèque basée sur Promise pour organiser les interactions avec divers services externes.

Il a de nombreuses fonctionnalités et se concentre sur un travail sûr. Nous parlons du fait qu'Axios prend en charge la protection contre les attaques XSRF, les intercepteurs de demande et de réponse, les moyens de convertir les données de demande et de réponse, il prend en charge l'annulation des demandes, et bien plus encore.

Nous allons connecter la bibliothèque Axios à l'application et la configurer afin de ne pas avoir à l'importer à chaque utilisation. Créez un axiosfichier dans le dossier index.js:

import axios from "axios";

const apiHost = process.env.VUE_APP_API_HOST || "/";

let baseURL = "api";

if (apiHost) {
  baseURL = `${apiHost}api`;
}
export default axios.create({ baseURL: baseURL });

Ajoutez un main.jsintercepteur de réponse au fichier , conçu pour interagir avec une API externe. Nous utiliserons l'intercepteur pour préparer les données transférées vers l'application et pour traiter les erreurs.

import HTTP from "./axios";

//   
HTTP.interceptors.response.use(
  response => {
    if (response.data instanceof Blob) {
      return response.data;
    }
    return response.data.data || {};
  },
  error => {
    if (error.response) {
      Vue.prototype.$buefy.toast.open({
        message: error.response.data.message || "Something went wrong",
        type: "is-danger"
      });
    } else {
      Vue.prototype.$buefy.toast.open({
        message: "Unable to connect to server",
        type: "is-danger"
      });
    }
    return Promise.reject(error);
  }
);

Vue.prototype.$http = HTTP;

Maintenant, ajoutez à la main.jsvariable globale $http:

import HTTP from "./axios";
Vue.prototype.$http = HTTP;

Nous pourrons travailler avec cette variable tout au long de l'application via une instance de Vue.js.

Nous sommes maintenant prêts à faire des demandes d'API, qui peuvent ressembler à ceci:

const data = await this.$http.get("notes/getall");

Optimisation


Imaginez que notre application ait pris de la taille lorsqu'elle comprend des centaines de composants et de vues.

Cela affectera le temps de chargement de l'application, car tout son code JavaScript sera téléchargé dans le navigateur en une seule fois. Afin d'optimiser le chargement de l'application, nous devons répondre à plusieurs questions:

  1. Comment s'assurer que les composants et les vues qui ne sont pas actuellement utilisés ne sont pas chargés?
  2. Comment réduire la taille des documents téléchargés?
  3. Comment améliorer le temps de chargement des applications?

En réponse à ces questions, les suggestions suivantes peuvent être suggérées: charger immédiatement la structure de base de l'application et charger les composants et les vues lorsqu'ils sont nécessaires. Nous le ferons en utilisant les capacités de Webpack et en apportant les modifications suivantes aux paramètres du routeur:

{
path: "/notes",
name: "Notes",
component: () =>
import(/* webpackChunkName: "home" */ "../views/Home.vue")
}
//   /* webpackChunkName: "home" */

Cela vous permet de créer des fragments séparés pour un itinéraire particulier avec des matériaux d'application (affichage [view].[hash].js) qui sont chargés en mode paresseux lorsque l'utilisateur visite cet itinéraire.

Emballage d'un projet dans un conteneur Docker et déploiement


Maintenant, l'application fonctionne comme il se doit, ce qui signifie qu'il est temps de la conteneuriser. Ajoutez le fichier suivant au projet Dockerfile:

# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG VUE_APP_API_HOST
ENV VUE_APP_API_HOST $VUE_APP_API_HOST
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Lorsque vous utilisez l'application en production, nous la plaçons derrière un serveur HTTP puissant comme Nginx. Cela protège l'application contre le piratage et autres attaques.

Vous vous souvenez de la variable d'environnement contenant les informations d'hôte que nous avons déclarées lors de la configuration d'Axios? Elle est là:

const apiHost = process.env.VUE_APP_API_HOST || "/";

Comme il s'agit d'une application de navigateur, nous devons définir et transmettre cette variable à l'application lors de son assemblage. Il est très simple de le faire en utilisant l'option --build-arglors de l'assemblage de l'image:

sudo docker build --build-arg VUE_APP_API_HOST=<Scheme>://<ServiceHost>:<ServicePort>/ -f Dockerfile -t vue-app-image .

Faites attention à ce que vous devez remplacer <Scheme>, <ServiceHost>et <ServicePort>aux valeurs qui ont du sens pour votre projet.

Une fois le conteneur d'application assemblé, il peut être lancé:

sudo docker run -d -p 8080:80 — name vue-app vue-app-image

Sommaire


Nous avons examiné le processus de développement d'une application basée sur Vue.js, parlé de quelques outils auxiliaires et abordé des problèmes d'optimisation des performances. Maintenant, avec notre application, vous pouvez expérimenter dans le navigateur. Voici une vidéo montrant comment travailler avec.

Chers lecteurs! Que conseilleriez-vous de prêter attention aux débutants cherchant à développer des applications Vue.js hautes performances qui évoluent bien?

Source: https://habr.com/ru/post/undefined/


All Articles