Заметки о чистом бэкэнд-колесе vue

внешний интерфейс JavaScript CDN Vue.js
Заметки о чистом бэкэнд-колесе vue

инструкция

В связи с потребностями компании,якакчистыйБэкенд-инженер, более полугода обучающийся Vue-самоучке, был вынужден стать малым фулстеком. новичок с недостаточной глубиной фронтенда и бекенда.Глубоко осознавая их многочисленные недостатки и понимая, что хорошая память не так хороша, каксгнившийПо принципу написания всегда не плохо сделать больше колёс и сделать больше заметок :)

Итак, у меня недавно появилось время, и я написал его, когда только изучал vuejs.плохой проектНемного рефакторинг и сделал некоторые заметки о целевых подмодулях во время рефакторинга следующим образом.

  • маршрутизация
  • государственное управление
  • управление полномочиями
  • Контролируйте упаковку и использование
  • смешать
  • Моделирование данных
  • Оптимизация упаковки и пользовательский опыт

Если не хочешь так долго растягиваться, можешь идтиКрупнейший в мире сайт гей-знакомствПроверять

Войдите в режим плохого пера

маршрутизация

1. Загрузка маршрута

// 直接加载页面
import page from '@/views/page';
// 懒加载页面
() => import('@/views/page');
// 指定打包名称的懒加载,可将多个页面打包成一个js进行加载
() => import(/* webpackChunkName: "group-page" */'@/views/page1');
() => import(/* webpackChunkName: "group-page" */'@/views/page2');
() => import(/* webpackChunkName: "group-page" */'@/views/page3');

2. Маршрутизация 404

// 加载一个404页面
import page404 from '@/views/page404';
// 将以下路由配置放置在路由表的最末端,当路径无法匹配前面的所有路由时将会跳转至page404组件页面
{ path: '*', component: page404}

3. Перехват маршрута

// 路由跳转前的拦截器
router.beforeEach((to, from, next) => {
  
});
// 路由跳转后的拦截器
router.afterEach(to => {

});
// 路由跳转时出现错误时的拦截器
router.onError((err) => {

});

4. Динамическая маршрутизация

Динамическая маршрутизация обычно используется с контролем разрешений на уровне страниц.

// 通过router.addRoutes方法动态添加可访问路由
router.addRoutes(addRouters)
// hack方法 确保addRoutes已完成
next({ ...to, replace: true }) // set the replace: true so the navigation will not leave a history record 

5. Анимация при загрузке маршрута

Анимация загрузки при загрузке маршрута обычно используется в сочетании с ленивой загрузкой маршрута.

// 在状态管理中定义一个路由loading标志
const app = {
  state: {
    routerLoading: false, //路由的loading过渡
  },
  mutations: {
    //修改路由loading状态
    UPDATE_ROUTER_LOADING(state, status) {
      state.routerLoading = status
    }
  }
}

// 在路由拦截器中修改loading状态
router.beforeEach((to, from, next) => {
  store.commit('UPDATE_ROUTER_LOADING', true); // 展示路由加载时动画
});
router.afterEach(to => {
  store.commit('UPDATE_ROUTER_LOADING', false);
});
router.onError(err => {
  console.error(err); // for bug
  store.commit('UPDATE_ROUTER_LOADING', false);
});

// 在router-view定义loading动画
// element-ui提供了v-loading指令可以直接使用
<router-view v-loading="$store.getters.routerLoading"></router-view>

государственное управление

1. Мало знаний

  • Изменение данных в состоянии должно быть вызвано мутацией или действием.
  • Методы в мутации должны быть синхронизированными функциями
  • action может содержать любую асинхронную операцию и может возвращать Promise
  • Мутация и действие могут повторяться, они будут вызываться последовательно при вызове, а геттер должен быть уникальным

2. Мультимодуль

Когда бизнес более сложный, вы можете использовать несколько модулей в управлении состоянием.Существуют следующие меры предосторожности.

  • За исключением состояния, которое будет добавлено в соответствии с псевдонимом модуля при объединении, остальные объединяются под корневым уровнем, поэтому геттеры, коммиты и диспетчеры, полученные функцией обратного вызова, являются глобальными.
  • Параметром обратного вызова мутации является только состояние, которое представляет собой дерево состояний текущего модуля, то же самое ниже
  • Параметры обратного вызова действия: состояние, rootState, геттеры, фиксация, отправка.Если вам нужно вызвать другие действия в действии, вы можете использовать диспетчер для прямого вызова
  • Параметры обратного вызова геттера: состояние, rootState, геттеры.
  • Модули могут взаимодействовать через rootState обратного вызова.
  • Мутации и действия с одинаковыми именами будут запускаться последовательно
