Vue 项目里戳中你痛点的问题及解决办法

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

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

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

  • API接口的统一管理

  • UI库的按需加载

  • 定时器问题

  • rem文件的导入问题

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

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

  • fastClick的300ms延迟解决方案

  • 组件中写选项的顺序

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

  • 开启gzip压缩代码

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

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

===========================这是华丽丽的分割线~~=========================

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

例如商品列表页面前往商品详情页面,需要传一个商品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+动态路由传参。

说下两者的区别:

1.query通过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>
2.query通过`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;
}
3.query传参的url展现方式:/detail?id=1&user=123&identity=1&更多参数

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

4.params动态路由传参,一定要在路由中定义参数,然后在路由跳转的时候必须要加上参数,否则就是空白页面:
{      
    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;
}

个人角度来说,我是更喜欢query来传参的,更灵活自由。PS:生命诚可贵,爱情价更高。若为自由故,两者皆可抛呀~~~

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

[图片上传中...(image-e71731-1530834775408-3)]

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

vue-cli初始化的项目,在配置文件中提供了proxyTable来解决本地开发的跨域问题。config文件的index.js文件中,找到proxyTable选项,进行如下配置:

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

例如请求接口:/api/posts/1 ==>http://jsonplaceholder.typicode.com/posts/1

这个时候就可以在本地环境请求后台接口了。

axios的封装和API接口的统一管理:

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

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

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

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

由于这里内容稍微多一些,日后放在另一篇文章,更新后这里会送上链接。

UI库的按需加载:

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

  • 安装: cnpm i vant -S
  • 安装babel-plugin-import插件使其按需加载: cnpm i babel-plugin-import -D
  • 在 .babelrc文件中中添加插件配置 :
libraryDirectory { 

    "plugins": [ 
        // 这里是原来的代码部分
        // …………

        // 这里是要我们配置的代码
        ["import", 
            { 
                "libraryName": "vant", 
                "libraryDirectory": "es", 
                "style": true 
            }
        ] 
    ] 
}
  • 在main.js中按需加载你需要的插件:
// 按需引入vant组件
import {   
    DatetimePicker,   
    Button,   
    List 
} from 'vant';
  • 使用组件:
// 使用vant组件
Vue.use(DatetimePicker)  
    .use(Button)  
    .use(List);
  • 最后在在页面中使用:
<van-button type="primary">按钮</van-button>

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

定时器问题:

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

[图片上传中...(image-bbb27b-1530834775407-2)]

[图片上传中...(image-22274c-1530834775407-1)]

解决方法1:

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

data() {            
    return {                              
        timer: null  // 定时器名称          
    }        
},

然后这样使用定时器:

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

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

beforeDestroy() {
    clearInterval(this.timer);        
    this.timer = null;
}

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

  • 它需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。

解决方案2:

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

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

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

综合来说,我们更推荐使用方案2,使得代码可读性更强,一目了然。如果不清楚$once、$on、$off的使用,这里送上官网的地址教程,在程序化的事件侦听器那里

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的路径根据你的文件路径去填写。

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文档,更多用法,请参考文档说明。

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

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

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

[图片上传中...(image-c1ad26-1530834775405-0)]

fastClick的300ms延迟解决方案

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

安装 fastClick:

cnpm install fastclick -S

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

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

组件中写选项的顺序

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

  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`
  1. 渲染 (组件输出的声明式描述)
*   `template`/`render`
*   `renderError`

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

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

[图片上传中...(image-f5c75e-1530834775415-4)]

npm run build --report // 直接运行,然后在浏览器打开http://127.0.0.1:8888/即可查看

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

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

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

路由文件中,

非懒加载写法:

import Index from '@/page/index/index';
export default new Router({  
    routes: [    
        { 
            path: '/', 
            name: 'Index',     
            component: Index 
        }
    ]
})

路由懒加载写法:

export default new Router({
  routes: [    
        { 
            path: '/', 
            name: 'Index', 
            component: resolve => require(['@/view/index/index'], resolve) 
        }
   ]
})

开启gzip压缩代码

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

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

cnpm i compression-webpack-plugin

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

build: {
    ………………
    productionGzip: true, // false不开启gizp,true开启
    ………………
}

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

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

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

这样一个场景:有三个页面,首页/或者搜索页,商品分类页面,商品详情页。我们希望从首页进入分类页面时,分类页面要刷新数据,从分类进入详情页再返回到分类页面时,我们不希望刷新,我们希望此时的分类页面能够缓存已加载的数据和自动保存用户上次浏览的位置。之前在百度搜索的基本都是keep-alive处理的,但是总有那么一些不完善,所以自己在总结了之后进行了如下的实践。

解决这种场景需求我们可以通过vue提供的keepAlive属性。这里直接送上另一篇处理这个问题的传送门

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

大家都知道当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。那么他是怎么实现的呢,大家看一下编译前后的代码就明白了:

编译前:

<style scoped>
.example {
  color: red;
}
</style>

编译后:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}

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

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

<style scoped>
    .parent >>> .child { /* ... */ }
</style>

上述代码将会编译成:

.parent[data-v-f3f3eg9] .child { 
    /* ... */ 
}

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

==================================

后面会继续更新:

  • restful路由设计
  • axios封装和api接口的统一管理
  • hiper打开速度测试
  • postcss在vue中的使用和相关插件
  • porp双向数据流的实践
  • vue插件的开发
  • vue开发公众号时接入微信jssdk,jssdk接口项目的坑和封装
  • vue不同需求的登录流程的实践
  • vue骨架屏的实现
  • vue页面切换动画相关的实践
  • vue中css、js代码的提取和去除冗余
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容