困扰99%前端程序员的Vue问题,全在这了

新闻 前端
最近要求使用vue进行前后端分离开发微信公众号,不断摸索踩坑之后,总结出如下几点vue项目开发中常见的问题及解决办法。

[[348925]]

 1.前言

最近要求使用vue进行前后端分离开发微信公众号,不断摸索踩坑之后,总结出如下几点vue项目开发中常见的问题及解决办法。如果你是vue大佬,请忽略小弟的愚见

  • 列表进入详情页的传参问题。
  • 本地开发环境请求服务器接口跨域的问题
  • axios封装和api接口的统一管理
  • UI库的按需加载
  • 如何优雅的只在当前页面中覆盖ui库中组件的样式
  • 定时器问题
  • rem文件的导入问题
  • Vue-Awesome-Swiper基本能解决你所有的轮播需求
  • 打包后生成很大的.map文件的问题
  • fastClick的300ms延迟解决方案
  • 组件中写选项的顺序

2.列表进入详情页传参问题

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

  1. <router-link :to="{path: 'detail', query: {id: 1}}">前往detail页面</router-link> 

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

vue传参方式有:query、params+动态路由传参。说下两者的区别:

  • query通过 path 切换路由,params通过 name 切换路由
  1. // query通过path切换路由 
  2. <router-link :to="{path: 'Detail', query: { id: 1 }}">前往Detail页面</router-link> 
  3. // params通过name切换路由 
  4. <router-link :to="{name: 'Detail', params: { id: 1 }}">前往Detail页面</router-link> 
  • query通过 this.$route.query 来接收参数,params通过 this.$route.params 来接收参数。
  1. // query通过this.$route.query接收参数 
  2. created () { 
  3.     const id = this.$route.query.id; 
  4.  
  5. // params通过this.$route.params来接收参数 
  6. created () { 
  7.     const id = this.$route.params.id; 
  • query传参的url展现方式: /detail?id=1&user=123&identity=1&更多参数 params+动态路由的url方式: /detail/123
  • params动态路由传参,一定要在路由中定义参数,然后在路由跳转的时候必须要加上参数,否则就是空白页面
  1.     path: '/detail/:id'
  2.     name: 'Detail'
  3.     component: Detail 
  4. }, 

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

  1. // 定义的路由中,只定义一个id参数 
  2.     path: 'detail/:id'
  3.     name: 'Detail'
  4.     components: Detail 
  5.  
  6. // template中的路由传参, 
  7. // 传了一个id参数和一个token参数 
  8. // id是在路由中已经定义的参数,而token没有定义 
  9. <router-link :to="{name: 'Detail', params: { id: 1, token: '123456' }}">前往Detail页面</router-link> 
  10.  
  11. // 在详情页接收 
  12. created () { 
  13.     // 以下都可以正常获取到 
  14.     // 但是页面刷新后,id依然可以获取,而token此时就不存在了 
  15.     const id = this.$route.params.id; 
  16.     const token = this.$route.params.token; 

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

 

 

 

 

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

 

 

 

 

可以看到,此时我们点击获取数据,浏览器提示我们跨域了。所以我们访问不到数据。那么接下来我们演示设置允许跨域后的数据获取情况:  

 

 

 

 

我们在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 的请求地址。这样没什么大惊小怪的,代理而已: 

 

 

 

 

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

  1. proxyTable: { 
  2.       // 用‘/api’开头,代理所有请求到目标服务器 
  3.       '/api': { 
  4.         target: 'http://jsonplaceholder.typicode.com', // 接口域名 
  5.         changeOrigin: true// 是否启用跨域 
  6.         pathRewrite: { // 
  7.           '^/api''' 
  8.         } 
  9.       } 

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

axios的封装,主要是用来帮我们进行请求的拦截和响应的拦截。在请求的拦截中我们可以携带userToken,post请求头、qs对post提交数据的序列化等。在响应的拦截中,我们可以进行根据状态码来进行错误的统一处理等等。axios接口的统一管理,是做项目时必须的流程。这样可以方便我们管理我们的接口,在接口更新时我们不必再返回到我们的业务代码中去修改接口。

4.UI库的按需加载

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

  • 安装: cnpm i vant -S
  • 安装 babel-plugin-import 插件使其按需加载: cnpm i babel-plugin-import -D
  • 在 .babelrc文件中中添加插件配置 :
  1. libraryDirectory { 
  2.      
  3.     "plugins": [ 
  4.         // 这里是原来的代码部分 
  5.         // ………… 
  6.  
  7.         // 这里是要我们配置的代码 
  8.         ["import"
  9.             { 
  10.                 "libraryName""vant"
  11.                 "libraryDirectory""es"
  12.                 "style"true 
  13.             } 
  14.         ] 
  15.     ] 
  • 在main.js中按需加载你需要的插件:
  1. // 按需引入vant组件 
  2. import { 
  3.     DatetimePicker, 
  4.     Button, 
  5.     List 
  6. } from 'vant'
  • 使用组件:
  1. // 使用vant组件 
  2. Vue.use(DatetimePicker) 
  3.     .use(Button) 
  4.     .use(List); 
  • 最后在在页面中使用:
  1. <van-button type="primary">按钮</van-button> 

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

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

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

 

 

 

 

我们正常写的所有样式,都会被加上[data-v-23d425f8]这个属性(如1所示),但是第三方组件内部的标签并没有编译为附带[data-v-23d425f8]这个属性。所以,我们想修改组件的样式,就没辙了。怎么办呢,有些小伙伴给第三方组件写个class,然后在一个公共的css文件中或者在当前页面再写一个没有socped属性的style标签,然后直接在里面修改第三方组件的样式。这样不失为一个方法,但是存在全局污染和命名冲突的问题。约定特定的命名方式,可以避免命名冲突。但是还是不够优雅。作为一名优(  )秀(  )的(  )前(  )端(  ),怎么能允许这种情况出现呢?好了,下面说下优雅的解决方式:通过深度选择器解决。例如修改上图中组件里的van-ellipsis类的样式,可以这样做:

  1. .van-tabs /deep/ .van-ellipsis { color: blue}; 

编译后的结果就是: 

 

 

 

 

这样就不会给 van-ellipsis 也添加 [data-v-23d425f8] 属性了。至此你可以愉快的修改第三方组件的样式了。当然了这里的深度选择器 /deep/ 是因为我用的 less 语言,如果你没有使用 less/sass 等,可以用 >>> 符号。更多的关于深度选择器的内容,在文章后面有介绍。

6.定时器问题

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

 

 

 

 

解决方法1

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

  1. data() { 
  2.     return { 
  3.         timer: null  // 定时器名称 
  4.     } 
  5. }, 

然后这样使用定时器:

  1. this.timer = (() => { 
  2.     // 某些操作 
  3. }, 1000

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

  1. beforeDestroy() { 
  2.     clearInterval(this.timer); 
  3.     this.timer = null

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

  1. timer 

解决方案2

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

  1. const timer = setInterval(() =>{ 
  2.     // 某些定时器操作 
  3. }, 500); 
  4. // 通过$once来监听定时器,在beforeDestroy钩子可以被清除。 
  5. this.$once('hook:beforeDestroy', () => { 
  6.     clearInterval(timer); 
  7. }) 

方案2要感谢@zzx18023在评论区提供出的解决方案。类似于其他需要在当前页面使用,离开需要销毁的组件(例如一些第三方库的picker组件等等),都可以使用此方式来解决离开后以后在背后运行的问题。综合来说,我们更推荐使用 方案2,使得代码可读性更强,一目了然 。如果不清楚once、on、$off的使用,这里送上官网的地址教程,在程序化的事件侦听器那里: https:// cn.vuejs.org/v2/guide/c omponents-edge-cases.html#%E7%A8%8B%E5%BA%8F%E5%8C%96%E7%9A%84%E4%BA%8B%E4%BB%B6%E4%BE%A6%E5%90%AC%E5%99%A8 。

7.rem文件的导入问题

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

  1. (function(c, d) { 
  2.   var e = document.documentElement || document.body, 
  3.     a = "orientationchange" in window ? "orientationchange" : "resize"
  4.     b = function() { 
  5.       var f = e.clientWidth; 
  6.       e.style.fontSize = f >= 750 ? "100px" : 100 * (f / 750) + "px"
  7.     }; 
  8.   b(); 
  9.   c.addEventListener(a, b, false); 
  10. })(window); 

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

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

在我们使用的很多ui库(vant、antiUi、elementUi等)中,都有轮播组件,对于普通的轮播效果足够了。但是,某些时候,我们的轮播效果可能比较炫,这时候ui库中的轮播可能就有些力不从心了。当然,如果技术和时间上都还可以的话,可以自己造个比较炫的轮子.这里我说一下 vue-awesome-swiper 这个轮播组件,真的非常强大,基本可以满足我们的轮播需求。swiper相信很多人都用过,很好用,也很方便我们二次开发,定制我们需要的轮播效果。vue-awesome-swiper组件实质上基于swiper的,或者说就是能在vue中跑的 swiper 。下面说下怎么使用:

  1. cnpm install vue-awesome-swiper --save 

 

  1. // 引入组件 
  2. import 'swiper/dist/css/swiper.css' 
  3. import { swiper, swiperSlide } from 'vue-awesome-swiper' 
  4.  
  5. // 在components中注册组件 
  6. components: { 
  7.     swiper, 
  8.     swiperSlide 
  9.  
  10. // template中使用轮播 
  11. // ref是当前轮播 
  12. // callback是回调 
  13. // 更多参数用法,请参考文档 
  14. <swiper :options="swiperOption" ref="mySwiper" @someSwiperEvent="callback"
  15.     <!-- slides --> 
  16.     <swiper-slide><div class="item">1</div></swiper-slide> 
  17.     <swiper-slide><div class="item">2</div></swiper-slide> 
  18.     <swiper-slide><div class="item">3</div></swiper-slide> 
  19.            
  20.     <!-- Optional controls --> 
  21.     <div class="swiper-pagination"  slot="pagination"></div> 
  22.     <div class="swiper-button-prev" slot="button-prev"></div> 
  23.     <div class="swiper-button-next" slot="button-next"></div> 
  24.     <div class="swiper-scrollbar"   slot="scrollbar"></div> 
  25. </swiper> 
  26. // 参数要写在data中 
  27. data() { 
  28.     return { 
  29.         // swiper轮播的参数 
  30.         swiperOption: { 
  31.             // 滚动条 
  32.             scrollbar: { 
  33.                 el: '.swiper-scrollbar'
  34.             }, 
  35.             // 上一张,下一张 
  36.             navigation: { 
  37.                 nextEl: '.swiper-button-next'
  38.                 prevEl: '.swiper-button-prev'
  39.             }, 
  40.             // 其他参数………… 
  41.         } 
  42.     } 
  43. }, 

swiper需要配置哪些功能需求,自己根据文档进行增加或者删减。附上文档:npm文档,swiper3.0/4.0文档,更多用法,请参考文档说明。

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

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

 

 

 

 

10.fastClick的300ms延迟解决方案

开发移动端项目,点击事件会有300ms延迟的问题。至于为什么会有这个问题,请自行百度即可。这里只说下常见的解决思路,不管vue项目还是jq项目,都可以使用 fastClick 解决。安装 fastClick :

  1. cnpm install fastclick -S 

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

  1. import FastClick from 'fastclick'// 引入插件 
  2. FastClick.attach(document.body); // 使用 fastclick 

11.组件中写选项的顺序

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

    • 副作用 (触发组件外的影响)
      • el
    • 全局感知 (要求组件以外的知识)
      • name
      • parent
    • 组件类型 (更改组件的类型)
      • functional
    • 模板修改器 (改变模板的编译方式)
  1. delimiters 
  2. comments 
  • 模板依赖 (模板内使用的资源)
  1. components 
  2. directives 
  3. filters 
    • 组合 (向选项里合并属性)
  1. extends 
  2. mixins 
    • 接口 (组件的接口)
      • inheritAttrs
      • model
      • props / propsData
    • 本地状态 (本地的响应式属性)
  1. data 
  2. computed 
    • 事件 (通过响应式事件触发的回调)
  1. beforeCreate 
  2. created 
  3. beforeMount 
  4. mounted 
  5. beforeUpdate 
  6. updated 
  7. activated 
  8. deactivated 
  9. beforeDestroy 
  10. destroyed 
  11. watch 
  • 非响应式的属性 (不依赖响应系统的实例属性)
    • methods
  • 渲染 (组件输出的声明式描述)
    • template / render
    • renderError

 

 

责任编辑:张燕妮 来源: 知乎
相关推荐

2022-08-01 09:43:19

程序员Googlefacebook

2018-11-01 15:20:17

前端程序员编程语言

2020-08-05 07:53:53

程序员网站技术

2019-12-03 10:04:18

程序员招聘开发

2021-10-18 21:41:10

Go程序员 Defer

2023-11-13 08:34:01

Java编程习惯

2018-07-17 09:08:55

程序员技能区块链

2017-11-13 20:27:09

程序员月薪技术

2018-08-02 17:00:15

Vue.js学习iOS开发

2016-09-25 22:13:11

程序员代码编码

2022-07-15 08:20:54

Java基础知识

2012-06-08 09:57:25

2019-11-23 23:21:44

程序员前端全栈

2022-08-08 15:45:44

JavaPromise前端

2015-12-04 09:33:15

程序员前端演进史

2015-11-03 15:17:28

前端程序员特色

2013-08-20 09:33:59

程序员

2015-11-04 10:30:06

前端文艺范程序员

2012-02-23 15:02:46

程序员

2011-06-08 10:24:58

程序员
点赞
收藏

51CTO技术栈公众号