// 多模块的实现 app以及user为各个子模块
export default new Vuex.Store({
    modules: {
        app,
        user
    },
    getters
})

3. Вспомогательные функции

Помимо предоставления объектов Store, Vuex также предоставляет некоторые вспомогательные функции для внешнего мира.

  • mapState и mapGetters сопоставляют свойства состояния и геттеров в хранилище с локальными вычисляемыми свойствами компонента vue.
import { mapState } from 'vuex'
computed: mapState([ 
    // 映射 this.name 到 this.$store.state.name 
    'name'
])

import { mapGetters } from 'vuex'
computed: {
    // 映射 this.name 到 this.$store.getters.name 
    ...mapGetters([ 'name' ])
}
  • mapActions, mapMutations сопоставляют методы отправки и фиксации в хранилище с локальными методами компонента vue.
import { mapActions } from 'vuex'

methods: { 
    // 映射 this.LoginByUsername() 到 this.$store.dispatch('LoginByUsername')
    ...mapActions([ 'LoginByUsername' ]), 
    // 映射 this.login() to this.$store.dispatch('LoginByUsername')
    ...mapActions({ login: 'LoginByUsername'}) 
}

import { mapMutations } from 'vuex'

methods: { 
    // 映射 this.SET_NAME() 到 this.$store.commit('SET_NAME') ])
    ...mapMutations([ 'SET_NAME' ]) , 
    // 映射 this.setName() 到 this.$store.commit('SET_NAME') })
    ...mapMutations({ setName: 'SET_NAME' ])
}

4. Плагин сохранения данных

Используйте этот плагин, если хотите, чтобы состояние не терялось при обновлении страницы.

// 摘抄于 https://github.com/robinvdvleuten/vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'

const store = new Store({
  // ...
  plugins: [
    createPersistedState({
      storage: {
        getItem: key =* Cookies.get(key),
        // Please see https://github.com/js-cookie/js-cookie#json, on how to handle JSON.
        setItem: (key, value) =* Cookies.set(key, value, { expires: 3, secure: true }),
        removeItem: key =* Cookies.remove(key)
      }
    })
  ]
})

5. Плагин журнала

Этот плагин доступен, когда среда разработки хочет иметь возможность отслеживать изменения состояния и вывод.

// createLogger是vuex中的内置插件
import createLogger from 'vuex/dist/logger'

let vuexPlugins = [];
if(process.env.NODE_ENV !== 'production'){ // 开发环境加载该插件
    vuexPlugins.push(createLogger); 
}

const store = new Store({
  // ...
  plugins: vuexPlugins
})

управление полномочиями

1. Функции, которые необходимо реализовать

  • Генерировать маршруты в соответствии с таблицей разрешений после входа пользователя
  • Контроль разрешений на уровне страницы
  • Контроль разрешений на уровне элемента DOM
  • Обработка аннулирования статуса входа

2. Схема маршрутизации

Во-первых, нам нужно спроектировать необходимую информацию о параметрах для объекта маршрутизации.

Чтобы добиться управления разрешениями, у нас должен быть параметр roles, чтобы представлять, какие разрешения должен иметь маршрут для доступа.

Для лучшего отображения роутинга здесь предназначены два параметра title и icon для отображения меню боковой панели.

Некоторые маршруты не нужно отображать на боковой панели, здесь скрытый параметр используется, чтобы сообщить программе, какие маршруты не нужно отображать.

// 首先设计路由对象参数
/**
* hidden: true                   如果hidden为true则在左侧菜单栏展示,默认为false
* name:'router-name'             路由名称,路由唯一标识
* meta : {
    roles: ['admin','editor']    权限列表,用于页面级的权限控制,默认不设置代表任何权限均可访问
    title: 'title'               对应路由在左侧菜单栏的标题名称
    icon: 'icon-class'           对应路由在左侧菜单栏的图标样式
 }
**/

Далее нам нужно реализовать динамическую загрузку маршрутов

