Desarrollando aplicaciones más rápidas en Vue.js

JavaScript es el alma de las aplicaciones web modernas. Este es el ingrediente principal en el desarrollo front-end. Existen varios marcos JavaScript para crear interfaces de proyectos web. Vue.js es uno de estos marcos, que se puede atribuir a soluciones bastante populares.

Vue.js es un marco progresivo para crear interfaces de usuario. Su biblioteca principal está dirigida principalmente a crear la parte visible de las interfaces. Otras bibliotecas pueden integrarse fácilmente en un proyecto basado en Vue si es necesario. Además, con la ayuda de Vue.js y con la participación de herramientas modernas y bibliotecas auxiliares, es posible crear aplicaciones complejas de una sola página.



Este artículo describirá el proceso de creación de una aplicación Vue.js simple diseñada para trabajar con notas sobre ciertas tareas. Aquí está el repositorio frontend del proyecto. Aquí está el repositorio de su backend. En el camino, analizaremos algunas características poderosas de Vue.js y herramientas auxiliares.

Creación de proyectos


Antes de pasar al desarrollo, creemos y configuremos el proyecto básico de nuestra aplicación de administración de tareas.

  1. Cree un nuevo proyecto utilizando la interfaz de línea de comando Vue.js 3:

    vue create notes-app
  2. Agregue el package.jsonsiguiente archivo al proyecto :

    {
      "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. Establezca las dependencias descritas en package.json:

    npm install

Ahora, una vez que la base de datos de la aplicación esté lista, podemos pasar al siguiente paso para trabajar en ella.

Enrutamiento


El enrutamiento es una de las grandes características de las aplicaciones web modernas. El enrutador se puede integrar en la aplicación Vue.js utilizando la biblioteca vue-router. Este es el enrutador oficial para proyectos Vue.js. Entre sus características, destacamos lo siguiente:

  • Rutas / vistas anidadas.
  • Configuración modular del enrutador.
  • Acceso a parámetros de ruta, solicitudes, plantillas.
  • Animación de transiciones de representaciones basadas en las capacidades de Vue.js.
  • Conveniente control de navegación.
  • Soporte para el diseño automático de enlaces activos.
  • Compatibilidad con el historial de HTML5-API, la capacidad de usar hashes de URL, cambia automáticamente al modo de compatibilidad IE9.
  • Comportamiento de desplazamiento de página personalizable.

Para implementar el enrutamiento en nuestra aplicación, cree, en una carpeta router, un archivo index.js. Agregue el siguiente código:

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;

Considere un objeto routesque incluye una descripción de las rutas admitidas por la aplicación. Las rutas anidadas se usan aquí.

El objeto childrencontiene rutas anidadas que se mostrarán en la página de la aplicación que representa su panel de control (archivo DashboardLayout.vue). Aquí está la plantilla para esta página:

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

En este código, lo más importante es la etiqueta router-view. Desempeña el papel de un contenedor que contiene todos los componentes correspondientes a la ruta mostrada.

Conceptos básicos


Los componentes son el componente central de las aplicaciones Vue.js. Nos dan la oportunidad de utilizar un enfoque modular para el desarrollo, lo que significa dividir las páginas DOM en varios fragmentos pequeños que se pueden reutilizar en diferentes páginas.

Al diseñar componentes, para que sean escalables y adecuados para su reutilización, hay algunas cosas importantes a considerar:

  1. Identifique una pieza separada de funcionalidad que se puede seleccionar del proyecto como componente.
  2. No sobrecargue el componente con capacidades que no corresponden a su funcionalidad principal.
  3. Incluya solo el código que se utilizará para garantizar su propia operación. Por ejemplo, este es un código que garantiza el funcionamiento de los enlaces de datos estándar para un determinado componente, como el año, el sexo del usuario, etc.
  4. No agregue código al componente que proporciona trabajo con mecanismos externos al componente, por ejemplo, con ciertas API.

Aquí, como un ejemplo simple, puede considerar la barra de navegación, un componente NavBarque contiene solo descripciones de estructuras DOM relacionadas con herramientas de navegación para la aplicación. El código del componente está contenido en el archivo 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>

Así es como se usa este componente en 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>

Interacción de componentes


En cualquier aplicación web, la organización adecuada de los flujos de datos es extremadamente importante. Esto le permite manipular y administrar efectivamente los datos de la aplicación.

Al utilizar el enfoque de componentes, al separar el marcado y el código de la aplicación en partes pequeñas, el desarrollador pregunta cómo transferir y procesar los datos utilizados por varios componentes. La respuesta a esta pregunta es la organización de la interacción de los componentes.

La interacción de componentes en un proyecto Vue.js se puede organizar utilizando los siguientes mecanismos:

  1. Las propiedades (accesorios) se utilizan al transferir datos de componentes principales a componentes secundarios.
  2. El método $ emit () se usa al transferir datos de componentes secundarios a componentes principales.
  3. El bus de eventos global (EventBus) se usa cuando se usan estructuras de componentes con anidamiento profundo, o cuando es necesario, a escala global, organizar un intercambio entre componentes de acuerdo con el modelo de editor / suscriptor.

Para comprender el concepto de interacción de componentes en Vue.js, agregamos dos componentes al proyecto:

  • El componente Addque se utilizará para agregar nuevas tareas al sistema y editar tareas existentes.
  • Un componente NoteViewerdiseñado para mostrar información sobre una tarea.

Aquí está el archivo componente 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>

Aquí está el archivo componente 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>

Ahora que se crean los componentes, examinaremos sus secciones <script>.

El sitio propsanunció algunos objetos con sus tipos. Estos son los objetos que vamos a pasar al componente cuando se mostrará en alguna página de la aplicación.

Además, preste atención a las partes del código donde se usa el método $emit(). Con él, un componente secundario genera eventos por los cuales los datos se pasan al componente primario.

Hablemos sobre cómo usar los componentes en la aplicación Addy NoteViewer. En el archivo a Home.vuecontinuación, describimos los mecanismos para transmitir datos a estos componentes y los mecanismos para escuchar los eventos generados por ellos:

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

Ahora, si observa detenidamente este código, notará que el componente que Addlleva el nombre note-editorse aplica dos veces. Una vez, para agregar una nota, una segunda vez, para actualizar su contenido.

Además, reutilizamos el componente NoteViewerpresentado aquí como note-viewer, mostrando con él una lista de notas cargadas desde la base de datos, que iteramos a través del atributo v-for.

Todavía vale la pena prestar atención a un evento @cancelque se usa en el elemento note-editor, que es para operaciones Addy se Updatemaneja de manera diferente, a pesar de que estas operaciones se implementan sobre la base del mismo componente.

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

Así es como se pueden evitar los problemas de escala. El punto es que si hay una probabilidad de un cambio en la implementación de cierto mecanismo, entonces en tal situación el componente simplemente genera el evento correspondiente.

Cuando trabajamos con componentes, utilizamos la inyección dinámica de datos. Por ejemplo, el atributo :notec note-viewer.

Eso es todo. Ahora nuestros componentes pueden intercambiar datos.

Usando la Biblioteca Axios


Axios es una biblioteca basada en Promise para organizar interacciones con varios servicios externos.

Tiene muchas características y se centra en el trabajo seguro. Estamos hablando del hecho de que Axios admite protección contra ataques XSRF, interceptores de solicitud y respuesta, medios para convertir datos de solicitud y respuesta, admite cancelación de solicitud y mucho más.

Conectaremos la biblioteca Axios a la aplicación y la configuraremos para que no tengamos que importarla cada vez que la usemos. Crea un axiosarchivo en la carpeta 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 });

