vs code vue插件_干货分享 | Vue框架常见问题浅谈

8041db865d33e2293cb5f4a29a6e4698.png

ba9ef59ff60ac7bf1364f9fb0d50fd4a.png

友情提示:全文7800多文字,预计阅读时间10分钟

812c9af4907d01cbf14d720e13de2e89.gif

Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。使用其进行前后端分离开发前端业务平台,不断摸索踩坑之后,总结出如下几点Vue项目开发中常见的问题及解决办法,希望与君共同探讨。

  • 列表进入详情页的传参问题

  • 本地开发环境请求服务器接口跨域的问题

  • axios封装和api接口的统一管理

  • UI库的按需加载

  • 如何优雅的只在当前页面中覆盖ui库中组件的样式

  • 定时器问题

  • rem文件的导入问题

  • Vue-Awesome-Swiper基本能解决你所有的轮播需求

  • 打包后生成很大的.map文件的问题

  • fastClick的300ms延迟解决方案

  • 组件中写选项的顺序

  • 路由懒加载(也叫延迟加载)

  • 开启gzip压缩代码

  • 详情页返回列表页缓存数据和浏览位置、其他页面进入列表页刷新数据的实践

  • css的scoped私有作用域和深度选择器

  • hiper打开速度测试

  • 路由的拆分管理

  • 可选链操作符

  • 利用闭包构造map缓存数据

812c9af4907d01cbf14d720e13de2e89.gif 812c9af4907d01cbf14d720e13de2e89.gif

列表进入详情页的传参问题

812c9af4907d01cbf14d720e13de2e89.gif

例如租户列表页面前往租户详情页面,需要传一个租户id;

9be87a8a0d93c1ae2d7761032f4c3947.png

页面的路径为http://localhost:8080/#/detail?code=1,可以看到传了一个参数code=1,并且就算刷新页面id也还会存在。此时在页面可以通过code来获取对应的详情数据,获取code的方式是this.$route.query.code

vue传参方式有:query、params+动态路由传参。

说下两者的区别:

1、query通过path切换路由,params通过name切换路由

1703df21d9ebe69175fa34acd7f1343e.png

2、query通过this.$route.query来接收参数,params通过this.$route.params来接收参数。

4ac8b1c73207620dbe42e805a3356c88.png

3、query传参的url展现方式:

/detail?code=1&user=123&identity=1&更多参数

params+动态路由的url方式:/detail/123

4、params动态路由传参,一定要在路由中定义参数,然后在路由跳转的时候必须要加上参数,否则就是空白页面:

8f86f32f3ed6b67b0b16f92832031333.png

注意,params传参时,如果没有在路由中定义参数,也是可以传过去的,同时也能接收到,但是一旦刷新页面,这个参数就不存在了。这对于需要依赖参数进行某些操作的行为是行不通的,因为你总不可能要求用户不能刷新页面吧。例如:

13077ab5abbad43cf22b84ce3e32e6c8.png 812c9af4907d01cbf14d720e13de2e89.gif

本地开发环境请求服务器接口跨域的问题

812c9af4907d01cbf14d720e13de2e89.gif 56bdaefee4f8831cbab8550abb914415.png

上面的这个报错大家都不会陌生,报错是说没有访问权限(跨域问题)。本地开发项目请求服务器接口的时候,因为客户端的同源策略,导致了跨域的问题。

下面先演示一个没有配置允许本地跨域的的情况:

6d1e8073fb740c298abf0e6ae4271283.png 13436096b1d933fba072acde1e66b5ef.png

可以看到,此时我们点击获取数据,浏览器提示我们跨域了。所以我们访问不到数据。

那么接下来我们演示设置允许跨域后的数据获取情况:

1953451f7e0a7a014b0d7d8f98601162.png

注意:配置好后一定要关闭原来的server,重新npm run dev启动项目。不然无效。

414a2ff01c385415986b29d6723b4a03.png daa25da86624c133b4b513519b76773b.png

我们在1处设置了允许本地跨域,在2处,要注意我们访问接口时,写的是/api,此处的/api指代的就是我们要请求的接口域名。如果我们不想每次接口都带上/api,可以更改axios的默认配置axios.defaults.baseURL = '/api';