При инициализации системы загружаются необходимые маршруты, а затем загружаются квалифицированные маршруты в соответствии с разрешениями вошедшего в систему пользователя.

// 定义系统初始化时加载的必要路由信息
export const constantRouterMap = [
  { path: '/login', name: 'login', meta: { title: "系统登录", hidden: true }, component: login },
  { path: "/404", name: "page404", meta: { title: "页面走丢了", hidden: true }, component: page404 },
  { path: "/401", name: "page401", meta: { title: "权限不足", hidden: true }, component: page401 }
]
// 定义布局页面
const layout = () => import(/* webpackChunkName: "group-index" */ '@/views/layout');
// 定义异步加载的路由信息
export const asyncRouterMap = [
  {
    path: '/',
    name: 'main',
    redirect: '/dashboard',
    hidden: true,
    component: layout,
    children: [
      { path: 'dashboard', name: 'dashboard', meta: { title: "仪表盘" }, component: () => import(/* webpackChunkName: "group-index" */'@/views/dashboard') }
    ]
  },
  {
    path: '/permission',
    name: 'permission',
    meta: { title: "权限页", icon: "dbm d-icon-quanxian" },
    redirect: '/permission/adminpermission',
    component: layout,
    children: [
      { path: "adminpermission", name: "adminPermission", meta: { title: "管理员权限页", roles: ["admin"] }, component: () => import('@/views/permission/admin') },
      { path: "watcherpermission", name: "watcherPermission", meta: { title: "游客权限页", roles: ["admin", "watcher"] }, component: () => import('@/views/permission/watcher') },
      { path: "elementpermission", name: "elementPermission", meta: { title: "元素级别权限" }, component: () => import('@/views/permission/element') }
    ]
  },
  { path: '*', redirect: '/404', hidden: true }
]

3. Контроль разрешений на уровне страницы

Используйте перехват маршрута для реализации контроля разрешений на уровне страницы

Перехватывать переходы по маршруту, чтобы определить, вошел ли пользователь в систему

Извлеките таблицу разрешений из полученной информации о пользователе и динамически загрузите таблицу асинхронной маршрутизации с помощью метода addRoutes.

Каждый раз при переходе маршрута определите, есть ли у пользователя разрешение на доступ к маршруту, чтобы добиться динамического сопоставления разрешений.

// 定义免登白名单
const whiteList = ['/login', '/404', '/401'];
// 拦截路由跳转
router.beforeEach((to, from, next) => {
  store.commit('UPDATE_ROUTER_LOADING', true); // 展示路由加载时动画
  if (getToken()) {  // 存在token
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完用户信息
        store.dispatch('GetUserInfo').then(data => { // 拉取用户信息
          const roles = data.roles // 权限表必须为数组,例如: ['admin','editer']
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch(err => { // 拉取用户信息失败,提示登录状态失效
          store.dispatch('FedLogOut').then(() => {
            Message.error('登录状态失效, 请重新登录');
            next({ path: '/login' });
          })
        })
      } else {
        if (hasPermission(store.getters.roles, to.meta.roles)) { // 动态权限匹配
          next();
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true } });
        }
      }
    }
  } else { // 没有token
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next();
    } else {
      next('/login'); // 否则全部重定向到登录页
    }
  }
});

4. Контроль разрешений на уровне элементов

Используйте пользовательские директивы для реализации контроля разрешений на уровне элементов

Убедитесь, что у пользователя есть необходимые разрешения для связанного элемента, когда он вставлен в родительский узел.

Определите, следует ли удалить элемент в соответствии с результатом аутентификации.

import store from '@/store'

export default {
  inserted(el, binding, vnode) {
    const { value } = binding; // 获取自定义指令传入的鉴权信息
    const roles = store.getters && store.getters.roles; // 从状态管理中获取当前用户的路由信息

    if (value && value instanceof Array && value.length > 0) {
      const permissionRoles = value;

      const hasPermission = roles.some(role => { // 判断用户是否包含该元素所需权限
        return permissionRoles.includes(role);
      })

      if (!hasPermission) { // 权限不足
        el.parentNode && el.parentNode.removeChild(el); // 移除该dom元素
      }
    } else {
      throw new Error(`必须要有权限写入,例如['admin']`)
    }
  }
}

