vue项目的一些最佳实践提炼和经验总结

  • 项目组织结构
  • ajax数据请求的封装和api接口的模块化管理
  • 第三方库按需加载
  • 利用less的深度选择器优雅覆盖当前页面UI库组件的样式
  • webpack实时打包进度
  • vue组件中选项的顺序
  • 路由的懒加载
  • 路由模块拆分化管理

项目组织结构

清晰的项目结构能让别人开发进来更容易理解,当然,每个人都有一定的代码风格习惯。但基于vue开发框架的项目,vue-cli脚手架搭建的项目组织结构大同小异。同时,预想到后面的需求变更及功能增加进展得更有效率,下面截图是我觉得比较好的项目组织结构:

这个截图只是针对个人觉得比较通用的vue工程结构,不过这个结构要根据具体的项目情况调整,不必为了模块化而模块化。模块化的优势就是体现在项目业务比较复杂的情况,如果项目业务逻辑并不复杂,可以适当的删减部分模块或文件。

相关说明:

assets: 存放图片、UI设计的图标文件

componets:自研的业务型及通用型组件

router:项目的路由管理模块

store:基于vuex的状态管理容器,api存放各模块的数据请求,modules存放将store分割成模块(module),按官网的说法,每个模块应该拥有自己的 state、mutation、action、getter,主要是解决应用的所有状态如果全部集中到一个比较大的store对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿而难以维护。

例子:

其中的一个模块configManage.js

import {
  configManageService
} from "../api/index"
// state
const state = {
 accountMenuList:[]
}
// getters
const getters = {
// 菜单
menuTree: state => {
    return state.accountMenuList;
  },
}
// actions
const actions = {
  async GET_ACCOUT_MENU({
    state,
    commit
  }, model) {
    // 参数 state为当前局部状态,commit响应式改变当前绑定的菜单数据
    const res = await configManageService.getACountMenu(model);
    commit("CHANGE_MENU", res.data);
  }
}
// mutations
const mutations = {
  CHANGE_MENU: (data) => {
    state.accountMenuList = data;
  }
}
export default {
  state,
  getters,
  actions,
  mutations
}

index.js,统一出口,导出全部的store模块

import Vue from ‘vue‘
import Vuex from ‘vuex‘
import index from ‘./modules/index‘
import report from ‘./modules/report‘
import createLogger from ‘vuex/dist/logger‘ // 控制台输出当前变化的某个状态
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== ‘production‘ // 生产或开发环境打包
export const indexStore = new Vuex.Store({
  modules: {
    report,
    index
  },
  strict: debug, // 按照官网建议,改变state的状态只能通过getter
  plugins: debug ? [createLogger()] : []
})

style:

存放重写UI库的样式和不同组件公共样式文件

util:

存放用es6封装的工具类,http请求类,配置类、校验类、事件类等

views:

存放各路由模块页面

static:

存放全局配置文件,环境域名等

iconfont:

存放字体图标文件

ajax数据请求的封装和api接口的模块化管理

基于vue的项目,与后台请求数据我们通常使用的是axios,它是基于promise的http库,其提供的优秀的特性被广泛运用在项目当中,官方已推荐使用axios,放弃原有的vue-resource。

1、axios的封装,在很多业务场景下用来进行请求的拦截、响应的拦截及请求超时等;

// axios请求类,一些基础化配置
class AjaxRequestModel {
  constructor(model) {
    this.url = model.url || "";
    this.data = model.data || {};
    this.method = model.method || "POST";
    // this.success = model.success || function () {};
    // this.fail = model.fail || function () {};
    // this.slientSuccess = model.slientSuccess || true;
    this.failMsg = model.failMsg || true;
    this.baseUrl = model.baseUrl || window.sysConfig.baseUrl;
    this.loading = model.loading || true;
    // this.setData();
    this.setUrl();
  }
  setData() {
    // let options = {
    //   sessionid: ""
    // };
    this.data = Object.assign({}, this.data);
  }
  setUrl() {
    this.url = this.baseUrl + this.url;
  }
}
// 实例化axios,配置请求超时时间
const axiosInstance = axios.create({
  timeout: 1000 * 20
});
// 封装ajaxService函数,以更少的代码处理get、post、delete、put请求方式,同时支持async、await异步处理方案,返回promise
const ajaxService = param => {
  let model = new AjaxRequestModel(param);
  let o = {
    url: model.url,
    data: model.data,
    method: model.method
  };
  // if (model.loading) {
  //   ak.Msg.showLoading();
  // }
  if (model.method === "GET") {
    o = {
      url: model.url,
      params: model.data,
      method: model.method
    };
  }
  return new Promise((resolve, reject) => {
    axiosInstance
      .request(o)
      .then(res => {
        if (res.data.code === 200 || res.data.code === 0) {
          resolve(res.data);
        } else {
          ak.Msg.toast(res.data.message, "error");
          reject(res.data);
        }
      })
      .catch(err => {
        httpResponseHandle.call(err);
        reject(err);
      });
  });
};

