Desenvolvendo aplicativos mais rápidos no Vue.js

JavaScript é a alma das aplicações web modernas. Este é o principal ingrediente do desenvolvimento front-end. Existem várias estruturas JavaScript para criar interfaces de projeto da web. O Vue.js é uma dessas estruturas, que pode ser atribuída a soluções bastante populares.

Vue.js é uma estrutura progressiva para criar interfaces de usuário. Sua biblioteca principal visa principalmente a criação da parte visível das interfaces. Outras bibliotecas podem ser facilmente integradas a um projeto baseado no Vue, se necessário. Além disso, com a ajuda do Vue.js e o envolvimento de ferramentas modernas e bibliotecas auxiliares, é possível criar aplicativos complexos de página única.



Este artigo descreve o processo de criação de um aplicativo Vue.js. simples, projetado para trabalhar com notas sobre determinadas tarefas. Aqui está o repositório de front-end do projeto. Aqui está o repositório de seu back-end. Ao longo do caminho, analisaremos alguns recursos poderosos do Vue.js e ferramentas auxiliares.

Criação de projeto


Antes de avançarmos para o desenvolvimento, vamos criar e configurar o projeto básico do nosso aplicativo de gerenciamento de tarefas.

  1. Crie um novo projeto usando a interface da linha de comando Vue.js 3:

    vue create notes-app
  2. Adicione o package.jsonseguinte arquivo ao projeto :

    {
      "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. Defina as dependências descritas em package.json:

    npm install

Agora, depois que o banco de dados do aplicativo estiver pronto, podemos avançar para a próxima etapa do trabalho.

Encaminhamento


O roteamento é um dos grandes recursos dos aplicativos da web modernos. O roteador pode ser integrado ao aplicativo Vue.js. usando a biblioteca vue-router. Este é o roteador oficial para projetos Vue.js. Entre suas características, destacamos o seguinte:

  • Rotas / visualizações aninhadas.
  • Configuração modular do roteador.
  • Acesso a parâmetros de rota, solicitações, modelos.
  • Animação de transições de representações com base nos recursos do Vue.js.
  • Controle de navegação conveniente.
  • Suporte para o estilo automático de links ativos.
  • O suporte ao histórico da API HTML5, a capacidade de usar hashes de URL, alterna automaticamente para o modo de compatibilidade IE9.
  • Comportamento de rolagem de página personalizável.

Para implementar o roteamento em nosso aplicativo, crie, em uma pasta router, um arquivo index.js. Adicione o seguinte código a ele:

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 um objeto routesque inclua uma descrição das rotas suportadas pelo aplicativo. As rotas aninhadas são usadas aqui.

O objeto childrencontém rotas aninhadas que serão mostradas na página do aplicativo que representa seu painel de controle (arquivo DashboardLayout.vue). Aqui está o modelo para esta página:

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

Nesse código, o mais importante é a tag router-view. Ele desempenha o papel de um contêiner que contém todos os componentes correspondentes à rota exibida.

Noções básicas sobre componentes


Componentes são o componente principal dos aplicativos Vue.js. Eles nos dão a oportunidade de usar uma abordagem modular para o desenvolvimento, o que significa dividir as páginas DOM em vários pequenos fragmentos que podem ser reutilizados em páginas diferentes.

Ao projetar componentes, para torná-los escaláveis ​​e adequados para reutilização, há algumas coisas importantes a serem consideradas:

  1. Identifique uma parte separada da funcionalidade que pode ser selecionada no projeto como um componente.
  2. Não sobrecarregue o componente com recursos que não correspondem à sua funcionalidade principal.
  3. Inclua apenas o código que será usado para garantir sua própria operação. Por exemplo, este é um código que garante a operação de ligações de dados padrão para um componente, como ano, sexo do usuário etc.
  4. Não adicione código ao componente que fornece trabalho com mecanismos externos ao componente, por exemplo, com determinadas APIs.

Aqui, como um exemplo simples, você pode considerar a barra de navegação - um componente NavBarque contém apenas descrições das estruturas DOM relacionadas às ferramentas de navegação do aplicativo. O código do componente está contido no arquivo 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>

Veja como esse componente é usado em 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>

Interação de componentes


Em qualquer aplicativo da Web, a organização adequada dos fluxos de dados é extremamente importante. Isso permite que você manipule e gerencie efetivamente os dados do aplicativo.

Ao usar a abordagem de componente, ao separar a marcação e o código do aplicativo em pequenas partes, o desenvolvedor pergunta como transferir e processar os dados usados ​​por vários componentes. A resposta para esta pergunta é a organização da interação dos componentes.

A interação dos componentes em um projeto Vue.js. pode ser organizada usando os seguintes mecanismos:

  1. As propriedades (adereços) são usadas ao transferir dados dos componentes pai para os componentes filhos.
  2. O método $ emit () é usado ao transferir dados de componentes filho para componentes pai.
  3. O barramento de eventos global (EventBus) é usado quando estruturas de componentes com aninhamento profundo são usadas, ou quando é necessário, em escala global, para organizar uma troca entre componentes de acordo com o modelo do publicador / assinante.

Para entender o conceito de interação de componentes no Vue.js, adicionamos dois componentes ao projeto:

  • O componente Addque será usado para adicionar novas tarefas ao sistema e editar tarefas existentes.
  • Um componente NoteViewerprojetado para exibir informações sobre uma tarefa.

Aqui está o arquivo do 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>

Aqui está o arquivo do 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>

Agora que os componentes foram criados, examinaremos suas seções <script>.

O site propsanunciou alguns objetos com seus tipos. Esses são os objetos que passaremos para o componente quando ele for exibido em alguma página do aplicativo.

Além disso, preste atenção nas partes do código em que o método é usado $emit(). Com ele, um componente filho gera eventos pelos quais os dados são passados ​​para o componente pai.

Vamos falar sobre como usar os componentes no aplicativo Adde NoteViewer. Vamos descrever no arquivo Home.vueabaixo os mecanismos de transmissão de dados para esses componentes e os mecanismos de escuta dos eventos gerados por eles:

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

Agora, se você observar atentamente esse código, notará que o componente Addcom o nome note-editoré aplicado duas vezes. Uma vez - para adicionar uma nota, uma segunda vez - para atualizar seu conteúdo.

Além disso, podemos reutilizar o componente NoteViewerapresentado aqui como note-viewer, listando com a sua ajuda uma lista de notas carregados do banco de dados, que iterate através do atributo v-for.

Ainda vale a pena prestar atenção a um evento @cancelque é usado no elemento note-editor, que é para operações Adde Updatetratado de maneira diferente, apesar do fato de que essas operações são implementadas com base no mesmo 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)" />