// 在vue组件上使用它
// 引入并注册permission指令
import permission from "@/directive/permission/index.js";
export default {
  directives: {
    permission
  }
}
// 使用permission指令
<el-button v-permission="['admin']">admin 可见</el-button>
<el-button v-permission="['admin','watcher']">watcher 可见</el-button>

функция рендеринга

1. Как инкапсулировать компонент, поддерживающий рендеринг

  • Сначала создайте функциональный компонент
// 表格拓展函数式组件的实现
// see https://github.com/calebman/vue-DBM/blob/master/src/components/table/expand.js
export default {
  name: 'TableExpand',
  functional: true, // 标记组件为 functional,这意味它是无状态 (没有响应式数据),无实例 (没有 this 上下文)。
  props: {
    row: Object, // 当前行对象
    field: String, // 列名称
    index: Number, // 行号
    render: Function // 渲染函数
  },
  render: (h, ctx) => { // 提供ctx作为上下文
    const params = {
      row: ctx.props.row,
      field: ctx.props.field,
      index: ctx.props.index
    };
    return ctx.props.render(h, params);
  }
};
  • Представлен в родительском компоненте
// see https://github.com/calebman/vue-DBM/blob/master/src/components/table/table.vue
import expand from "./expand.js";

<span v-if="typeof col.render ==='function'">
   <expand :field="col.field" :row="item" :render="col.render" :index="rowIndex"></expand>
</span>
  • Рендеринг с помощью функции рендеринга
// see https://github.com/calebman/vue-DBM/blob/master/src/views/demo/datatable/data-table.vue
// 引入自定义组件
import IndexColumn from "@/components/business/index-column.vue";
// 注册
components: {
  // ...
  IndexColumn
}
// 使用
// 获取当前组件的上下文
let self = this;
// 定义渲染函数
render: (h, params) =>
  h("div", [
    h(IndexColumn, {
      props: {
        field: params.field,
        index: params.index,
        pagingIndex:
          (self.pagination.pageCurrent - 1) * self.pagination.pageSize
      },
      on: { "on-value-delete": self.deleteRow }
    })
  ])

смешать

1. Мало знаний

  • Объекты Mixin будут иметь жизненный цикл смешиваемого компонента.
  • Данные компонентов будут иметь приоритет, когда объекты данных смешиваются в конфликтах.
  • Когда параметры объекта (такие как методы, компоненты, директивы) смешиваются в конфликт, возьмите пару ключ-значение объекта компонента
  • Хук с таким же именем смешивается с массивом, и хук смешанного объекта будет вызываться перед собственным хуком компонента.

2. Сценарии применения

  • Если вы хотите, чтобы некоторые перенаправленные страницы уничтожались при выходе, но не хотите, чтобы каждая перенаправляемая страница определяла локальный маршрут
// 定义混入对象
export default {
  beforeRouteLeave(to, from, next) {
    if (to.meta && to.meta.destroy) {
      this.$destroy();
    }
    next();
  }
}

// 混入需要此功能的组件页面
import routeLeaveDestoryMixin from "routeleave-destory-mixin";
export default {
  // ...
  mixins: [routeLeaveDestoryMixin]
}
  • Таблица данных имеет настраиваемые компоненты текста, числа, времени и ячейки файла.Когда каждый компонент имеет одинаковую модификацию данных, выбор фокуса и другие методы, его можно извлечь как смешанный объект для улучшения возможности повторного использования компонентов.
// see https://github.com/calebman/vue-DBM/blob/master/src/components/business/render-column-mixin.js

// 定义混入对象
export default {
  // ...
  computed: {
    // 是否选中此单元格
    inSelect() {
      if (this.cellClickData.index == this.index &&
        this.cellClickData.field == this.field) {
        this.focus();
        return true;
      }
    }
  },
  methods: {
    // 获取焦点
    focus() {
      let self = this;
      setTimeout(function () {
        if (self.$refs["rendercolumn"]) {
          self.$refs["rendercolumn"].focus();
        }
      }, 100);
    },
    // 失去焦点
    blur() {
      if (this.v != this.value) {
        this.$emit("on-value-change", this.field, this.index, this.v);
      }
      this.$emit("on-value-cancel", this.field, this.index);
    },
    // 数据修改
    changeValue(val) {
      this.$emit("on-value-change", this.field, this.index, val);
      this.$emit("on-value-cancel", this.field, this.index);
    }
  },
  watch: {
    // 监听父组件数据变化
    value(val) {
      this.v = val;
    }
  }
}