2、在请求的拦截中,可以携带用于接口身份验证的token,配置headers请求头、提交参数的序列化等

// 请求头相关配置
axiosInstance.interceptors.request.use(
  function (config) {
    const info = ak.Utils.getSessionStorage("USER_INFO");
    config.headers.common[‘token‘] = info ? info[0].token : "";
    // config.headers.common[‘Content-Type‘] = "application/json";
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

3、在响应的拦截中,可以进行根据各种状态码来进行错误的统一处理等

const httpResponseHandle = err => {
  const opt = err.response;
  // 请求超时
  if (err.code === "ECONNABORTED") {
    ak.Msg.toast("请求超时,请稍后再试", "error");
  }
  if (opt.status === 401) {
    ak.Msg.confirm("用户登录超时,请重新登录", () => {
      sessionStorage.removeItem("USER_INFO");
      window.utryVue.$router.replace("/login");
      location.reload();
    });
  } else {
    ak.Msg.toast(opt.data.message, "error");
  }
};

4、api接口模块化管理,业务逻辑和数据请求分层,这样可以很方便统一管理我们的接口

如图,把不同的功能拆分,实现代码模块化管理,全部的接口均放在api文件夹下面。index.js是一个api接口的导出的出口,这样就可以把api接口根据功能划分为多个模块,利于多人协作开发,比如一个人只负责一个模块的开发等,还能方便每个模块中接口的命名

index.js:

import report from ‘./report‘; // 报表模块
import accountService from ‘./accountService‘; // 登陆、用户信息相关
// 导出接口
export {
  accountService,
  report
}

API请求service层:

// 报表管理请求模块,与后台请求的参数、请求方式、url均看作一个model
import http from "@/util/http.js";
const API_CONTEXT = "sys/"; // 请求的上下文
const report = {
  async getMenuList() {
    let model = {};
    model.url = API_CONTEXT + "category/getCategoryTree";
    model.method = "GET";
    let res = await http.ajaxService(model);
    return res;
  },
  async removeMenu(model) {
    model.data = { ...model };
    model.url = API_CONTEXT + "category/removeCategory";
    let res = await http.ajaxService(model);
    return res;
  }
}
export default report;

组件的业务逻辑层调用方式:

// 说明:async、await的写法省去了不少的回调,在有些必须请求两个接口或者两个接口以上场景下,async、await优势就显示出来了
import { reportService } from "../../store/api/index";
async getMenuList() {
      const param = {
        role: ""
      };
      const res = await reportService.getMenuList(param);
      // 下面代码返回成功时才执行,错误由上面所讲的axios封装ajaxService统一处理
      this.menuList = res.data;
 }

5、如果后期维护需要修改的接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦,如果修改的量比较大,难免会自测不充分产生bug,直接gg。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦

6、处理接口域名、端口有多个情况

// 无需前端打包,运维环境快速修改配置,eg:
window.sysConfig = {
  // 运维平台
  baseUrl: ‘http://10.0.33.97:7083/‘,
  // 租户平台
  tenantUrl: ‘http://10.0.33.96:7082/‘
}
// 区分不同平台的url地址在http.js文件下的AjaxRequestModel类实例化会统一处理
this.baseUrl = model.baseUrl ? window.sysConfig.baseUrl :  window.sysConfig.tenantUrl

第三方库按需加载

按需加载是针对某些第三方库体积比较大的情况下,优化webpack打包后的js体积,减少页面的加载时间

以echart为例子:

优化前:

// 全导入
import * as echarts from "echarts";

webpack打包后:

优化后(主js体积减少了400kb,同时build编译打包速度也得到了减少)

import echarts from "echarts/lib/echarts";
// 依赖注入,目前项目只用到折线图、饼图和柱形图,故只需引入对应的模块即可,tooltip是提示类,title是鼠标悬停显示的对应的图表名称
import ‘echarts/lib/chart/bar‘;
import ‘echarts/lib/chart/line‘;
import ‘echarts/lib/chart/pie‘;
import ‘echarts/lib/component/tooltip‘;
import ‘echarts/lib/component/title‘;

利用less的深度选择器优雅覆盖当前页面UI库组件的样式

vue页面组件的样式基本是写在<style scoped lang="less"></style>中,增加scoped属性的目的让其样式只在当前页面有效。按照这些写的方式,编译后当前标签会加上类似于[data-v-]这样的属性,但是第三方的UI组件库并没有编译为带[data-v-]这样的属性,所以就遇到了当前页面覆盖的样式没生效的情况,有没有方法处理这种问题呢。有些小伙伴可能会想到我在公共样式里面写,额外添加类名来覆盖当前组件的样式,其实,这也不失为一种方案,但是会引来样式全局污染和命名可能重名的情况。下面列举更简单粗暴的方式,同时避免了样式污染和命名冲突的问题:

.menu-tree {
    /deep/ .el-tree-node__content {
      height: 32px;
    }
    /deep/ .is-current .el-tree-node__content {
      background-color: #f2f2fa;
    }
  }

编译后,默认给menu-tree加上了[data-v-3c93a211]

/deep/深度选择器支持less或者sass,如果你用的是原生的css,可以用<<<符号

webpack实时打包进度

在项目用jenkins自动化打包前端项目的时候,常常会遇到打包速度慢而体验很差,在优化减负依赖包的情况下,同时没有一个测试环境或生产环境当前打包进度捉鸡。这里推荐一个第三方的插件包

progress-bar-webpack-plugin。

用法:

// 需安装依赖 npm install progress-bar-webpack-plugin --save-dev
const ProgressBarPlugin = require(‘progress-bar-webpack-plugin‘)
// 在生产环境webpack配置文件的plugin是加上
new ProgressBarPlugin(), // 打包进度 

vue组件中写选项的顺序

这里纯属个人观点,可能有些小伙伴用vue开发不是遵从这个。为什么要规定组件的写法顺序呢,或者说它是官方要求的规范,不如说是能让的代码更加优雅,更易于维护,因为你写的代码不仅是你一个人维护。要是一个团队都按这个规范来,大家在维护代码的时候认知一样,那效率就提高了。

组件依赖:

components(自研的子组件或第三方组件)

service(api请求类,其他服务类)

utils(工具类等)

事件传递(vue eventBus)

mixins(复用的属性或方法)

组合:

mixins

组件的属性、接口:

components

props

本地响应式属性、状态:

data

computed

事件注册:

watch

组件生命周期:

created

mounted

destroyed等

组件的方法:

methods

例子:

// 例子
import utryTree from "@/components/utry-tree/utry-tree.vue";
import { reportService } from "@/store/api/index";
import Validation from "../../util/Validation";
import eventBus from "@/util/eventBus";import reportMixins from "@/mixins/reportMixins";
export default {
  mixins: [],
  components: {
  },
  props: {
    menuList: {
      type: Array,
      default() {
        return [];
      }
    }
  },
  data(){},
  computed:{},
  watch:{},
  mounted(){},
  methods:{},
}

路由的懒加载

有时候,针对有些复杂组件,初始化页面其实并不需要把全部组件资源加载进来,把业务复杂的组件抽离出来,从而能减少初始化页面的加载时间

优化前:

import reportManage from ‘@/views/reportManage/index‘;
import reportPreview from ‘@/views/reportManage/reportPreview‘;
export default [
  { path: ‘reportManage/index‘, name: ‘reportManage‘, component: reportManage },
  { path: ‘reportManage/reportPreview‘,  name: ‘reportPreview‘, component: reportPreview }
];

初始化页面的加载耗时:

优化后:

import reportManage from ‘@/views/reportManage/index‘;
export default [
  { path: ‘reportManage/index‘, name: ‘reportManage‘, component: reportManage },
  { path: ‘reportManage/reportPreview‘, name: ‘reportPreview‘, component: () => import(‘@/views/reportManage/reportPreview‘),
    meta: { keepAlive: false }
  }
];

初始化页面加载耗时:

时间的差别主要是在js的解析上,主要是是因为初始化页面没有加载当前模块的二次组件的js,等到跳转到二次页面再去解析静态资源,总体优化后初始化页面的加载时间快了100多毫秒。

路由模块的拆分化管理

这里的路由拆分,是指按模块拆分成不同的路由文件,针对单页面应用这样更方便团队的多人协调同步开发,自己写的功能模块互不影响。如果当业务需求多起来的时候,它的优势就越能体现出来。我们并不想就在一个router.js写整个工程的路由,这样会是单文件代码量庞大而变得很槽糕,同时也会带来其他同事误改的问题。

我们在router文件夹下面创建router.js作为路由的入口文件,其他以router.js后缀的文件存放着各个模块的路由。

router.js:

import Vue from "vue";
import Router from "vue-router";
import NProgress from "nprogress"; // 引入nprogress,每次路由变化网页顶端有个加载条效果
import ak from "@/util/ak.js";
// 业务路由
import login from "@/views/index/login"; // 租户平台
import oamLogin from "@/views/index/oamLogin"; // 运维平台
import indexRouter from "./index.router"; // 首页相关
import reportManage from "./reportManage.router"; // 报表管理
Vue.use(Router);
// 默认登录
let routes = [
  {
    path: "/",
    redirect: "login"
  },
  {
    path: "/login",
    name: "login",
    component: login
  },
  {
    path: "/oamLogin",
    name: "oamLogin",
    component: oamLogin
  }
];
routes = routes.concat(
  indexRouter,
  reportManage
);

// router register
const router = new Router({
  routes
});
// 路由相关的拦截操作,在这里处理,之前有的router相关操作写在main.js,并不是很友好
router.beforeEach((to, from, next) => {
  // 每次切换页面时,调用进度条
  NProgress.start();
  // cache机制
  const info = ak.Utils.getSessionStorage("USER_INFO");
  const token = info ? info[0].token : "";
  if (token) {
    next();
  } else {
    if (to.path === "/oamLogin") {
      next();
    } else if (to.path === "/login") {
      next();
    } else {
      next("/login");
    }
  }
});
router.afterEach(() => {
  // 在即将进入新的页面组件前,关闭掉进度条
  NProgress.done();
}); 

index.router.js:

import home from ‘@/views/index/home‘;
export default [
  { path: ‘/index/home‘, name: ‘home‘, component: home }
];

这里把首页的路由放在一个数组里,然后导出去,有router.js统一引入,并实例化当前路由

未完待续......

原文地址:https://www.cnblogs.com/zhouqy/p/10311802.html

时间: 2024-10-08 10:01:54

vue项目的一些最佳实践提炼和经验总结的相关文章

《国际项目集管理最佳实践与实战应用》大型复杂项目与项目群管理工作坊

<国际项目集管理最佳实践与实战应用>大型复杂项目与项目群管理工作坊 Program Management Training 主办单位:共创国际-项目管理者联盟 2015年4月10.11.12日 北京 & 课程前言 项目集(PROGRAM)定义为“经过协调管理以获取单独管理它们时无法取得收益与控制的一组关联的项目和项目集活动.”从组织战略的角度来看,项目集管理主要是指组织为实现其战略目标或为其客户提供整体解决方案而在组织高层针对战略性资源进行跨界整合的管理活动. 在全球经济一体化的背景下

《项目集管理标准》:全球项目集管理最佳实践

<项目集管理标准>是全球项目集管理最佳实践           <项目集管理标准>(The Standard for Program Management)是由美国项目管理协会推出的一个重要的国家性的,也是事实上的全球性标准.标准详细描述了项目集管理的内容,促进不同项目小组之间进行有效沟通和资源调配.提供了帮助人们理解如何通过提升相互关联的组件的交付能力从而促进组织战略实现的重要且基本的内容.在大多数情况对大部分项目集而言,被认为是清晰.完整的.相关的和公认的在项目集管理中良好实践

Vue项目骨架屏注入实践

相比于早些年前后端代码紧密耦合.后端工程师还得写前端代码的时代,如今已发展到前后端分离,这种开发方式大大提升了前后端项目的可维护性与开发效率,让前后端工程师关注于自己的主业.然而在带来便利的同时,也带来了一些弊端,比如首屏渲染时间(FCP)因为首屏需要请求更多内容,比原来多了更多HTTP的往返时间(RTT),这造成了白屏,如果白屏时间过长,用户体验会大打折扣,如果用户网速差,则FCP会更长. 由此引申出一系列的优化方法,骨架屏也因此被提出. FCP优化 在 Google 提出的以用户为中心的四个

大数据Hadoop最佳实践(V3)

一:课程简介: Hadoop是当下云计算大数据的王者. Hadoop不仅是一个大数据的计算框架,同时也是大数据的存储平台. 使用Hadoop,用户可以在不了解分布式底层细节的情况下开发出分布式程序,从而可以使用众多廉价的计算设备的集群的威力来高速的运算和存储,而且Hadoop的运算和存储是可靠的.高效的.可伸缩的,能够使用普通的社区服务器出来PB级别的数据,是分布式大数据处理的存储的理想选择 使用Hadoop可以主要完成: 1,构建离线处理平台,完成海量离线数据的存储分析,相对于传统的关系型数据

vue.js+boostrap最佳实践

一.为什么要写这篇文章 最近忙里偷闲学了一下vue.js,同时也复习了一下boostrap,发现这两种东西如果同时运用到一起,可以发挥很强大的作用,boostrap优雅的样式和丰富的组件使得页面开发变得更美观和更容易,同时vue.js又是可以绑定model和view(这个相当于MVC中的,M和V之间的关系),使得对数据变换的操作变得更加的简易,简化了很多的逻辑代码. 二.学习这篇文章需要具备的知识 1.需要有vue.js的知识 2.需要有一定的HTML.CSS.JavaScript的基础知识 3

一位云架构师用服务打动客户的故事之六(阿里云上的MSP最佳实践项目分享)

最近找了一个典型的云服务客户的案例对内进行分享,今天把核心内容脱敏后分享出来.希望能给目前在路上(做云服务MSP)的同行,有一些借鉴意义或者帮助. 该用户据全年跟进情况,目前该客户距正式启用我们公司云服务(运维服务)的日子已经有半年有余了,目前整体趋于稳定,故将目前用户进行深度复盘剖析,让各位伙伴更好的从该客户案例中提取一些有用的"武器"."售前技巧". 云产商:阿里云 企业背景-日企上来的终极三问~ > 为什么选择我们做云服务商?PS:此云服务并非指的是阿里

Vue 工程化最佳实践

目录结构 总览 api 目录用于存放 api 请求,文件名与模型名称基本一致,文件名使用小驼峰,方法名称与后端 restful 控制器一致. enums 目录存放 常量,与后端的常量目录对应 icons 目录用于存放图标,element-ui 提供的图标实在是太少啦.所以我通常会使用 阿里的 iconfont lang 目录存放多语言 layouts 目录存放布局 上面展示的是一个后台系统,empty 为一个空布局.用于登录页面,其他页面则使用 default 布局.布局不需要过多介绍,写过 l

Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt)

Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt) 作者: Desmond Chen,发布日期: 2014-05-17, 修改日期: 2014-05-18 在Django 1.6中的settings.py中可以修改130多项设置, 但大多数都继承自默认值. 设置是在web服务器启动时首次载入的, 服务器重启时重新载入, 因此, 程序员们应尽量避免修改正式服务器上使用的settings.py文件. 以下是一些我们

(桌面虚拟化最佳实践--呼叫中心系统优化之二)存储和服务器优化项目

VMware方案中母版盘优化 在具备SSD盘的局点,桌面母盘(replica盘)采用高性能SSD存储,以提高整个桌面池的使用性能. 一个母盘对应不超过64个虚拟机(即一个桌面池不超过64个虚拟机). 磁盘RAID优化 虚拟机母版盘.操作系统盘.linux文件服务器均采用RAID0+1技术,NAS文件服务器采用raid5,以提高磁盘读写性能. 存储LUN优化 按照安装指南,一个LUN按规划25~30个(该值如有变化,以最新的发布资料为准)虚拟机进行配置划分,以提高LUN的读写性能. 服务器优化 该