这样,我们请求接口就可以直接this.$axios.get('app.php?m=App&c=Index&a=index'),很简单有木有。此时如果你在network中查看xhr请求,你会发现显示的是localhost:8080/api的请求地址。这样没什么大惊小怪的,代理而已:

9b5c618a33bd7a93349127073487e563.png

好了,最后附上proxyTable的代码:

dd11e5a50750739e794e177543d8aee1.png

注意:配置好后一定要关闭原来的server,重新npm run dev启动项目。不然无效。

812c9af4907d01cbf14d720e13de2e89.gif

axios封装和api接口的统一管理

812c9af4907d01cbf14d720e13de2e89.gif

axios的封装,主要是用来帮我们进行请求的拦截和响应的拦截。

在请求的拦截中我们可以携带userToken,post请求头、qs对post提交数据的序列化等。

在响应的拦截中,我们可以进行根据状态码来进行错误的统一处理等等。

axios接口的统一管理,是做项目时必须的流程。这样可以方便我们管理我们的接口,在接口更新时我们不必再返回到我们的业务代码中去修改接口。

由于这里内容稍微多一些,放在另一篇文章。

812c9af4907d01cbf14d720e13de2e89.gif

UI库的按需加载

812c9af4907d01cbf14d720e13de2e89.gif

为什么要使用按需加载的方式而不是一次性全部引入,原因就不多说了。这里以Element 的按需加载为例,演示vue中ui库怎样进行按需加载:

  • 安装:npm i element-ui -S

  • 安装babel-plugin-import插件使其按需加载:npm i babel-plugin-import -D

  •  在 .babelrc文件中中添加插件配置:

cbc8cb0719143313f3358aef7e149102.png
  • 在main.js中按需加载你需要的插件:

85c72de7c4817789a1bd10929d0f185d.png
  • 使用组件:

effdfc632bf4b0f44e92db4a8b44870e.png
  • 最后在在页面中使用:

47569ea67aaef132fba76e41e3fd3e73.png

ps:除了elementUi库外,像antiUi、vant等,很多ui库都支持按需加载,可以去看文档,上面都会有提到。基本都是通过安装babel-plugin-import插件来支持按需加载的,使用方式与elementUi的如出一辙,可以去用一下。

812c9af4907d01cbf14d720e13de2e89.gif

如何优雅的只在当前页面中覆盖ui库中组件的样式

812c9af4907d01cbf14d720e13de2e89.gif

首先我们vue文件的样式都是写在标签中的,加scoped是为了使得样式只在当前页面有效。那么问题来了,看图:

b9c493c15e32fada2e90e04207689688.png

我们正常写的所有样式,都会被加上[data-v-08ec2874]这个属性(如上图所示),但是第三方组件内部的标签并没有编译为附带[data-v-08ec2874]这个属性。所以,我们想修改组件的样式,就没辙了。怎么办呢,有些小伙伴给第三方组件写个class,然后在一个公共的css文件中或者在当前页面再写一个没有socped属性的style标签,然后直接在里面修改第三方组件的样式。这样不失为一个方法,但是存在全局污染和命名冲突的问题。约定特定的命名方式,可以避免命名冲突。但是还是不够优雅。

作为一名优(强)秀(迫)的(症)前(患)端(者),怎么能允许这种情况出现呢?好了,下面说下优雅的解决方式:

通过深度选择器解决。例如修改上图中组件里的el-tab-item类的样式,可以这样做:

1d930c7c05bf10572e6c03aa74c12217.png

编译后的结果就是:

1881e3e90e10d50d278033e940cc30b1.png

这样就不会给el-tab也添加[data-v-08ec2874]属性了。至此你可以愉快的修改第三方组件的样式了。

当然了这里的深度选择器/deep/是因为我用的less语言,如果你没有使用less/sass等,可以用>>>符号。

812c9af4907d01cbf14d720e13de2e89.gif

定时器问题

812c9af4907d01cbf14d720e13de2e89.gif

我在页面a写一个定时,让他每秒钟打印一个1,然后跳转到页面b,此时可以看到,定时器依然在执行。这样是非常消耗性能的。

解决方案1:

首先我在data函数里面进行定义定时器名称:

57003707539e0ff06a826e59f8356f03.png

然后这样使用定时器:

ed260bb1215e89a1f29a8908dd53ea01.png

最后在beforeDestroy()生命周期内清除定时器:

048647397c7047fc05f0fef139157c2c.png

