Entwicklung schnellerer Anwendungen auf Vue.js.

JavaScript ist die Seele moderner Webanwendungen. Dies ist der Hauptbestandteil der Front-End-Entwicklung. Es gibt verschiedene JavaScript-Frameworks zum Erstellen von Webprojektschnittstellen. Vue.js ist eines dieser Frameworks, das auf recht beliebte Lösungen zurückgeführt werden kann.

Vue.js ist ein fortschrittliches Framework zum Erstellen von Benutzeroberflächen. Die Kernbibliothek zielt hauptsächlich darauf ab, den sichtbaren Teil von Schnittstellen zu erstellen. Andere Bibliotheken können bei Bedarf problemlos in ein Vue-basiertes Projekt integriert werden. Darüber hinaus ist es mit Hilfe von Vue.js und unter Einbeziehung moderner Tools und Hilfsbibliotheken möglich, komplexe einseitige Anwendungen zu erstellen.



In diesem Artikel wird der Prozess zum Erstellen einer einfachen Vue.js-Anwendung beschrieben, die mit Notizen zu bestimmten Aufgaben arbeiten soll. Hier ist das Projekt-Frontend-Repository. Hier ist das Repository seines Backends. Auf dem Weg werden wir einige leistungsstarke Funktionen von Vue.js und Hilfstools analysieren.

Projekterstellung


