تطوير تطبيقات أسرع على Vue.js

جافا سكريبت هي روح تطبيقات الويب الحديثة. هذا هو العنصر الرئيسي في تطوير الواجهة الأمامية. هناك العديد من أطر عمل جافا سكريبت لإنشاء واجهات مشروع الويب. Vue.js هو أحد هذه الأطر ، والذي يمكن أن يُعزى إلى حلول شائعة إلى حد ما.

Vue.js هو إطار عمل تقدمي لإنشاء واجهات المستخدم. تهدف مكتبتها الأساسية بشكل أساسي إلى إنشاء الجزء المرئي من الواجهات. يمكن دمج المكتبات الأخرى بسهولة في مشروع قائم على Vue إذا لزم الأمر. بالإضافة إلى ذلك ، بمساعدة Vue.js وباشتراك الأدوات الحديثة والمكتبات المساعدة ، من الممكن إنشاء تطبيقات معقدة من صفحة واحدة.



تصف هذه المقالة عملية إنشاء تطبيق Vue.js بسيط مصمم للعمل مع ملاحظات حول مهام معينة. هنا مستودع الواجهة الأمامية للمشروع. هنا هو مستودع الواجهة الخلفية له. سنقوم ، على طول الطريق ، بتحليل بعض الميزات القوية لـ Vue.js والأدوات المساعدة.

إنشاء المشروع


