1.前言
最近要求使用vue进行前后端分离开发微信公众号,不断摸索踩坑之后,总结出如下几点vue项目开发中常见的问题及解决办法。如果你是vue大佬,请忽略小弟的愚见
2.列表进入详情页传参问题
例如商品列表页面前往商品详情页面,需要传一个商品id;
<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+动态路由传参。说下两者的区别:
path
?切换路由,params通过?name
?切换路由//?query通过path切换路由?<router-link?:to="{path:?'Detail',?query:?{?id:?1?}}">前往Detail页面</router-link>?//?params通过name切换路由?<router-link?:to="{name:?'Detail',?params:?{?id:?1?}}">前往Detail页面</router-link>?
this.$route.query
?来接收参数,params通过?this.$route.params
?来接收参数。//?query通过this.$route.query接收参数?created?()?{?????const?id?=?this.$route.query.id;?}??//?params通过this.$route.params来接收参数?created?()?{?????const?id?=?this.$route.params.id;?}?
/detail?id=1&user=123&identity=1&更多参数
?params+动态路由的url方式:?/detail/123
{?????path:?'/detail/:id',?????name:?'Detail',?????component:?Detail?},?
注意,params传参时,如果没有在路由中定义参数,也是可以传过去的,同时也能接收到,但是一旦刷新页面,这个参数就不存在了。这对于需要依赖参数进行某些操作的行为是行不通的,因为你总不可能要求用户不能刷新页面吧。例如:
//?定义的路由中,只定义一个id参数?{?????path:?'detail/:id',?????name:?'Detail',?????components:?Detail?}??//?template中的路由传参,?//?传了一个id参数和一个token参数?//?id是在路由中已经定义的参数,而token没有定义?<router-link?:to="{name:?'Detail',?params:?{?id:?1,?token:?'123456'?}}">前往Detail页面</router-link>??//?在详情页接收?created?()?{?????//?以下都可以正常获取到?????//?但是页面刷新后,id依然可以获取,而token此时就不存在了?????const?id?=?this.$route.params.id;?????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的代码:
proxyTable:?{???????//?用‘/api’开头,代理所有请求到目标服务器???????'/api':?{?????????target:?'http://jsonplaceholder.typicode.com',?//?接口域名?????????changeOrigin:?true,?//?是否启用跨域?????????pathRewrite:?{?//???????????'^/api':?''?????????}???????}?}?
注意:配置好后一定要关闭原来的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
libraryDirectory?{??????????"plugins":?[?????????//?这里是原来的代码部分?????????//?…………??????????//?这里是要我们配置的代码?????????["import",?????????????{?????????????????"libraryName":?"vant",?????????????????"libraryDirectory":?"es",?????????????????"style":?true?????????????}?????????]?????]?}?
//?按需引入vant组件?import?{?????DatetimePicker,?????Button,?????List?}?from?'vant';?
//?使用vant组件?Vue.use(DatetimePicker)?????.use(Button)?????.use(List);?
<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类的样式,可以这样做:
.van-tabs?/deep/?.van-ellipsis?{?color:?blue};?
编译后的结果就是:
这样就不会给?van-ellipsis
?也添加?[data-v-23d425f8]
?属性了。至此你可以愉快的修改第三方组件的样式了。当然了这里的深度选择器?/deep/
?是因为我用的?less
?语言,如果你没有使用?less/sass
?等,可以用?>>>
?符号。更多的关于深度选择器的内容,在文章后面有介绍。
6.定时器问题
我在a页面写一个定时,让他每秒钟打印一个1,然后跳转到b页面,此时可以看到,定时器依然在执行。这样是非常消耗性能的。如下图所示:
解决方法1
首先我在data函数里面进行定义定时器名称:
data()?{?????return?{?????????timer:?null??//?定时器名称?????}?},?
然后这样使用定时器:
this.timer?=?(()?=>?{?????//?某些操作?},?1000)?
最后在beforeDestroy()生命周期内清除定时器:
beforeDestroy()?{?????clearInterval(this.timer);?????this.timer?=?null;?}?
方案1有两点不好的地方,引用尤大的话来说就是:
timer?
该方法是通过$once这个事件侦听器器在定义完定时器之后的位置来清除定时器。以下是完整代码:
const?timer?=?setInterval(()?=>{?????//?某些定时器操作?},?500);?//?通过$once来监听定时器,在beforeDestroy钩子可以被清除。?this.$once('hook:beforeDestroy',?()?=>?{?????clearInterval(timer);?})?
方案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大小,基本上小伙伴们都知道,这里直接附上代码,不多做介绍。
(function(c,?d)?{???var?e?=?document.documentElement?||?document.body,?????a?=?"orientationchange"?in?window???"orientationchange"?:?"resize",?????b?=?function()?{???????var?f?=?e.clientWidth;???????e.style.fontSize?=?f?>=?750???"100px"?:?100?*?(f?/?750)?+?"px";?????};???b();???c.addEventListener(a,?b,?false);?})(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
?。下面说下怎么使用:
cnpm?install?vue-awesome-swiper?--save?
//?引入组件?import?'swiper/dist/css/swiper.css'?import?{?swiper,?swiperSlide?}?from?'vue-awesome-swiper'??//?在components中注册组件?components:?{?????swiper,?????swiperSlide?}??//?template中使用轮播?//?ref是当前轮播?//?callback是回调?//?更多参数用法,请参考文档?<swiper?:options="swiperOption"?ref="mySwiper"?@someSwiperEvent="callback">?????<!--?slides?-->?????<swiper-slide><div?class="item">1</div></swiper-slide>?????<swiper-slide><div?class="item">2</div></swiper-slide>?????<swiper-slide><div?class="item">3</div></swiper-slide>????????????????<!--?Optional?controls?-->?????<div?class="swiper-pagination"??slot="pagination"></div>?????<div?class="swiper-button-prev"?slot="button-prev"></div>?????<div?class="swiper-button-next"?slot="button-next"></div>?????<div?class="swiper-scrollbar"???slot="scrollbar"></div>?</swiper>?//?参数要写在data中?data()?{?????return?{?????????//?swiper轮播的参数?????????swiperOption:?{?????????????//?滚动条?????????????scrollbar:?{?????????????????el:?'.swiper-scrollbar',?????????????},?????????????//?上一张,下一张?????????????navigation:?{?????????????????nextEl:?'.swiper-button-next',?????????????????prevEl:?'.swiper-button-prev',?????????????},?????????????//?其他参数…………?????????}?????}?},?
swiper需要配置哪些功能需求,自己根据文档进行增加或者删减。附上文档:npm文档,swiper3.0/4.0文档,更多用法,请参考文档说明。
9.打包后生成很大的.map文件的问题
项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。 而生成的.map后缀的文件,就可以像未加密的代码一样,准确的输出是哪一行哪一列有错可以通过设置来不生成该类文件。但是我们在生成环境是不需要.map文件的,所以可以在打包时不生成这些文件:在?config/index.js
?文件中,设置?productionSourceMap: false,
?就可以不生成?.map
?文件?
10.fastClick的300ms延迟解决方案
开发移动端项目,点击事件会有300ms延迟的问题。至于为什么会有这个问题,请自行百度即可。这里只说下常见的解决思路,不管vue项目还是jq项目,都可以使用?fastClick
?解决。安装?fastClick
?:
cnpm?install?fastclick?-S?
在main.js中引入fastClick和初始化:
import?FastClick?from?'fastclick';?//?引入插件?FastClick.attach(document.body);?//?使用?fastclick?
11.组件中写选项的顺序
为什么选项要有统一的书写顺序呢?很简单,就是要将选择和认知成本最小化。
el
functional
delimiters?comments?
components?directives?filters?
extends?mixins?
inheritAttrs
model
props
?/?propsData
data?computed?
beforeCreate?created?beforeMount?mounted?beforeUpdate?updated?activated?deactivated?beforeDestroy?destroyed?watch?
methods
template
?/?render
renderError
领取专属 10元无门槛券
私享最新 技术干货