Bevor wir mit der Entwicklung fortfahren, erstellen und konfigurieren wir das Basisprojekt unserer Task-Management-Anwendung.

  1. Erstellen Sie ein neues Projekt über die Befehlszeilenschnittstelle von Vue.js 3:

    vue create notes-app
  2. Fügen Sie dem Projekt die package.jsonfolgende Datei hinzu :

    {
      "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. Legen Sie die folgenden Abhängigkeiten fest package.json:

    npm install

Nachdem die Anwendungsdatenbank fertig ist, können wir mit dem nächsten Schritt fortfahren.

Routing


Routing ist eine der großartigen Funktionen moderner Webanwendungen. Der Router kann über die Bibliothek in die Anwendung Vue.js integriert werden vue-router. Dies ist der offizielle Router für Vue.js Projekte. Unter seinen Merkmalen stellen wir Folgendes fest:

  • Verschachtelte Routen / Ansichten.
  • Modulare Konfiguration des Routers.
  • Zugriff auf Routenparameter, Anforderungen, Vorlagen.
  • Animation von Übergängen von Darstellungen basierend auf den Fähigkeiten von Vue.js.
  • Bequeme Navigationssteuerung.
  • Unterstützung für das automatische Styling aktiver Links.
  • Die Unterstützung des HTML5-API-Verlaufs und die Möglichkeit, URL-Hashes zu verwenden, wechseln automatisch in den IE9-Kompatibilitätsmodus.
  • Anpassbares Bildlaufverhalten.

Um das Routing in unserer Anwendung zu implementieren, erstellen Sie in einem Ordner routereine Datei index.js. Fügen Sie den folgenden Code hinzu:

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;

Stellen Sie sich ein Objekt vor routes, das eine Beschreibung der von der Anwendung unterstützten Routen enthält. Hier werden verschachtelte Routen verwendet.

Das Objekt childrenenthält verschachtelte Routen, die auf der Anwendungsseite angezeigt werden, die die Systemsteuerung (Datei DashboardLayout.vue) darstellt. Hier ist die Vorlage für diese Seite:

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

In diesem Code ist das Tag das wichtigste router-view. Es spielt die Rolle eines Containers, der alle Komponenten enthält, die der angezeigten Route entsprechen.

Komponentengrundlagen


Komponenten sind die Kernkomponente der Vue.js-Anwendungen. Sie geben uns die Möglichkeit, einen modularen Entwicklungsansatz zu verwenden, dh DOM-Seiten in mehrere kleine Fragmente aufzuteilen, die auf verschiedenen Seiten wiederverwendet werden können.

Beim Entwerfen von Komponenten sind einige wichtige Dinge zu beachten, um sie skalierbar und für die Wiederverwendung geeignet zu machen:

  1. Identifizieren Sie eine separate Funktionalität, die aus dem Projekt als Komponente ausgewählt werden kann.
  2. Überladen Sie die Komponente nicht mit Funktionen, die nicht der Hauptfunktionalität entsprechen.
  3. Fügen Sie nur den Code ein, der verwendet wird, um den eigenen Betrieb sicherzustellen. Dies ist beispielsweise ein Code, der den Betrieb von Standarddatenbindungen für eine bestimmte Komponente sicherstellt, z. B. Jahr, Geschlecht des Benutzers usw.
  4. Fügen Sie der Komponente keinen Code hinzu, der die Arbeit mit Mechanismen außerhalb der Komponente ermöglicht, z. B. mit bestimmten APIs.

Als einfaches Beispiel können Sie die Navigationsleiste betrachten - eine Komponente NavBar, die nur Beschreibungen von DOM-Strukturen enthält, die sich auf Navigationswerkzeuge für die Anwendung beziehen. Der Komponentencode ist in der Datei enthalten 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>

So wird diese Komponente verwendet 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>

Komponenteninteraktion


In jeder Webanwendung ist die ordnungsgemäße Organisation des Datenflusses äußerst wichtig. Auf diese Weise können Sie Anwendungsdaten effektiv bearbeiten und verwalten.

Bei Verwendung des Komponentenansatzes und bei der Trennung von Markup und Anwendungscode in kleine Teile fragt der Entwickler, wie die von verschiedenen Komponenten verwendeten Daten übertragen und verarbeitet werden sollen. Die Antwort auf diese Frage ist die Organisation des Zusammenspiels von Komponenten.

Die Interaktion von Komponenten in einem Vue.js-Projekt kann mithilfe der folgenden Mechanismen organisiert werden:

  1. Eigenschaften (Requisiten) werden beim Übertragen von Daten von übergeordneten Komponenten zu untergeordneten Komponenten verwendet.
  2. Die $ emit () -Methode wird verwendet, wenn Daten von untergeordneten Komponenten zu übergeordneten Komponenten übertragen werden.
  3. Der globale Ereignisbus (EventBus) wird verwendet, wenn Komponentenstrukturen mit tiefer Verschachtelung verwendet werden oder wenn auf globaler Ebene ein Austausch zwischen Komponenten gemäß dem Publisher / Subscriber-Modell organisiert werden muss.

Um das Konzept der Komponenteninteraktion in Vue.js zu verstehen, fügen wir dem Projekt zwei Komponenten hinzu:

  • Die Komponente Add, mit der dem System neue Aufgaben hinzugefügt und vorhandene Aufgaben bearbeitet werden.
  • Eine Komponente NoteViewer, die Informationen zu einer Aufgabe anzeigt.

Hier ist die Komponentendatei 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>

Hier ist die Komponentendatei 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>

Nachdem die Komponenten erstellt wurden, werden wir ihre Abschnitte untersuchen <script>.

Die Seite propskündigte einige Objekte mit ihren Typen an. Dies sind die Objekte, die wir an die Komponente übergeben, wenn sie auf einer Seite der Anwendung angezeigt wird.

Achten Sie außerdem auf die Teile des Codes, in denen die Methode verwendet wird $emit(). Damit generiert eine untergeordnete Komponente Ereignisse, durch die Daten an die übergeordnete Komponente übergeben werden.

Lassen Sie uns darüber sprechen, wie die Komponenten in der Anwendung Addund verwendet werden NoteViewer. Beschreiben wir in der folgenden Datei Home.vuedie Mechanismen zum Übertragen von Daten an diese Komponenten und die Mechanismen zum Abhören der von ihnen erzeugten Ereignisse:

<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>

Wenn Sie sich diesen Code genau ansehen, werden Sie feststellen, dass die Komponente Addmit dem Namen note-editorzweimal angewendet wird. Einmal - um eine Notiz hinzuzufügen, ein zweites Mal - um den Inhalt zu aktualisieren.

Darüber hinaus verwenden wir die NoteViewerhier dargestellte Komponente erneut als note-viewerund zeigen damit eine Liste der aus der Datenbank geladenen Notizen an, die wir durch das Attribut iterieren v-for.

Es lohnt sich immer noch, auf ein Ereignis zu achten, @canceldas in dem Element verwendet wird note-editor, das für Operationen vorgesehen ist Addund Updateanders behandelt wird, obwohl diese Operationen auf der Grundlage derselben Komponente implementiert werden.

<!-- 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)" />

So können Skalierungsprobleme vermieden werden. Der Punkt ist, dass wenn in einer solchen Situation die Wahrscheinlichkeit einer Änderung der Implementierung eines bestimmten Mechanismus besteht, die Komponente einfach das entsprechende Ereignis erzeugt.

Bei der Arbeit mit Komponenten verwenden wir die dynamische Dateninjektion. Zum Beispiel Attribut :notec note-viewer.