Agregue un main.jsinterceptor de respuesta al archivo , diseñado para interactuar con una API externa. Utilizaremos el interceptor para preparar los datos transferidos a la aplicación y para manejar los errores.

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;

Ahora agregue a la main.jsvariable global $http:

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

Podremos trabajar con esta variable en toda la aplicación a través de una instancia de Vue.js.

Ahora estamos listos para hacer solicitudes de API, que pueden verse así:

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

Mejoramiento


Imagine que nuestra aplicación ha crecido a tamaños cuando incluye cientos de componentes y vistas.

Esto afectará el tiempo de carga de la aplicación, ya que todo su código JavaScript se descargará al navegador de una vez. Para optimizar la carga de la aplicación, debemos responder varias preguntas:

  1. ¿Cómo asegurarse de que los componentes y las vistas que no están actualmente en uso no estén cargados?
  2. ¿Cómo reducir el tamaño de los materiales descargados?
  3. ¿Cómo mejorar el tiempo de carga de la aplicación?

Como respuesta a estas preguntas, se puede sugerir lo siguiente: cargar inmediatamente la estructura básica de la aplicación y cargar componentes y vistas cuando sea necesario. Lo haremos utilizando las capacidades de Webpack y realizando los siguientes cambios en la configuración del enrutador:

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

Esto le permite crear fragmentos separados para una ruta particular con materiales de aplicación (vista [view].[hash].js) que se cargan en modo diferido cuando el usuario visita esta ruta.

Empaquetar un proyecto en un contenedor Docker y desplegarlo


Ahora la aplicación funciona como debería, lo que significa que es hora de contenerla. Agregue el siguiente archivo al proyecto 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;"]

Cuando usamos la aplicación en producción, la colocamos detrás de un poderoso servidor HTTP como Nginx. Esto protege la aplicación de piratería y otros ataques.

¿Recuerda la variable de entorno que contiene la información de host que declaramos al configurar Axios? Aqui esta ella:

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

Como se trata de una aplicación de navegador, debemos establecer y pasar esta variable a la aplicación durante su ensamblaje. Es muy simple hacer esto usando la opción --build-argal ensamblar la imagen:

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

Prestar atención a lo que hay que sustituir <Scheme>, <ServiceHost>y <ServicePort>con los valores que tienen sentido para su proyecto.

Después de ensamblar el contenedor de la aplicación, se puede iniciar:

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

Resumen


Examinamos el proceso de desarrollo de una aplicación basada en Vue.js, hablamos sobre algunas herramientas auxiliares y abordamos problemas de optimización del rendimiento. Ahora con nuestra aplicación puedes experimentar en el navegador. Aquí hay un video que muestra cómo trabajar con él.

¡Queridos lectores! ¿Qué le aconsejaría que prestara atención a los principiantes que buscan desarrollar aplicaciones Vue.js de alto rendimiento que se adapten bien?

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


All Articles