方案1有两点不好的地方,引用尤大的话来说就是:

  • 它需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。

  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。

解决方案2:

该方法是通过$once这个事件侦听器器在定义完定时器之后的位置来清除定时器。以下是完整代码:

1ffea4bdf2d83f02a947c103fa4f4801.png

类似于其他需要在当前页面使用,离开需要销毁的组件(例如一些第三方库的picker组件等等),都可以使用此方式来解决离开后以后在背后运行的问题。

综合来说,我们更推荐使用解决方案2,使得代码可读性更强,一目了然。

如果不清楚$once、$on、$off的使用,这里送上官网的地址教程,在程序化的事件侦听器那里。(官网地址:https://cn.vuejs.org/v2/guide/)

812c9af4907d01cbf14d720e13de2e89.gif

rem文件的导入问题

812c9af4907d01cbf14d720e13de2e89.gif

我们在做手机端时,适配是必须要处理的一个问题。例如,我们处理适配的方案就是通过写一个rem.js,原理很简单,就是根据网页尺寸计算html的font-size大小,基本上小伙伴们都知道,这里直接附上代码,不多做介绍。

2bb10185348205930b569d1c6c79afa3.png

这里说下怎么引入的问题,很简单。在main.js中,直接import './config/rem'导入即可。import的路径根据你的文件路径去填写。

812c9af4907d01cbf14d720e13de2e89.gif

打包后生成很大的.map文件的问题

812c9af4907d01cbf14d720e13de2e89.gif

项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。 而生成的.map后缀的文件,就可以像未加密的代码一样,准确的输出是哪一行哪一列有错可以通过设置来不生成该类文件。但是我们在生产环境是不需要.map文件的,所以可以在打包时不生成这些文件:

在config/index.js文件中,设置productionSourceMap: false,就可以不生成.map文件。

812c9af4907d01cbf14d720e13de2e89.gif

fastClick的300ms延迟解决方案

812c9af4907d01cbf14d720e13de2e89.gif

开发移动端项目,点击事件会有300ms延迟的问题。

这里只说下常见的解决思路,不管vue项目还是jq项目,都可以使用fastClick解决。

安装 fastClick:

7a6446f4b08d747458a1293cdf7975a2.png

在main.js中引入fastClick和初始化:

62eb295d2ab6c5f339de98c6e160c7a4.png 812c9af4907d01cbf14d720e13de2e89.gif

组件中写选项的顺序

812c9af4907d01cbf14d720e13de2e89.gif

为什么选项要有统一的书写顺序呢?很简单,就是要将选择和认知成本最小化。

1、副作用 (触发组件外的影响)

·  el

2、全局感知 (要求组件以外的知识)

·  name

·  parent

3、组件类型 (更改组件的类型)

·  functional

4、模板修改器 (改变模板的编译方式)

·  delimiters

有时候我们使用"{{内容}}",当后台的插值符跟现有的插值符有冲突时,这时就要改变我们的插值符

·  comments

5、模板依赖 (模板内使用的资源)

·  components

·  directives

·  filters

6、组合(向选项里合并属性)

·  extends

·  mixins

7、接口(组件的接口)

·  inheritAttrs

·  model

·  props/propsData

8、本地状态(本地的响应式属性)

·  data

·  computed

9、事件(通过响应式事件触发的回调)

·  watch

· 生命周期钩子(按照它们被调用的顺序)

- beforeCreate- created- beforeMount- mounted- beforeUpdate- updated- activated- deactivated- beforeDestroy- destroyed

10、非响应式的属性(不依赖响应系统的实例属性)

·  methods

11、渲染(组件输出的声明式描述)

·  template

·  render

·  renderError

812c9af4907d01cbf14d720e13de2e89.gif

查看打包后各文件的体积  帮你快速定位大文件

812c9af4907d01cbf14d720e13de2e89.gif

如果你是vue-cli初始化的项目,会默认安装webpack-bundle-analyzer插件,该插件可以帮助我们查看项目的体积结构对比和项目中用到的所有依赖。也可以直观看到各个模块体积在整个项目中的占比。

03f59e27da58e1baa36a6b13f6a748fb.png 8c9434567e306e8d753c49af662d7dba.png

记得运行的时候先把之前**npm run dev**开启的本地关掉。

812c9af4907d01cbf14d720e13de2e89.gif

路由懒加载(也叫延迟加载)

812c9af4907d01cbf14d720e13de2e89.gif

路由懒加载可以帮我们在进入首屏时不用加载过度的资源,从而减少首屏加载速度。

路由文件中,

非懒加载写法:

873803958d86a561124c07d0f47bf1e0.png

路由懒加载写法:

6910ee19c043cc23b4fa87706aa3b8fa.png 812c9af4907d01cbf14d720e13de2e89.gif

开启gzip压缩代码

812c9af4907d01cbf14d720e13de2e89.gif

spa这种单页应用,首屏由于一次性加载所有资源,所有首屏加载速度很慢。解决这个问题非常有效的手段之一就是前后端开启gizp(其他还有缓存、路由懒加载等等)。gizp其实就是帮我们减少文件体积,能压缩到30%左右,即100k的文件gizp后大约只有30k。

vue-cli初始化的项目中,是默认有此配置的,只需要开启即可。但是需要先安装插件:

5632b1c83b939938bd3753629107f78a.png

然后在config/index.js中开启即可:

现在打包的时候,除了会生成之前的文件,还是生成.gz结束的gzip过后的文件。具体实现就是如果客户端支持gzip,那么后台后返回gzip后的文件,如果不支持就返回正常没有gzip的文件。

注意:这里前端进行的打包时的gzip,但是还需要后台服务器的配置。配置是比较简单的,配置几行代码就可以了,一般这个操作可以叫运维小哥哥小姐姐去搞一下,没有运维的让后台去帮忙配置。

812c9af4907d01cbf14d720e13de2e89.gif

详情页返回列表页缓存数据和浏览位置、

其他页面进入列表页刷新数据的实践

812c9af4907d01cbf14d720e13de2e89.gif

这样一个场景:有两个页面,用户列表页,用户列表详情页。我们希望从用户列表页进入用户列表详情页时,用户列表详情页面要刷新数据,从用户列表详情页进入详情页再返回到用户列表详情页面时,我们不希望刷新,我们希望此时的分类页面能够缓存已加载的数据和自动保存用户上次浏览的位置。

解决这种场景需求我们可以通过vue提供的keepAlive属性。除此之外,还需要配合activated、beforeRouteLeave、beforeRouteEnter钩子函数进行处理;如下:

ca578c17eb1cda92b297db0211c35e48.png 812c9af4907d01cbf14d720e13de2e89.gif

CSS的scoped私有作用域和深度选择器

812c9af4907d01cbf14d720e13de2e89.gif

大家都知道当 

编译前:

loading.gif

编译后:

loading.gif

其实是我们在写的组件的样式,添加了一个属性而已,这样就实现了所谓的私有作用域。但是也会有弊端,考虑到浏览器渲染各种 CSS 选择器的方式,当 div { color: #409EFF } 设置了作用域时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id 取而代之,比如 .user-manage-container { color: #409EFF },性能影响就会消除。所以,在你的样式里,进来避免直接使用标签,取而代之的你可以给标签起个class名。

如果我们希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符:

loading.gif

代码将会编译成:

loading.gif

而对于less或者sass等预编译,是不支持>>>操作符的,可以使用/deep/来替换>>>操作符,例如:.parent /deep/ .child { /* ... */ }

loading.gif

Hiper:一款性能分析工具

loading.gif loading.gif

如上图,是hiper工具的测试结果,从中我们可以看到DNS查询耗时、TCP连接耗时、第一个Byte到达浏览器的用时、页面下载耗时、DOM Ready之后又继续下载资源的耗时、白屏时间、DOM Ready 耗时、页面加载总耗时。

在编辑器终端中全局安装:

loading.gif

使用:**终端输入命令:hiper 测试的网址

loading.gif

上述的用法示例,我直接拷贝的文档说明,具体的可以看下文档,这里送上链接。当我们项目打开速度慢时,这个工具可以帮助我们快速定位出到底在哪一步影响的页面加载的速度。

平时我们查看性能的方式,是在performance和network中看数据,记录下几个关键的性能指标,然后刷新几次再看这些性能指标。有时候我们发现,由于样本太少,受当前「网络」、「CPU」、「内存」的繁忙程度的影响很重,有时优化后的项目反而比优化前更慢。

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

loading.gif

路由拆分管理

loading.gif

我这里说的路由拆分指的是将路由的文件,按照模块拆分,这样方便路由的管理,更主要的是方便多人开发。具体要不要拆分,那就要视你的项目情况来定了,如果项目较小的话,也就一二十个路由,那么是拆分是非常没必要的。但倘若你开发一些功能点较多的C端项目,路由可以会有一百甚至几百个,那么此时将路由文件进行拆分是很有必要的。不然,index.js文件中一大长串串串串串串的路由,也是很糟糕的。

loading.gif

首先我们在router文件夹中创建一个index.js作为路由的入口文件,然后新建一个modules文件夹,里面存放各个模块的路由文件。例如这里储存了一个user.js用户模块的路由文件和一个公共模块的路由文件。下面直接上index.js吧,而后再简单介绍:

loading.gif

首先引入vue和router最后导出,基本的操作。

这里把路由守卫router.beforeEach的操作写了router的index.js文件中,有些人可能会写在main.js中,这也没有错,只不过,个人而言,既然是路由的操作,还是放在路由文件中管理更好些。这里就顺便演示了,如何在页面切换时,自动修改页面标题的操作。

而后引入你根据路由模块划分的各个js文件,然后在实例化路由的时候,在routes数组中,将导入的各个文件通过结构赋值的方法取出来。最终的结果和正常的写法是一样的。

然后看下我们导入的user.js:

loading.gif

这里就是将用户模块的路由放在一个数组中导出去。整个路由拆分的操作,不是vue的知识,就是一个es6导入导出和结构的语法。具体要不要拆分,还是因项目和环境而异吧。这里的路由用到了懒加载路由的方式,如果不清楚,文字上面有介绍到。还有meta元字段中,定义了一个title信息,用来存储当前页面的页面标题,即document.title。

loading.gif

可选链操作符

loading.gif

js可选链操作“.?”双问号“??”(babel-plugin-proposal-optional-chainingplugin-proposal-nullish-coalescing-operator)

可选链操作符 “?.” 可以按照操作符之前的属性是否有效,链式读取对象的属性或者使整个对象链返回 undefined。“?.” 运算符的作用与“.”运算符类似,不同之处在于,如果对象链上的引用是 nullish (null 或者 undefined),“.” 操作符会抛出一个错误,而“ ?. ”操作符则会按照短路计算的方式进行处理,返回 undefined。可选链操作符也可用于函数调用,如果操作符前的函数不存在,也将会返回 undefined。

上面是官方解释,假设你是第一次看到类似下面这类语法,大概你是懵逼的。

loading.gif loading.gif

上面的代码很容易产生错误,你大概会这么取值

插件安装

loading.gif

基本语法(可选链)

obj?.prop

obj?.[expr]

arr?.[index]

func?.(args)

loading.gif

短路:遇到 null/undefined 停止

可选链接运算符的有趣之处在于,只要在左侧person?.details 遇到无效值,右侧访问就会停止,通常会返回undefined ,这称为短路。

默认值(双问号)

loading.gif loading.gif

这个运算符就是“??”,如果它左侧表达式的结果是 undefined,personData,就会取右侧的"默认值"(stranger)。是不是跟运算符“||” 很像,其实“??” 就是为了取代“||” ,来做设置默认值这件事的。

兼容性

尽管这个特性很完美,但想直接在项目中使用还是不太现实的,好在还有babel,插件babel-plugin-proposal-optional-chaining、plugin-proposal-nullish-coalescing-operator

loading.gif loading.gif loading.gif

本地开发环境请求服务器接口跨域的问题

loading.gif

vue中判断我们写的组件名是不是html内置标签的时候,如果用数组类遍历那么将要循环很多次获取结果,如果把数组转为对象,把标签名设置为对象的key,那么不用依次遍历查找,只需要查找一次就能获取结果,提高了查找效率。

loading.gif

END

作者简介

loading.gif

蔡巍巍

2019年12月加入中国移动云能力中心,Iaas产品部云管中台组组员,主要负责云管中台前端相关研发、管理工作。

 往期 · 精选 

1、干货分享 | 服务注册中心Spring Cloud Eureka部分源码分析

2、干货分享 | 深入浅出基于Kpatch的内核热补丁技术

3、干货分享 | BRAFT快速上手-实践篇

loading.gif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值