Das ist alles. Jetzt können unsere Komponenten Daten austauschen.

Verwenden der Axios-Bibliothek


Axios ist eine Promise-basierte Bibliothek zum Organisieren von Interaktionen mit verschiedenen externen Diensten.

Es hat viele Funktionen und konzentriert sich auf sicheres Arbeiten. Wir sprechen über die Tatsache, dass Axios den Schutz vor XSRF-Angriffen, Anforderungs- und Antwortabfangjägern, Mittel zum Konvertieren von Anforderungs- und Antwortdaten, die Unterstützung des Anforderungsabbruchs und vieles mehr unterstützt.

Wir verbinden die Axios-Bibliothek mit der Anwendung und konfigurieren sie so, dass wir sie nicht jedes Mal importieren müssen, wenn wir sie verwenden. Erstellen Sie eine axiosDatei im Ordner 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 });

Fügen Sie main.jsder Datei einen Antwort-Interceptor hinzu, der für die Interaktion mit einer externen API ausgelegt ist. Wir werden den Interceptor verwenden, um die an die Anwendung übertragenen Daten vorzubereiten und Fehler zu behandeln.

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;

Fügen Sie nun der main.jsglobalen Variablen hinzu $http:

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

Wir können mit dieser Variablen in der gesamten Anwendung über eine Instanz von Vue.js arbeiten.

Jetzt können wir API-Anfragen stellen, die folgendermaßen aussehen können:

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

Optimierung


Stellen Sie sich vor, unsere Anwendung ist auf Größen angewachsen, wenn sie Hunderte von Komponenten und Ansichten enthält.

Dies wirkt sich auf die Ladezeit der Anwendung aus, da der gesamte JavaScript-Code auf einmal in den Browser heruntergeladen wird. Um das Laden der Anwendung zu optimieren, müssen wir einige Fragen beantworten:

  1. Wie kann sichergestellt werden, dass Komponenten und Ansichten, die derzeit nicht verwendet werden, nicht geladen werden?
  2. Wie kann ich die Größe der heruntergeladenen Materialien reduzieren?
  3. Wie kann die Ladezeit von Anwendungen verbessert werden?

Als Antwort auf diese Fragen kann Folgendes vorgeschlagen werden: Laden Sie sofort die Grundstruktur der Anwendung und laden Sie Komponenten und Ansichten, wenn sie benötigt werden. Dazu verwenden wir die Funktionen von Webpack und nehmen die folgenden Änderungen an den Router-Einstellungen vor:

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

Auf diese Weise können Sie separate Fragmente für eine bestimmte Route mit Anwendungsmaterialien (Ansichtsmaterialien [view].[hash].js) erstellen , die im Lazy-Modus geladen werden, wenn der Benutzer diese Route besucht.

Packen eines Projekts in einen Docker-Container und Bereitstellen


Jetzt funktioniert die Anwendung wie es sollte, was bedeutet, dass es Zeit ist, sie zu containerisieren. Fügen Sie dem Projekt die folgende Datei hinzu 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;"]

Wenn Sie die Anwendung in der Produktion verwenden, platzieren wir sie hinter einem leistungsstarken HTTP-Server wie Nginx. Dies schützt die Anwendung vor Hacking und anderen Angriffen.

Erinnern Sie sich an die Umgebungsvariable mit den Hostinformationen, die wir beim Einrichten von Axios deklariert haben? Da ist sie:

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

Da es sich um eine Browseranwendung handelt, müssen wir diese Variable während ihrer Assemblierung festlegen und an die Anwendung übergeben. Dies ist sehr einfach, indem Sie --build-argbeim Zusammenstellen des Bildes die Option verwenden :

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

Achten Sie darauf, was Sie ersetzen müssen <Scheme>, <ServiceHost>und <ServicePort>auf Werte, die für Ihr Projekt sinnvoll sind.

Nachdem der Anwendungscontainer zusammengestellt wurde, kann er gestartet werden:

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

Zusammenfassung


Wir haben den Entwicklungsprozess einer auf Vue.js basierenden Anwendung untersucht, über einige Hilfstools gesprochen und Probleme mit der Leistungsoptimierung angesprochen. Jetzt können Sie mit unserer Anwendung im Browser experimentieren. Hier ist ein Video, das zeigt, wie man damit arbeitet.

Liebe Leser! Was würden Sie Anfängern empfehlen, die leistungsstarke Vue.js-Anwendungen entwickeln möchten, die sich gut skalieren lassen?

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


All Articles