Mengembangkan aplikasi yang lebih cepat di Vue.js

JavaScript adalah jiwa dari aplikasi web modern. Ini adalah bahan utama dalam pengembangan front-end. Ada berbagai kerangka kerja JavaScript untuk membuat antarmuka proyek web. Vue.js adalah salah satu kerangka kerja ini, yang dapat dikaitkan dengan solusi yang cukup populer.

Vue.js adalah kerangka kerja progresif untuk membuat antarmuka pengguna. Pustaka intinya ditujukan terutama untuk menciptakan bagian antarmuka yang terlihat. Perpustakaan lain dapat dengan mudah diintegrasikan ke dalam proyek berbasis Vue jika perlu. Selain itu, dengan bantuan Vue.js dan dengan keterlibatan alat-alat modern dan perpustakaan tambahan, dimungkinkan untuk membuat aplikasi satu halaman yang kompleks.



Artikel ini akan menjelaskan proses pembuatan aplikasi Vue.js sederhana yang dirancang untuk bekerja dengan catatan tentang tugas-tugas tertentu. Berikut adalah repositori frontend proyek. Ini adalah gudang backendnya. Kami, di sepanjang jalan, akan menganalisis beberapa fitur Vue.js dan alat bantu yang kuat.

Pembuatan proyek


