在Vue.js上开发更快的应用程序

JavaScript是现代Web应用程序的灵魂。这是前端开发中的主要成分。有多种用于创建Web项目界面的JavaScript框架。 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

现在,在准备好应用程序数据库之后,我们可以继续进行下一步。

路由


路由是现代Web应用程序的重要功能之一。可以使用该库将路由器集成到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>

在此代码中,最重要的是tag router-view它充当包含所有与显示的路线相对应的组件的容器的角色。

组件基础


组件是Vue.js应用程序的核心组件。它们为我们提供了使用模块化方法进行开发的机会,这意味着将DOM页面分为几个小片段,可以在不同页面上重复使用。

在设计组件时,为了使它们可伸缩并适合重用,需要考虑以下重要事项:

  1. 标识可以从项目中选择为组件的单独功能。
  2. 请勿使组件过载与其主要功能不符的功能。
  3. 仅包括将用于确保其自身操作的代码。例如,这是确保特定组件(例如,年份,用户性别等等)的标准数据绑定操作的代码。
  4. 请勿将代码添加到提供与组件外部机制(例如某些API)一起工作的组件。

在这里,作为一个简单的示例,您可以考虑导航栏-一个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>

组件互动


在任何Web应用程序中,正确组织数据流都是非常重要的。这使您可以有效地操纵和管理应用程序数据。

当使用组件方法时,将标记和应用程序代码分成小部分时,开发人员会询问如何传输和处理各种组件使用的数据。这个问题的答案是组件交互的组织。

可以使用以下机制来组织Vue.js项目中组件的交互:

  1. 将数据从父组件传输到子组件时,将使用属性(props)。
  2. 将数据从子组件传输到父组件时,将使用$酋长()方法。
  3. 当使用具有深层嵌套的组件结构时,或者在必要时在全局范围内根据发布者/订阅者模型来组织组件之间的交换时,将使用全局事件总线(EventBus)。

为了理解Vue.js中组件交互的概念,我们向项目添加了两个组件:

  • 该组件Add将被用于新的任务添加到系统中,并编辑现有任务。
  • NoteViewer设计用来显示有关一项任务的信息的组件

这是组件文件AddAdd.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>

这是组件文件NoteViewerNoteViewer.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>

创建了组件之后,我们将检查其section <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)" />

这样可以避免缩放问题。关键是,如果某种机制的实现可能发生变化,那么在这种情况下,组件将简单地生成相应的事件。

在处理组件时,我们使用动态数据注入。例如,属性:notec note-viewer

就这样。现在我们的组件可以交换数据了。

使用Axios库


Axios是一个基于Promise的库,用于组织与各种外部服务的交互。

它具有许多功能,并且专注于安全工作。我们正在谈论一个事实,即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 文件添加响应拦截器,该拦截器旨在与外部API进行交互。我们将使用拦截器来准备传输到应用程序的数据并处理错误。

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当用户访问此路由时以惰性模式加载的应用程序(视图材料为特定路由创建单独的片段

将项目打包到Docker容器中并进行部署


现在,该应用程序可以正常工作,这意味着是时候对其进行容器化了。将以下文件添加到项目中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