قبل أن ننتقل إلى التطوير ، لنقم بإنشاء وتكوين المشروع الأساسي لتطبيق إدارة المهام لدينا.

  1. أنشئ مشروعًا جديدًا باستخدام واجهة سطر الأوامر Vue.js 3:

    vue create notes-app
  2. أضف الملف package.jsonالتالي إلى المشروع :

    {
      "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. قم بتعيين التبعيات الموضحة في package.json:

    npm install

الآن ، بعد أن تصبح قاعدة بيانات التطبيق جاهزة ، يمكننا الانتقال إلى الخطوة التالية للعمل عليها.

التوجيه


يعد التوجيه أحد الميزات الرائعة لتطبيقات الويب الحديثة. يمكن دمج الموجه في تطبيق Vue.js باستخدام المكتبة vue-router. هذا هو جهاز التوجيه الرسمي لمشاريع Vue.js. من بين ميزاته ، نلاحظ ما يلي:

  • طرق / طرق عرض متداخلة.
  • التكوين المعياري للموجه.
  • الوصول إلى معلمات المسار والطلبات والقوالب.
  • الرسوم المتحركة لانتقالات التمثيلات على أساس قدرات Vue.js.
  • التحكم في التنقل المريح.
  • دعم التصميم التلقائي للروابط النشطة.
  • دعم سجل HTML5-API ، والقدرة على استخدام تجزئات عناوين URL ، والتبديل تلقائيًا إلى وضع التوافق IE9.
  • سلوك تمرير الصفحة القابل للتخصيص.

لتنفيذ التوجيه في طلبنا، وخلق، في مجلد router، ملف index.js. أضف الكود التالي إليها:

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;

ضع في الاعتبار كائن routesيتضمن وصفًا للمسارات التي يدعمها التطبيق. يتم استخدام المسارات المتداخلة هنا. يحتوي

الكائن childrenعلى مسارات متداخلة سيتم عرضها في صفحة التطبيق التي تمثل لوحة (ملف DashboardLayout.vue) التحكم الخاصة به . هنا هو قالب هذه الصفحة:

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

في هذا الكود ، الأهم هو الوسم router-view. تلعب دور الحاوية التي تحتوي على جميع المكونات المقابلة للمسار المعروض.

أساسيات المكون


المكونات هي المكون الأساسي لتطبيقات Vue.js. تتيح لنا الفرصة لاستخدام نهج معياري للتطوير ، مما يعني تقسيم صفحات DOM إلى عدة أجزاء صغيرة يمكن إعادة استخدامها على صفحات مختلفة.

عند تصميم المكونات ، من أجل جعلها قابلة للتطوير ومناسبة لإعادة الاستخدام ، هناك بعض الأشياء المهمة التي يجب مراعاتها:

  1. حدد قطعة منفصلة من الوظائف التي يمكن اختيارها من المشروع كمكون.
  2. لا تفرط في تحميل المكون بقدرات لا تتوافق مع وظيفته الرئيسية.
  3. قم بتضمين الرمز الذي سيتم استخدامه لضمان تشغيله فقط. على سبيل المثال ، هذا هو الرمز الذي يضمن تشغيل عمليات ربط البيانات القياسية لمكون معين ، مثل السنة ونوع المستخدم وما إلى ذلك.
  4. لا تقم بإضافة رمز إلى المكون الذي يوفر العمل مع الآليات الخارجية للمكون ، على سبيل المثال ، مع واجهات برمجة تطبيقات معينة.

هنا ، كمثال بسيط ، يمكنك النظر في شريط التنقل - وهو مكون NavBarيحتوي فقط على أوصاف هياكل DOM المتعلقة بأدوات التنقل للتطبيق. كود المكون موجود في الملف 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>

إليك كيفية استخدام هذا المكون في 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>

تفاعل المكونات


في أي تطبيق ويب ، التنظيم المناسب لتدفقات البيانات مهم للغاية. هذا يسمح لك بمعالجة وإدارة بيانات التطبيق بشكل فعال.

عند استخدام نهج المكون ، عند فصل الترميز ورمز التطبيق إلى أجزاء صغيرة ، يسأل المطور كيفية نقل ومعالجة البيانات المستخدمة من قبل المكونات المختلفة. الجواب على هذا السؤال هو تنظيم تفاعل المكونات.

يمكن تنظيم تفاعل المكونات في مشروع Vue.js باستخدام الآليات التالية:

  1. يتم استخدام الخصائص (الدعائم) عند نقل البيانات من المكونات الرئيسية إلى المكونات الفرعية.
  2. يتم استخدام الأسلوب $ emit () عند نقل البيانات من المكونات الفرعية إلى المكونات الرئيسية.
  3. يتم استخدام ناقل الأحداث العالمي (EventBus) عند استخدام هياكل المكونات ذات التعشيش العميق ، أو عند الضرورة ، على نطاق عالمي ، لتنظيم تبادل بين المكونات وفقًا لنموذج الناشر / المشترك.

من أجل فهم مفهوم تفاعل المكونات في Vue.js ، نضيف مكونين إلى المشروع:

  • المكون Addالذي سيتم استخدامه لاضافة مهام جديدة للنظام وتحرير المهام الموجودة.
  • مكون NoteViewerمصمم لعرض معلومات حول مهمة واحدة.

هنا هو ملف المكون 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>

هنا هو ملف المكون 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>

الآن بعد أن تم إنشاء المكونات ، سوف نقوم بفحص أقسامها <script>. أعلن

الموقع propsعن بعض الأشياء بأنواعها. هذه هي الكائنات التي سنمررها إلى المكون عندما يتم عرضه على بعض صفحات التطبيق.

بالإضافة إلى ذلك ، انتبه إلى تلك الأجزاء من الرمز حيث يتم استخدام الطريقة $emit(). باستخدامه ، يقوم المكون الفرعي بإنشاء الأحداث التي يتم من خلالها تمرير البيانات إلى المكون الأصلي.

دعونا نتحدث عن كيفية استخدام المكونات في التطبيق Addو NoteViewer. دعنا نوضح في الملف Home.vueأدناه آليات نقل البيانات إلى هذه المكونات وآليات الاستماع إلى الأحداث الناتجة عنها:

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

الآن ، إذا نظرت عن كثب إلى هذا الرمز ، ستلاحظ أن المكون الذي Addيحمل الاسم note-editorيتم تطبيقه مرتين. مرة واحدة - لإضافة ملاحظة ، مرة ثانية - لتحديث محتوياتها.

بالإضافة إلى ذلك ، نعيد استخدام المكون NoteViewerالمعروض هنا على أنه note-viewerيسرد بمساعدته قائمة بالملاحظات المحملة من قاعدة البيانات ، والتي نكررها من خلال السمة v-for.

لا يزال هناك جدير بالاهتمام لحدث @cancelيستخدم في العنصر note-editor، وهو مخصص للعمليات Addويتم Updateالتعامل معه بشكل مختلف ، على الرغم من حقيقة أن هذه العمليات يتم تنفيذها على أساس نفس المكون.

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

هذه هي الطريقة التي يمكن بها تجنب مشاكل التحجيم. النقطة هي أنه إذا كان هناك احتمال لتغيير في تنفيذ آلية معينة ، فعندئذٍ في مثل هذه الحالة ، يقوم المكون ببساطة بإنشاء الحدث المقابل.

عند العمل مع المكونات ، نستخدم حقن البيانات الديناميكية. على سبيل المثال ، السمة :noteج note-viewer.

هذا كل شئ. الآن يمكن لمكوناتنا تبادل البيانات.

استخدام مكتبة أكسيوس


Axios هي مكتبة مبنية على وعود لتنظيم التفاعلات مع الخدمات الخارجية المختلفة.

لديها العديد من الميزات وتركز على العمل الآمن. نحن نتحدث عن حقيقة أن Axios تدعم الحماية ضد هجمات XSRF ، واعتراضات الطلب والاستجابة ، ووسائل تحويل بيانات الطلب والاستجابة ، وتدعم إلغاء الطلبات ، وأكثر من ذلك بكثير.

سنقوم بتوصيل مكتبة Axios بالتطبيق وتكوينه بحيث لا نضطر إلى استيراده في كل مرة نستخدمه. قم بإنشاء axiosملف في المجلد 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 });