Sebelum kita beralih ke pengembangan, mari kita buat dan konfigurasikan proyek dasar aplikasi manajemen tugas kita.

  1. Buat proyek baru menggunakan antarmuka baris perintah Vue.js 3:

    vue create notes-app
  2. Tambahkan file package.jsonberikut ke proyek :

    {
      "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. Atur dependensi yang dijelaskan dalam package.json:

    npm install

Sekarang, setelah database aplikasi siap, kita dapat beralih ke langkah selanjutnya untuk mengatasinya.

Rute


Routing adalah salah satu fitur hebat dari aplikasi web modern. Router dapat diintegrasikan ke dalam aplikasi Vue.js menggunakan perpustakaan vue-router. Ini adalah router resmi untuk proyek Vue.js. Di antara fitur-fiturnya, kami perhatikan hal berikut:

  • Rute / tampilan bersarang.
  • Konfigurasi modular dari router.
  • Akses ke parameter rute, permintaan, templat.
  • Animasi transisi representasi berdasarkan kemampuan Vue.js.
  • Kontrol navigasi yang nyaman.
  • Dukungan untuk penataan otomatis tautan aktif.
  • Dukungan untuk riwayat HTML5-API, kemampuan untuk menggunakan hash URL, secara otomatis beralih ke mode kompatibilitas IE9.
  • Perilaku gulir halaman yang dapat disesuaikan.

Untuk menerapkan perutean di aplikasi kita, buat, di folder router, file index.js. Tambahkan kode berikut ke dalamnya:

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;

Pertimbangkan objek routesyang menyertakan deskripsi rute yang didukung oleh aplikasi. Rute bersarang digunakan di sini.

Objek childrenberisi rute bersarang yang akan ditampilkan pada halaman aplikasi yang mewakili panel kontrol (file DashboardLayout.vue). Inilah templat untuk halaman ini:

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

Dalam kode ini, yang paling penting adalah tag router-view. Ini memainkan peran wadah yang berisi semua komponen yang sesuai dengan rute yang ditampilkan.

Dasar-dasar Komponen


Komponen adalah komponen inti dari aplikasi Vue.js. Mereka memberi kita kesempatan untuk menggunakan pendekatan modular untuk pengembangan, yang berarti memecah halaman DOM menjadi beberapa fragmen kecil yang dapat digunakan kembali pada halaman yang berbeda.

Saat mendesain komponen, untuk membuatnya terukur dan cocok untuk digunakan kembali, ada beberapa hal penting yang perlu dipertimbangkan:

  1. Identifikasi bagian fungsionalitas terpisah yang dapat dipilih dari proyek sebagai komponen.
  2. Jangan membebani komponen dengan kemampuan yang tidak sesuai dengan fungsi utamanya.
  3. Hanya sertakan kode yang akan digunakan untuk memastikan operasinya sendiri. Misalnya, ini adalah kode yang memastikan pengoperasian binding data standar untuk komponen tertentu, seperti tahun, jenis kelamin pengguna, dan sebagainya.
  4. Jangan menambahkan kode ke komponen yang menyediakan mekanisme eksternal untuk komponen tersebut, misalnya, dengan API tertentu.

Di sini, sebagai contoh sederhana, Anda dapat mempertimbangkan bilah navigasi - komponen NavBaryang hanya berisi deskripsi struktur DOM yang terkait dengan alat navigasi untuk aplikasi. Kode komponen terkandung dalam file 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>

Begini cara komponen ini digunakan 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>

Interaksi Komponen


Dalam setiap aplikasi web, pengaturan aliran data yang benar sangat penting. Ini memungkinkan Anda untuk memanipulasi dan mengelola data aplikasi secara efektif.

Saat menggunakan pendekatan komponen, saat memisahkan markup dan kode aplikasi menjadi bagian-bagian kecil, pengembang bertanya bagaimana cara mentransfer dan memproses data yang digunakan oleh berbagai komponen. Jawaban atas pertanyaan ini adalah organisasi interaksi komponen.

Interaksi komponen dalam proyek Vue.js dapat diatur menggunakan mekanisme berikut:

  1. Properti (alat peraga) digunakan ketika mentransfer data dari komponen induk ke komponen turunan.
  2. Metode $ emit () digunakan ketika mentransfer data dari komponen turunan ke komponen induk.
  3. Global event bus (EventBus) digunakan ketika struktur komponen dengan sarang dalam digunakan, atau bila perlu, dalam skala global, untuk mengatur pertukaran antar komponen menggunakan model penerbit / pelanggan.

Untuk memahami konsep interaksi komponen di Vue.js, kami menambahkan dua komponen ke proyek:

  • Komponen Addyang akan digunakan untuk menambah tugas baru ke sistem dan untuk mengedit tugas yang ada.
  • Komponen yang NoteViewerdirancang untuk menampilkan informasi tentang satu tugas.

Berikut adalah file komponen 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>

Berikut adalah file komponen 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>

Sekarang komponen sudah dibuat, mari kita periksa bagian mereka <script>.

Situs propsmengumumkan beberapa objek dengan tipenya. Ini adalah objek yang akan kita lewati ke komponen ketika akan ditampilkan pada beberapa halaman aplikasi.

Selain itu, perhatikan bagian-bagian kode tempat metode ini digunakan $emit(). Dengan itu, komponen anak menghasilkan peristiwa dengan mana data dilewatkan ke komponen induk.

Mari kita bicara tentang cara menggunakan komponen dalam aplikasi Adddan NoteViewer. Mari kita jelaskan dalam file di Home.vuebawah ini mekanisme untuk mentransmisikan data ke komponen-komponen ini dan mekanisme untuk mendengarkan peristiwa yang dihasilkan oleh mereka:

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

Sekarang, jika Anda memperhatikan kode ini dengan seksama, Anda akan melihat bahwa komponen yang Addmemuat nama note-editortersebut diterapkan dua kali. Sekali - untuk menambahkan catatan, untuk kedua kalinya - untuk memperbarui kontennya.

Selain itu, kami menggunakan kembali komponen yang NoteViewerdisajikan di sini sebagai note-viewer, daftar dengan bantuannya daftar catatan yang dimuat dari database, yang kami iterate melalui atribut v-for.

Masih ada baiknya memperhatikan suatu peristiwa @cancelyang digunakan dalam elemen note-editor, yang untuk operasi Adddan Updateditangani secara berbeda, terlepas dari kenyataan bahwa operasi ini dilaksanakan berdasarkan komponen yang sama.

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

Inilah bagaimana masalah penskalaan dapat dihindari. Intinya adalah bahwa jika ada kemungkinan perubahan dalam implementasi mekanisme tertentu, maka dalam situasi seperti itu komponen hanya menghasilkan peristiwa yang sesuai.

Saat bekerja dengan komponen, kami menggunakan injeksi data dinamis. Misalnya, atribut :notec note-viewer.

Itu saja. Sekarang komponen kami dapat bertukar data.

Menggunakan Perpustakaan Axios


Axios adalah perpustakaan berbasis janji untuk mengatur interaksi dengan berbagai layanan eksternal.

Ini memiliki banyak fitur dan fokus pada pekerjaan yang aman. Kita berbicara tentang fakta bahwa Axios mendukung perlindungan terhadap serangan XSRF, pencegat permintaan dan respons, sarana untuk mengonversi data permintaan dan tanggapan, mendukung pembatalan permintaan, dan banyak lagi.

Kami akan menghubungkan perpustakaan Axios ke aplikasi dan mengkonfigurasinya sehingga kami tidak perlu mengimpornya setiap kali kami menggunakannya. Buat axiosfile di folder 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 });