// 文本列
// see https://github.com/calebman/vue-DBM/blob/master/src/components/business/text-column.vue
<template>
  <div>
    <input v-show="inSelect" ref="rendercolumn" @blur="blur" @keyup="enter($event)" v-model="v" />
    <span v-show="!inSelect" class="cell-text">{{v}}</span>
  </div>
</template>
// 时间列
// see https://github.com/calebman/vue-DBM/blob/master/src/components/business/datetime-column.vue
<template>
  <div>
    <el-date-picker v-show="inSelect" ref="rendercolumn" v-model="v" type="datetime" @change="changeValue" @blur="blur"></el-date-picker>
    <span v-show="!inSelect">{{coverValue}}</span>
  </div>
</template>
  • Если вы хотите уменьшить сложность компонентов, вы можете использовать несколько компонентов примесей, чтобы разделить функции основных компонентов.
# see https://github.com/calebman/vue-DBM/tree/master/src/components/table
├─table
│      cell-edit-mixin.js                      # 单元格编辑
│      classes-mixin.js                        # 表格样式                     
│      scroll-bar-control-mixin.js             # 表格滚动
│      table-empty-mixin.js                    # 无数据时的处理
│      table-resize-mixin.js                   # 表格的自适应
│      table-row-mouse-events-mixin.js         # 鼠标移动时的样式改变

Моделирование данных

1. Функции, которые необходимо реализовать

  • Перехватывать запросы Ajax и задерживать ответы
  • Возвращаемый унифицированный формат данных
  • Реагировать на различные смоделированные данные

2. Настройте Mockjs для перехвата запросов Ajax

// see https://github.com/calebman/vue-DBM/blob/master/src/mock/index.js
// 引入Mockjs
import Mock from 'mockjs';
// 配置延时
Mock.setup({
  timeout: '300-1000'
});
// 配置拦截
Mock.mock(/\/user\/login/, 'post', loginAPI.loginByUsername);
Mock.mock(/\/user\/logout/, 'post', loginAPI.logout);
Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo);

3. Единый формат данных для ответов

// see https://github.com/calebman/vue-DBM/blob/master/src/mock/response.js
/**
 * 统一响应工具类
 * 响应统一格式的数据
 * response : {
 *    errCode: 00             响应结果码
 *    errMsg: 0000000(成功)  响应详细结果码
 *    data: null              具体数据
 * }
 */
 
export default {
  // 成功
  success: data => {
    return {
      errCode: '00',
      errMsg: '0000000(成功)',
      data: data ? data : null
    }
  },
  // 失败
  fail: (errCode, errMsg) => {
    return {
      errCode: errCode ? errCode : '04',
      errMsg: errMsg ? errMsg : '0401001(未知错误)',
      data: null
    }
  },
  // 权限不足
  unauthorized: () => {
    return {
      errCode: '43',
      errMsg: '4300001(无权访问)',
      data: null
    }
  }
}

4. Настройте логику ответа

// see https://github.com/calebman/vue-DBM/blob/master/src/mock/login.js

import { param2Obj } from '@/utils';
import Response from './response';

const userMap = {
  admin: {
    password: 'admin',
    roles: ['admin'],
    token: 'admin',
    introduction: '我是超级管理员',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Super Admin'
  },
  watcher: {
    password: 'watcher',
    roles: ['watcher'],
    token: 'watcher',
    introduction: '我是游客',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Normal Watcher'
  }
}

export default {
  // 使用用户名登录
  loginByUsername: config => {
    const { username, password } = JSON.parse(config.body);
    if (userMap[username] && userMap[username].password === password) {
      return Response.success(userMap[username]);
    } else {
      return Response.fail("01", "0101001(用户名或密码错误)")
    }
  },
  // 拉取用户信息
  getUserInfo: config => {
    const { token } = param2Obj(config.url);
    if (userMap[token]) {
      return Response.success(userMap[token]);
    } else {
      return Response.fail();
    }
  },
  // 注销
  logout: () => Response.success()
}

5. Моделирование случайных данных

// see https://github.com/nuysoft/Mock/wiki

import Mock from 'mockjs';