É assim que problemas de dimensionamento podem ser evitados. O ponto é que, se houver uma probabilidade de mudança na implementação de um determinado mecanismo, em tal situação, o componente simplesmente gera o evento correspondente.

Ao trabalhar com componentes, usamos injeção dinâmica de dados. Por exemplo, atributo :notec note-viewer.

Isso é tudo. Agora nossos componentes podem trocar dados.

Usando a Biblioteca Axios


Axios é uma biblioteca baseada em Promise para organizar interações com vários serviços externos.

Possui muitos recursos e está focado no trabalho seguro. Estamos falando do fato de o Axios oferecer suporte à proteção contra ataques XSRF, interceptadores de solicitação e resposta, meios para converter dados de solicitação e resposta, suportar cancelamento de solicitação e muito mais.

Vamos conectar a biblioteca do Axios ao aplicativo e configurá-lo para que não seja necessário importá-lo sempre que o usarmos. Crie um axiosarquivo na pasta 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 });

Adicione um main.jsinterceptador de resposta ao arquivo , projetado para interagir com uma API externa. Usaremos o interceptador para preparar os dados transferidos para o aplicativo e para lidar com erros.

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;

Agora adicione à main.jsvariável global $http:

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

Poderemos trabalhar com essa variável em todo o aplicativo por meio de uma instância do Vue.js.

Agora estamos prontos para fazer solicitações de API, que podem ser assim:

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

Otimização


Imagine que nosso aplicativo tenha crescido em tamanho quando inclui centenas de componentes e visualizações.

Isso afetará o tempo de carregamento do aplicativo, pois todo o seu código JavaScript será baixado no navegador de uma só vez. Para otimizar o carregamento do aplicativo, precisamos responder a várias perguntas:

  1. Como garantir que os componentes e visualizações que não estão atualmente em uso não sejam carregados?
  2. Como reduzir o tamanho dos materiais baixados?
  3. Como melhorar o tempo de carregamento do aplicativo?

Como resposta a essas perguntas, pode-se sugerir o seguinte: carregue imediatamente a estrutura básica do aplicativo e carregue componentes e visualizações quando necessário. Faremos isso usando os recursos do Webpack e fazendo as seguintes alterações nas configurações do roteador:

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

Isso permite criar fragmentos separados para uma rota específica com materiais de aplicativo (exibição [view].[hash].js) carregados no modo lento quando o usuário visita essa rota.

Empacotando um projeto em um contêiner Docker e implantando-o


Agora, o aplicativo funciona como deveria, o que significa que é hora de contê-lo. Adicione o seguinte arquivo ao projeto 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;"]

Ao usar o aplicativo em produção, o colocamos atrás de um poderoso servidor HTTP como o Nginx. Isso protege o aplicativo contra hackers e outros ataques.

Lembra da variável de ambiente que contém as informações do host que declaramos ao configurar o Axios? Aqui está ela:

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

Como esse é um aplicativo de navegador, precisamos definir e passar essa variável para o aplicativo durante sua montagem. É muito simples fazer isso usando a opção --build-argao montar a imagem:

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

Preste atenção ao que você precisa substituir <Scheme>, <ServiceHost>e <ServicePort>aos valores que fazem sentido para o seu projeto.

Após a montagem do contêiner do aplicativo, ele pode ser iniciado:

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

Sumário


Examinamos o processo de desenvolvimento de um aplicativo baseado no Vue.js, conversamos sobre algumas ferramentas auxiliares e abordamos questões de otimização de desempenho. Agora, com o nosso aplicativo, você pode experimentar no navegador. Aqui está um vídeo demonstrando como trabalhar com ele.

Queridos leitores! O que você recomendaria para prestar atenção aos iniciantes que desejam desenvolver aplicativos Vue.js. de alto desempenho que escalam bem?

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


All Articles