Tambahkan main.jspencegat respons ke file , yang dirancang untuk berinteraksi dengan API eksternal. Kami akan menggunakan interseptor untuk menyiapkan data yang ditransfer ke aplikasi dan untuk menangani kesalahan.

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;

Sekarang tambahkan ke main.jsvariabel global $http:

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

Kami akan dapat bekerja dengan variabel ini di seluruh aplikasi melalui instance Vue.js.

Sekarang kami siap untuk membuat permintaan API, yang mungkin terlihat seperti ini:

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

Optimasi


Bayangkan aplikasi kita telah berkembang ke ukuran ketika itu termasuk ratusan komponen dan tampilan.

Ini akan memengaruhi waktu pemuatan aplikasi, karena semua kode JavaScript-nya akan diunduh ke browser dalam sekali jalan. Untuk mengoptimalkan pemuatan aplikasi, kita perlu menjawab beberapa pertanyaan:

  1. Bagaimana cara memastikan bahwa komponen dan tampilan yang sedang tidak digunakan tidak dimuat?
  2. Bagaimana cara mengurangi ukuran materi yang diunduh?
  3. Bagaimana cara meningkatkan waktu pemuatan aplikasi?

Sebagai jawaban untuk pertanyaan-pertanyaan ini, berikut ini dapat disarankan: segera memuat struktur dasar aplikasi, dan memuat komponen dan tampilan saat dibutuhkan. Kami akan melakukan ini dengan menggunakan kemampuan Webpack dan membuat perubahan berikut pada pengaturan router:

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

Ini memungkinkan Anda untuk membuat fragmen terpisah untuk rute tertentu dengan materi aplikasi (tampilan [view].[hash].js) yang dimuat dalam mode malas ketika pengguna mengunjungi rute ini.

Mengemas proyek dalam wadah Docker dan menggunakannya


Sekarang aplikasi berfungsi sebagaimana mestinya, yang berarti saatnya untuk menyimpannya. Tambahkan file berikut ke proyek 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;"]

Saat menggunakan aplikasi dalam produksi, kami menempatkannya di belakang server HTTP yang kuat seperti Nginx. Ini melindungi aplikasi dari peretasan dan serangan lainnya.

Ingat variabel lingkungan yang berisi informasi host yang kami nyatakan saat menyiapkan Axios? Ini dia:

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

Karena ini adalah aplikasi browser, kita perlu mengatur dan meneruskan variabel ini ke aplikasi selama perakitannya. Sangat mudah untuk melakukan ini dengan menggunakan opsi --build-argsaat merakit gambar:

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

Perhatikan apa yang Anda perlu ganti <Scheme>, <ServiceHost>dan <ServicePort>nilai-nilai yang masuk akal untuk proyek Anda.

Setelah wadah aplikasi terpasang, dapat diluncurkan:

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

Ringkasan


Kami memeriksa proses pengembangan aplikasi berdasarkan Vue.js, berbicara tentang beberapa alat bantu, dan menyentuh masalah optimisasi kinerja. Sekarang dengan aplikasi kami Anda dapat bereksperimen di browser. Berikut adalah video yang menunjukkan cara bekerja dengannya.

Pembaca yang budiman! Apa yang Anda sarankan untuk memperhatikan pemula yang ingin mengembangkan aplikasi Vue.js berkinerja tinggi yang berskala baik?

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


All Articles