// 随机字符串
function mockStr() {
    let result = Mock.mock({ 'str': '@name' });
    return result.str;
}

// 随机数字
function mockNumber(min, max) {
    let key = 'num|' + min + '-' + max;
    let param = {}
    param[key] = 100;
    return Mock.mock(param).num;
}

// 随机小数,最高小数点后三位
function mockDecimal() {
    return Mock.Random.float(1, 100, 1, 3)
}

// 随机数组一项
const arr = ["image2.jpeg", "image3.jpeg", "image4.jpeg", "image5.jpeg", "image6.jpeg"];
function mockOneFileAddress() {
    return Mock.mock({ 'oneFile|1': arr }).oneFile;
}

// 随机日期
function mockDate() {
    let mockDateStr = Mock.Random.datetime('yyyy-MM-dd HH:mm:ss');
    // 在这里使用了momentjs将其解析为Date类型
    let mockDate = moment(mockDateStr, 'YYYY-MM-DD HH:mm:ss').toDate();
    return mockDate;
}

Оптимизация упаковки

1. Какую часть оптимизации сделать

  • оптимизация CDN
  • Отложенная загрузка маршрута
  • Другие оптимизации
  • Пользовательский опыт

2. Оптимизация CDN

Подобно vue, vue-router, moment, element-ui и т. д., класс стойки или инструмента, предоставляющий cdn, можно напрямую ввести в index.html, а затем настроить внешние параметры веб-пакета, чтобы он не добавлял конфигурацию упаковки. , тем самым уменьшая app.js, Размер vendor.js

  • Используйте cdn для импорта зависимых библиотек в index.html
<!-- 网络请求工具类 -->
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<!-- vue -->
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<!-- vue-router -->
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
<!-- vuex -->
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
<!-- momentjs的中文包 -->
<script src="https://cdn.bootcss.com/moment.js/2.22.1/moment-with-locales.min.js"></script>
<!-- momentjs -->
<script src="https://cdn.bootcss.com/moment.js/2.22.1/locale/zh-cn.js"></script>
<!-- element-ui样式 -->
<script src="https://cdn.bootcss.com/element-ui/2.3.6/theme-default/index.css"></script>
<!-- element-ui -->
<script src="https://cdn.bootcss.com/element-ui/2.3.6/index.js"></script>
  • Настройте файл webpack.base.conf.js в папке сборки.
module.exports = {
  // ...
  externals: {
    'axios': 'axios',
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex',
    'moment': 'moment',
    'element-ui': 'ELEMENT'
  }
}

3. Отложенная загрузка маршрута

Отложенная загрузка маршрутизации может разделить код в соответствии с конфигурацией маршрутизации и ускорить отрисовку первого экрана, что важно в больших одностраничных приложениях.

видетьуправление маршрутомреализация

5. Другие оптимизации

  • Зарегистрируйте как можно меньше глобальных компонентов и используйте инфраструктуру пользовательского интерфейса, чтобы обращаться к документации для загрузки по требованию.
  • Его можно использовать с сервером для использования сжатия gzip для сокращения времени передачи.
  • Для приложений, которые обновляются не очень часто, рассмотрите возможность увеличения времени кэширования.
  • Например, момент, lodash, такая огромная библиотека инструментов, может рассмотреть возможность поиска альтернатив, когда используется не так много функций.

6. Пользовательский опыт

Одностраничное приложение достигло определенного масштаба, независимо от того, как оптимизировать рендеринг первого экрана, это все еще относительно медленный процесс.В настоящее время вы можете рассмотреть возможность использования анимации загрузки при рендеринге первого экрана, чтобы сообщить пользователя, которого система инициализирует.

  • Сначала определите анимацию рендеринга в index.html.
<body>
  <div id="app"></div>
  <!-- 首屏渲染时的加载动画 -->
  <div id="system-loading" class="showbox">
    <div class="loader">
      <svg class="circular" viewBox="25 25 50 50">
        <circle class="path" cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10" />
      </svg>
    </div>
    <div class="text">
      <span>系统初始化中...</span>
    </div>
  </div>
  <!-- built files will be auto injected -->
</body>
  • Затем удалите эту загрузку в смонтированном хуке компонента App.vue.
export default {
  // ...
  mounted() {
    document.body.removeChild(document.getElementById("system-loading"));
  }
};