1. 接口模块处理

1.1 axios二次封装

很基础的部分,已封装好的请跳过。这里的封装是依据 JWT

import axios from 'axios'import router from '../router'import {MessageBox, Message} from 'element-ui'let loginUrl = '/login'// 根据环境切换接口地址axios.defaults.baseURL = process.env.VUE_APP_APIaxios.defaults.headers = {'X-Requested-With': 'XMLHttpRequest'}axios.defaults.timeout = 60000// 请求拦截器axios.interceptors.request.use(config => {if (router.history.current.path !== loginUrl) {let token = window.sessionStorage.getItem('token')if (token == null) {router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})return false} else {config.headers['Authorization'] = 'JWT ' + token}}return config}, error => {Message.warning(error)return Promise.reject(error)})

紧接着的是响应拦截器(即异常处理)

axios.interceptors.response.use(response => {return response.data}, error => {if (error.response !== undefined) {switch (error.response.status) {case 400:MessageBox.alert(error.response.data)breakcase 401:if (window.sessionStorage.getItem('out') === null) {window.sessionStorage.setItem('out', 1)MessageBox.confirm('会话已失效! 请重新登录', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => {router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})}).catch(action => {window.sessionStorage.clear()window.localStorage.clear()})}breakcase 402:MessageBox.confirm('登陆超时 !', '提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => {router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})})breakcase 403:MessageBox.alert('没有权限!')break// ...忽略default:MessageBox.alert(`连接错误${error.response.status}`)}return Promise.resolve(error.response)}return Promise.resolve(error)})

这里做的处理分别是会话已失效和登陆超时,具体的需要根据业务来作变更。

最后是导出基础请求类型封装。

export default {get (url, param) {if (param !== undefined) {Object.assign(param, {_t: (new Date()).getTime()})} else {param = {_t: (new Date()).getTime()}}return new Promise((resolve, reject) => {axios({method: 'get', url, params: param}).then(res => { resolve(res) })})},getData (url, param) {return new Promise((resolve, reject) => {axios({method: 'get', url, params: param}).then(res => {if (res.code === 4000) {resolve(res.data)} else {Message.warning(res.msg)}})})},post (url, param, config) {return new Promise((resolve, reject) => {axios.post(url, param, config).then(res => { resolve(res) })})},put: axios.put,_delete: axios.delete}

其中给 get请求加上时间戳参数,避免从缓存中拿数据。

浏览器缓存是基于url进行缓存的,如果页面允许缓存,则在一定时间内(缓存时效时间前)再次访问相同的URL,浏览器就不会再次发送请求到服务器端,而是直接从缓存中获取指定资源。

1.2 请求按模块合并

模块的请求:

import http from '@/utils/request'export default {A (param) { return http.get('/api/', param) },B (param) { return http.post('/api/', param) }C (param) { return http.put('/api/', param) },D (param) { return http._delete('/api/', {data: param}) },}

utils/api/index.js:

import http from '@/utils/request'import account from './account'// 忽略...const api = Object.assign({}, http, account, \*...其它模块*\)export default api

1.3 global.js中的处理

在 global.js中引入:

import Vue from 'vue'import api from './api/index'// 略...const errorHandler = (error, vm) => {console.error(vm)console.error(error)}Vue.config.errorHandler = errorHandlerexport default {install (Vue) {// 添加组件// 添加过滤器})// 全局报错处理Vue.prototype.$throw = (error) => errorHandler(error, this)Vue.prototype.$http = api// 其它配置}}

写接口的时候就可以简化为:

async getData () {const params = {/*...key : value...*/}let res = await this.$http.A(params)res.code === 4000 ? (this.aSata = res.data) : this.$message.warning(res.msg)}

2.Vue组件动态注册

来自 @SHERlocked93:Vue 使用中的小技巧

我们写组件的时候通常需要引入另外的组件:

<template><BaseInput v-model="searchText" @keydown.enter="search"/><BaseButton @click="search"><BaseIcon name="search"/></BaseButton></template><script>import BaseButton from './baseButton'import BaseIcon from './baseIcon'import BaseInput from './baseInput'export default {components: { BaseButton, BaseIcon, BaseInput }}</script>

写小项目这么引入还好,但等项目一臃肿起来...啧啧。 这里是借助 webpack,使用 require.context()方法来创建自己的模块上下文,从而实现自动动态 require组件。

这个方法需要3个参数:

  • 要搜索的文件夹目录

  • 是否还应该搜索它的子目录

  • 一个匹配文件的正则表达式。

在你放基础组件的文件夹根目录下新建 componentRegister.js:

import Vue from 'vue'/*** 首字母大写* @param str 字符串* @example heheHaha* @return {string} HeheHaha*/function capitalizeFirstLetter (str) {return str.charAt(0).toUpperCase() + str.slice(1)}/*** 对符合'xx/xx.vue'组件格式的组件取组件名* @param str fileName* @example abc/bcd/def/basicTable.vue* @return {string} BasicTable*/function validateFileName (str) {return /^\S+\.vue$/.test(str) &&str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1))}const requireComponent = require.context('./', true, /\.vue$/)// 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名requireComponent.keys().forEach(filePath => {const componentConfig = requireComponent(filePath)const fileName = validateFileName(filePath)const componentName = fileName.toLowerCase() === 'index'? capitalizeFirstLetter(componentConfig.default.name): fileNameVue.component(componentName, componentConfig.default || componentConfig)})

最后我们在 main.js

import 'components/componentRegister.js'

我们就可以随时随地使用这些基础组件,无需手动引入了。

3. 页面性能调试:Hiper

我们写单页面应用,想看页面修改后性能变更其实挺繁琐的。有时想知道是「正优化」还是「负优化」只能靠手动刷新查看 network。而 Hiper很好解决了这一痛点(其实 Hiper是后台静默运行 Chromium来实现无感调试)。

Hiper官方文档

我们开发完一个项目或者给一个项目做完性能优化以后,如何来衡量这个项目的性能是否达标?

我们的常见方式是在 DevTool中的 performance和 network中看数据,记录下几个关键的性能指标,然后刷新几次再看这些性能指标。

有时候我们发现,由于样本太少,受当前「网络」、「CPU」、「内存」的繁忙程度的影响很重,有时优化后的项目反而比优化前更慢。

如果有一个工具,一次性地请求N次网页,然后把各个性能指标取出来求平均值,我们就能非常准确地知道这个优化是「正优化」还是「负优化」。

并且,也可以做对比,拿到「具体优化了多少」的准确数据。这个工具就是为了解决这个痛点的。

安装

sudo npm install hiper -g# 或者使用 yarn:# sudo yarn global add hiper

性能指标

Key Value
DNS查询耗时 domainLookupEnd - domainLookupStart
TCP连接耗时 connectEnd - connectStart
第一个Byte到达浏览器的用时 responseStart - requestStart
页面下载耗时 responseEnd - responseStart
DOM Ready之后又继续下载资源的耗时 domComplete - domInteractive
白屏时间 domInteractive - navigationStart
DOM Ready 耗时 domContentLoadedEventEnd - navigationStart
页面加载总耗时 loadEventEnd - navigationStart

用例

# 当我们省略协议头时,默认会在url前添加`https://`# 最简单的用法hiper baidu.com# 如何url中含有任何参数,请使用双引号括起来hiper "baidu.com?a=1&b=2"# 加载指定页面100次hiper -n 100 "baidu.com?a=1&b=2"# 禁用缓存加载指定页面100次hiper -n 100 "baidu.com?a=1&b=2" --no-cache# 禁JavaScript加载指定页面100次hiper -n 100 "baidu.com?a=1&b=2" --no-javascript# 使用GUI形式加载指定页面100次hiper -n 100 "baidu.com?a=1&b=2" -H false# 使用指定useragent加载网页100次hiper -n 100 "baidu.com?a=1&b=2" -u "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"

此外,还可以配置 Cookie访问

module.exports = {....cookies: [{name: 'token',value: process.env.authtoken,domain: 'example.com',path: '/',httpOnly: true}],....}
# 载入上述配置文件(假设配置文件在/home/下)hiper -c /home/config.json# 或者你也可以使用js文件作为配置文件hiper -c /home/config.js

4. Vue高阶组件封装

我们常用的 <transition>和 <keep-alive>就是一个高阶(抽象)组件。

export default {name: 'keep-alive',abstract: true,...}

所有的高阶(抽象)组件是通过定义 abstract选项来声明的。高阶(抽象)组件不渲染真实 DOM。 一个常规的抽象组件是这么写的:

import { xxx } from 'xxx'const A = () => {.....}export default {name: 'xxx',abstract: true,props: ['...', '...'],// 生命周期钩子函数created () {....},....destroyed () {....},render() {const vnode = this.$slots.default....return vnode},})

4.1 防抖/节流 抽象组件

关于防抖和节流是啥就不赘述了。这里贴出组件代码:

改编自:Vue实现函数防抖组件

const throttle = function(fn, wait=50, isDebounce, ctx) {let timerlet lastCall = 0return function (...params) {if (isDebounce) {if (timer) clearTimeout(timer)timer = setTimeout(() => {fn.apply(ctx, params)}, wait)} else {const now = new Date().getTime()if (now - lastCall < wait) returnlastCall = nowfn.apply(ctx, params)}}}export default {name: 'Throttle',abstract: true,props: {time: Number,events: String,isDebounce: {type: Boolean,default: false},},created () {this.eventKeys = this.events.split(',')this.originMap = {}this.throttledMap = {}},render() {const vnode = this.$slots.default[0]this.eventKeys.forEach((key) => {const target = vnode.data.on[key]if (target === this.originMap[key] && this.throttleMap[key]) {vnode.data.on[key] = this.throttledMap[key]} else if (target) {this.originMap[key] = targetthis.throttledMap[key] = throttle(target, this.time, this.isDebounce, vnode)vnode.data.on[key] = this.throttledMap[key]}})return vnode},})

通过第三个参数 isDebounce来控制切换防抖节流。 最后在 main.js里引用:

import Throttle from '../Throttle'Vue.component('Throttle', Throttle)

使用:

<div id="app"><Throttle :time="1000" events="click"><button @click="onClick($event, 1)">click+1 {{val}}</button></Throttle><Throttle :time="1000" events="click" :isDebounce="true"><button @click="onAdd">click+3 {{val}}</button></Throttle><Throttle :time="3300" events="mouseleave" :isDebounce="true"><button @mouseleave.prevent="onAdd">click+3 {{val}}</button></Throttle></div>

使用:

const app = new Vue({el: '#app',data () {return {val: 0}},methods: {onClick ($ev, val) {this.val += val},onAdd () {this.val += 3}}})

抽象组件是一个接替Mixin实现抽象组件公共功能的好方法,不会因为组件的使用而污染DOM(添加并不想要的div标签等)、可以包裹任意的单一子元素等等

至于用不用抽象组件,就见仁见智了。

5. 性能优化:eventBus封装

中央事件总线eventBus的实质就是创建一个vue实例,通过一个空的vue实例作为桥梁实现vue组件间的通信。它是实现非父子组件通信的一种解决方案。

而 eventBus实现也非常简单

import Vue from 'Vue'export default new Vue

我们在使用中经常最容易忽视,又必然不能忘记的东西,那就是:清除事件总线 eventBus

不手动清除,它是一直会存在,这样当前执行时,会反复进入到接受数据的组件内操作获取数据,原本只执行一次的获取的操作将会有多次操作。本来只会触发并只执行一次,变成了多次,这个问题就非常严重。

当不断进行操作几分钟后,页面就会卡顿,并占用大量内存。

所以一般在vue生命周期 beforeDestroy或者 destroyed中,需要用vue实例的 $off方法清除 eventBus

beforeDestroy(){bus.$off('click')}

可当你有多个 eventBus时,就需要重复性劳动 $off销毁这件事儿。 这时候封装一个 eventBus就是更佳的解决方案。

5.1 拥有生命周期的 eventBus

我们从Vue.init中可以得知:

Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uid vm实例唯一标识vm._uid = uid++// ....}

每个Vue实例有自己的 _uid作为唯一标识,因此我们让 EventBus和_uid`关联起来,并将其改造:

实现来自:让在Vue中使用的EventBus也有生命周期

class EventBus {constructor (vue) {if (!this.handles) {Object.defineProperty(this, 'handles', {value: {},enumerable: false})}this.Vue = vue// _uid和EventName的映射this.eventMapUid = {}}setEventMapUid (uid, eventName) {if (!this.eventMapUid[uid]) this.eventMapUid[uid] = []this.eventMapUid[uid].push(eventName) // 把每个_uid订阅的事件名字push到各自uid所属的数组里}$on (eventName, callback, vm) {// vm是在组件内部使用时组件当前的this用于取_uidif (!this.handles[eventName]) this.handles[eventName] = []this.handles[eventName].push(callback)if (vm instanceof this.Vue) this.setEventMapUid(vm._uid, eventName)}$emit () {let args = [...arguments]let eventName = args[0]let params = args.slice(1)if (this.handles[eventName]) {let len = this.handles[eventName].lengthfor (let i = 0; i < len; i++) {this.handles[eventName][i](...params)}}}$offVmEvent (uid) {let currentEvents = this.eventMapUid[uid] || []currentEvents.forEach(event => {this.$off(event)})}$off (eventName) {delete this.handles[eventName]}}// 写成Vue插件形式,直接引入然后Vue.use($EventBus)进行使用let $EventBus = {}$EventBus.install = (Vue, option) => {Vue.prototype.$eventBus = new EventBus(Vue)Vue.mixin({beforeDestroy () {// 拦截beforeDestroy钩子自动销毁自身所有订阅的事件this.$eventBus.$offVmEvent(this._uid)}})}export default $EventBus

使用:

// main.js中...import EventBus from './eventBus.js'Vue.use(EnemtBus)...

组件中使用:

created () {let text = Array(1000000).fill('xxx').join(',')this.$eventBus.$on('home-on', (...args) => {console.log('home $on====>>>', ...args)this.text = text}, this) // 注意第三个参数需要传当前组件的this,如果不传则需要手动销毁},mounted () {setTimeout(() => {this.$eventBus.$emit('home-on', '这是home $emit参数', 'ee')}, 1000)},beforeDestroy () {// 这里就不需要手动的off销毁eventBus订阅的事件了}

6. webpack插件:真香

6.1 取代 uglifyjs 的 TerserPlugin

在二月初项目升级Vue-cli3时遇到了一个问题: uglifyjs不再支持webpack4.0。找了一圈,在 Google搜索里查到 TerserPlugin这个插件。

我主要用到了其中这几个功能:

  • cache,启用文件缓存。

  • parallel,使用多进程并行来提高构建速度。

  • sourceMap,将错误消息位置映射到模块(储存着位置信息)。

  • drop_console,打包时剔除所有的 console语句

  • drop_debugger,打包时剔除所有的 debugger语句

作为一个管小组前端的懒B,很多时候写页面会遗留 console.log,影响性能。设置个 drop_console就非常香。以下配置亲测有效。

const TerserPlugin = require('terser-webpack-plugin')....new TerserPlugin({cache: true,parallel: true,sourceMap: true, // Must be set to true if using source-maps in productionterserOptions: {compress: {drop_console: true,drop_debugger: true}}})

更多的配置请看Terser Plugin

6.2 双端开启 gzip

开启gzip压缩的好处是什么?

可以减小文件体积,传输速度更快。gzip是节省带宽和加快站点速度的有效方法。

  • 服务端发送数据时可以配置 Content-Encoding:gzip,用户说明数据的压缩方式

  • 客户端接受到数据后去检查对应字段的信息,就可以根据相应的格式去解码。

  • 客户端请求时,可以用 Accept-Encoding:gzip,用户说明接受哪些压缩方法。

6.2.1 Webpack开启 gzip

这里使用的插件为: CompressionWebpackPlugin

const CompressionWebpackPlugin = require('compression-webpack-plugin')module.exports = {“plugins”:[new CompressionWebpackPlugin]}

具体配置:

const CompressionWebpackPlugin = require('compression-webpack-plugin');webpackConfig.plugins.push(new CompressionWebpackPlugin({asset: '[path].gz[query]',algorithm: 'gzip',test: new RegExp('\\.(js|css)$'),// 只处理大于xx字节 的文件,默认:0threshold: 10240,// 示例:一个1024b大小的文件,压缩后大小为768b,minRatio : 0.75minRatio: 0.8 // 默认: 0.8// 是否删除源文件,默认: falsedeleteOriginalAssets: false}))

开启gzip前

开启gzip后

gzip后的大小从277KB到只有~91.2KB!

6.2.2 扩展知识: Nginx的 gzip设置

打开 /etc/nginx/conf.d编写以下配置。

server {gzip on;gzip_static on;gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;gzip_proxied any;gzip_vary on;gzip_comp_level 6;gzip_buffers 16 8k;gzip_http_version 1.1;...}

Nginx尝试查找并发送文件 /path/to/bundle.js.gz。如果该文件不存在,或者客户端不支持 gzip,Nginx则会发送该文件的未压缩版本。

保存配置后,重新启动 Nginx:

  1. $ sudo service nginx restart

开启gzip前

开启gzip后

6.2.3 如何验证gzip

通过使用 curl测试每个资源的请求响应,并检查 Content-Encoding

显示 Content-Encoding:gzip,即为配置成功

「Vue实战」武装你的前端项目相关推荐

  1. 「群体遗传学实战」第三课: 如何对SNP位点进行过滤

    往期教程 「群体遗传学实战」第一课: 对SNP位点进行注释 「群体遗传学实战」第二课: 画出和文章几乎一样的PCA图 SNP过滤有两种情况,一种是仅根据位点质量信息(测序深度,回帖质量等)对SNP进行 ...

  2. Vue入门之Web端CURD前端项目示例

    Vue入门之Web端CURD前端项目示例 随着vue.js越来越火,很多不太懂前端的小伙伴想要入坑.而入坑最好的办法就是上手实际操作,因此本文将重点放在实际操作上,对理论不做过多解释,不明白的地方小伙 ...

  3. 让炼丹更玄学!苏大博士生用「天干地支」生成随机种子,项目已开源

      视学算法报道   编辑:好困 袁榭 [新智元导读]为了顺利炼出模型,搞AI的朋友们真的是什么路子都想过. 机器学习,俗称「炼丹」. 作为AI「黑魔法」的一种,再加点「玄学」又会如何? 最近,有位来 ...

  4. 27亿参数的「野生版」GPT-3开源,GitHub项目2.9K Star量

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 OpenAI 著名的语言模型 GPT-3 可以懂数学.翻译文字,还能写论文拿到及格成绩 ...

  5. react安装_「React实战」三分钟搭建React开发环境

    其实16年的时候就已经接触到React,那个时候也只是入门,时隔多年,工作上一直都没有接触到相关的业务,不知不觉,前端的天也开始渐变,看到 了很多招聘要求上都是要求会React,三大框架怎么也得熟悉使 ...

  6. vue当前页引入js_「vue基础」新手入门导航(一)

    自从Facebook2013年推出React框架以来,基于UI组件的前端框架越来越流行,主要得益于组件的重用性,数据状态的管理等特性. 谷歌也推出了基于组件的第二代Angular框架,致力于开发全平台 ...

  7. 前端已死?我看未必,但「低代码」已剑指前端程序员

    本文笔者会从以下几个方面分享,希望能够帮助正在迷茫的前端小伙伴提供一点思路! 逛技术博客 不局限框架 全栈工程师兴起 关注前沿 写技术文章 录制前端视频 总结 2023第一季度快过去了,没工作的找到工 ...

  8. 怎么查询redis缓存的数据_阿里开发十年写出这份「Redis简明教程」+「Redis实战」请你查收...

    Redis是啥?用Redis官方的话来说就是: Redis is an open source (BSD licensed), in-memory data structure store, used ...

  9. springboot redis 刷新时间_「SpringBoot实战」SpringCache + Redis实现数据缓存

    关注我的微信公众号:后端技术漫谈 不定期推送关于后端开发.爬虫.算法题.数据结构方面的原创技术文章,以及生活中的逸闻趣事. 我目前是一名后端开发工程师.主要关注后端开发,数据安全,网络爬虫,物联网,边 ...

最新文章

  1. matlab同窗口显示图片,[求助]关于GUI的问题,如何在同一窗口里显示四副图片...........
  2. Qt 4.8.4 Qt Creator 2.6.1 安装和配置(Windows)
  3. Java 数组常用操作一(排序、元素位置查找、添加元素、获取长度、数组反向、最大值最小值、合并、范围填充)
  4. 防止表格中的单行按钮被频繁点击,前端实例讲解~
  5. 计算机图形学E4——OpenGL 区域种子填充
  6. BZOJ 1833 数位DP
  7. leetcode先刷_Maximum Subarray
  8. 软件著作权申请流程及模板
  9. drozer could not find java_Drozer-Android安全测试
  10. token干什么用_什么是Token?Token有什么用处?为什么使用它?Token认证又是什么?...
  11. SAP笔记-abap SD 定价公式(例程,即Formula)
  12. 环形10个树洞兔子c语言设计,[阅读打卡]I am a Bunny+提高孩子的欣赏能力我是一只小兔子,我的名字叫尼古拉斯 我住在一个树洞里面 在春天的时候,我喜欢看美丽的花儿,有...
  13. 手机沙盒隔离软件_虚拟隔离沙箱|隔离沙箱(BufferZone Pro)下载 v4.07 免费版 - 121下载站...
  14. 数字化智慧梁场管理系统解决方案
  15. WPF入门8:模板(Template)
  16. easypermission坑_Android EasyPermissions官方库高效处理权限相关教程
  17. 智能名片是什么?智能名片哪三大优势?
  18. Adobe软件大全,你用过几个?
  19. opencv图像颜色反转
  20. 26个数据分析案例——第一站:基于Python的HBase冠字号查询系统

热门文章

  1. kodexplorer可道云插件之控制台
  2. 微信读书十块钱一个月?分分钟搞定它!“白嫖”才是最香的!
  3. 『我与飞桨的故事』越波飞桨逐凫鷖
  4. instr,left,mid 定位、取内容(字符串)函数
  5. 腾讯2019秋招笔试真题 1.小Q爬塔 2.妞妞的问题
  6. 阿里云天池机器学习task3
  7. 如何用Python进行数据分析
  8. 怎样才能显示计算机开机次数增多,怎么查询电脑开机次数
  9. 福利来了!html类名大合集一,需要的快收藏!
  10. PaddleHub一键视频动漫化