أضف main.jsاعتراض استجابة إلى الملف ، مصمم للتفاعل مع واجهة برمجة تطبيقات خارجية. سنستخدم المعترض لإعداد البيانات المنقولة إلى التطبيق ومعالجة الأخطاء.

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;

أضف الآن إلى main.jsالمتغير العام $http:

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

سنكون قادرين على العمل مع هذا المتغير في جميع أنحاء التطبيق من خلال مثيل Vue.js.

الآن نحن على استعداد لتقديم طلبات API ، والتي قد تبدو على النحو التالي:

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

الاقوي


تخيل أن تطبيقنا قد نما إلى الأحجام عندما يتضمن مئات المكونات والمشاهدات.

سيؤثر ذلك على وقت تحميل التطبيق ، نظرًا لأنه سيتم تنزيل جميع رموز JavaScript الخاصة به إلى المتصفح دفعة واحدة. من أجل تحسين تحميل التطبيق ، نحتاج إلى الإجابة على عدة أسئلة:

  1. كيفية التأكد من عدم تحميل المكونات وطرق العرض غير المستخدمة حاليًا؟
  2. كيف تقلل من حجم المواد التي تم تنزيلها؟
  3. كيفية تحسين وقت تحميل التطبيق؟

كإجابة على هذه الأسئلة ، يمكن اقتراح ما يلي: قم بتحميل البنية الأساسية للتطبيق على الفور ، وتحميل المكونات وطرق العرض عند الحاجة. سنفعل ذلك باستخدام إمكانيات Webpack وإجراء التغييرات التالية على إعدادات جهاز التوجيه:

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

يتيح لك هذا إنشاء أجزاء منفصلة لمسار معين باستخدام مواد التطبيق (عرض [view].[hash].js) التي يتم تحميلها في الوضع الكسول عندما يزور المستخدم هذا المسار.

تعبئة مشروع في حاوية دوكر ونشره


يعمل التطبيق الآن كما ينبغي ، مما يعني أن الوقت قد حان لوضعه في حاوية. أضف الملف التالي إلى المشروع 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;"]

عند استخدام التطبيق في الإنتاج ، نضعه خلف خادم HTTP قوي مثل Nginx. هذا يحمي التطبيق من القرصنة والهجمات الأخرى.

هل تذكر متغير البيئة الذي يحتوي على معلومات المضيف التي أعلنا عنها عند إعداد Axios؟ ها هي:

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

نظرًا لأن هذا تطبيق متصفح ، نحتاج إلى تعيين هذا المتغير وتمريره إلى التطبيق أثناء تجميعه. من السهل جدًا القيام بذلك باستخدام الخيار --build-argعند تجميع الصورة:

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

انتبه إلى ما تحتاج إلى استبداله <Scheme>، <ServiceHost>وإلى <ServicePort>القيم المنطقية لمشروعك.

بعد تجميع حاوية التطبيق ، يمكن تشغيلها:

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

ملخص


لقد درسنا عملية تطوير تطبيق قائم على Vue.js ، وتحدثنا عن بعض الأدوات المساعدة ، وتطرقنا إلى مشكلات تحسين الأداء. الآن مع تطبيقنا يمكنك تجربة المتصفح. إليك مقطع فيديو يوضح كيفية العمل معه.

القراء الأعزاء! ما الذي تنصح به للانتباه إلى المبتدئين الذين يسعون إلى تطوير تطبيقات Vue.js عالية الأداء التي تتطور بشكل جيد؟

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


All Articles