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

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

  • 列表进入详情页的传参问题。
  • 本地开发环境请求服务器接口跨域的问题
  • axios封装和api接口的统一管理
  • UI库的按需加载
  • 如何优雅的只在当前页面中覆盖ui库中组件的样式
  • 定时器问题
  • rem文件的导入问题
  • Vue-Awesome-Swiper基本能解决你全部的轮播需求
  • 打包后生成很大的.map文件的问题
  • fastClick的300ms延迟解决方案
  • 组件中写选项的顺序
  • 路由懒加载(也叫延迟加载)
  • 开启gzip压缩代码
  • 详情页返回列表页缓存数据和浏览位置、其余页面进入列表页刷洗数据的实践
  • css的scoped私有做用域和深度选择器
  • hiper打开速度测试
  • vue数据的两种获取方式+骨架屏
  • 自定义组件(父子组件)的双向数据绑定
  • 路由的拆分管理
  • mixins混入简化常见操做
  • 打包以后文件、图片、背景图资源不存在或者路径错误的问题
  • vue插件的开发、发布到github、设置展现地址、发布npm包

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

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

例如商品列表页面前往商品详情页面,须要传一个商品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.idcss

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

说下二者的区别:前端

    1.query经过path切换路由,params经过name切换路由vue

// 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来接收参数。java

// 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&更多参数webpack

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

    4.params动态路由传参,必定要在路由中定义参数,而后在路由跳转的时候必需要加上参数,不然就是空白页面:git

{      
    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;
}复制代码


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


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

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




能够看到,此时咱们点击获取数据,浏览器提示咱们跨域了。因此咱们访问不到数据。

那么接下来咱们演示设置容许跨域后的数据获取状况:


注意:配置好后必定要关闭原来的server,从新npm run dev启动项目。否则无效。



咱们在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封装和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>复制代码

ps:出来vant库外,像antiUi、elementUi等,不少ui库都支持按需加载,能够去看文档,上面都会有提到。基本都是经过安装babel-plugin-import插件来支持按需加载的,使用方式与vant的一模一样,能够去用一下。


如何优雅的只在当前页面中覆盖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等,能够用>>>符号。

更多的关于深度选择器的内容,在文章后面有介绍。


定时器问题:

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




解决方法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文件


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
  11. 渲染 (组件输出的声明式描述)

    • template/render
    • renderError


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

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


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初始化的项目中,是默认有此配置的,只须要开启便可。可是须要先安装插件:

// 2.0的版本设置不同,本文写做时为v1版本。v2需配合vue-cli3cnpm i compression-webpack-plugin@1.1.11 复制代码

而后在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 { /* ... */ }


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

后面会继续更新:


  • axios封装和api接口的统一管理(已更新,在上面的连接)
  • hiper打开速度测试
  • vue数据的两种获取方式+骨架屏
  • 自定义组件(父子组件)的双向数据绑定
  • 路由的拆分管理
  • mixins混入简化常见操做
  • 打包以后文件、图片、背景图资源不存在或者路径错误的问题
  • vue插件的开发、发布到github、设置展现地址、发布npm包

------------华丽丽的分割线-------------------------华丽丽的分割线-------------------------华丽丽的分割线-------------------------华丽丽的分割线-------------------------华丽丽的分割线-------------------------华丽丽的分割线-------------------------华丽丽的分割线-------------

Hiper:一款使人愉悦的性能分析工具


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

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

cnpm install hiper -g复制代码

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

# 当咱们省略协议头时,默认会在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"复制代码

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

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

若是有一个工具,一次性地请求N次网页,而后把各个性能指标取出来求平均值,咱们就能很是准确地知道这个优化是「正优化」仍是「负优化」。

hiper就是解决这个痛点的。


vue获取数据的两种方式的实践+简单骨架屏实现

在vue中获取数据有两种方式,引入尤大大的话就是:

  • 导航完成以后获取:先完成导航,而后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。

  • 导航完成以前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪一种。那么咱们来实践一下这两种获取数据的方式,以及用户体验优化的一点思考。

1、首先是第一种:导航完成以后获取,这种方式是咱们大部分都在使用的,(由于可能一开始咱们只知道这种方式^V^)。使用这种方式时,咱们会立刻导航和渲染组件,而后在组件的 created 钩子中获取数据。这让咱们有机会在数据获取期间展现一个 loading 状态,还能够在不一样视图间展现不一样的 loading 状态。获取数据你们都会,这里说下用户体验的一些东西:

  • 在数据获取到以前,页面组件已经加载,可是数据没有拿到并渲染,因此在此过程当中,咱们不能加载页面内展现数据的那块组件,而是要有一个loading的加载中的组件或者骨架屏。
  • 当页面数据获取失败,能够理解为请求超时的时候,咱们要展现的是断网的组件。
  • 若是是列表页,还要考虑到空数据的状况,即为空提示的组件。

那么,咱们的页面是要有这基本的三个部分的,放代码:

<template>
    <div class="list">
        <!--加载中或者骨架屏-->
        <div v-if="loading">
       
        </div>

        <!--请求失败,即断网的提示组件-->
        <div v-if="error">
      
        </div>

        <!--页面内容-->
        <div v-if="requestFinished" class="content">
            <!--页面内容-->
            <div v-if="!isEmpty">
                <!--例若有个列表,固然确定还会有其余内容-->
                <ul></ul>
            </div>

            <!--为空提示组件-->
            <div v-else>空空如也</div>
        </div>
    </div>
</template>复制代码

这种获取数据的状况下,咱们进来默认的是展现loading或者骨架屏的内容,而后若是获取数据失败(即请求超时或者断网),则加载error的那个组件,隐藏其余组件。若是数据请求成功,则加载内容的组件,隐藏其余组件。若是是列表页,可能在内容组件中还会有列表和为空提示两块内容,因此这时候也还要根据获取的数据来判断是加载内容仍是加载为空提示。

2、第二种方式:导航完成以前获取

这种方式是在页面的beforeRouteEnter钩子中请求数据,只有在数据获取成功以后才会跳转导航页面。

beforeRouteEnter (to, from, next) {        
    api.article.articleDetail(to.query.id).then(res=> {            
        next(vm => {                
            vm.info = res.data;                
            vm.loadFinish = true            
        })        
    })    
},复制代码

1. 你们都知道钩子中beforeRouteEnter钩子中this还不能使用,因此要想进行赋值操做或者调用方法,咱们只能经过在next()方法的回调函数中处理,这个回调函数的第一个参数就表明了this,他会在组件初始化成功后进行操做。

2. 我想,不少时候咱们的api或者axios方法都是挂载到vue的原型上的,因为这里使用不了this,因此只能在页面组件内引入api或者咱们的axios。

3. 赋值操做也能够写在method方法中,可是调用这个赋值方法仍是vm.yourFunction()的方式。

4. 为空提示、断网处理等都和第一种方式同样,可是,因为是先获取到数据以后再跳转加载组件的,因此咱们不须要在预期的页面内展现骨架屏或者loading组件。能够,咱们须要在当前页面进入以前,即在上一个页面的时候有一个加载的提示,好比页面顶部的进度条。这样用户体验就比较友好了,而不至于由于请求的s速度慢一些致使半天没反应而用户又不知道的结果。全局的页面顶部进度条,能够在main.js中经过router.beforeEach(to, from, next) {}来设置,当页面路由变化时,显示页面顶部的进度条,进入新路由后隐藏掉进度条。


关于怎么添加进度条,由于在另外一篇文章已经写了,这里直接送上连接吧,就再也不重复浪费地方了。操做也比较简单,可自行查阅。

其实说到了这里,那么骨架屏的事情也就顺带已经解决了,通常页面骨架屏也就是一张页面骨架的图片,可是要注意这张图片要尽量的小。


自定义组件(父子组件)的双向数据绑定

说到父子组件的通讯,你们必定都不陌生了:父组件经过props向子组件传值,子组件经过emit触发父组件自定义事件。可是这里要说的是父子组件使用v-model实现的通讯。相信你们在使用别人的组件库的时候,常常是经过v-model来控制一个组件显示隐藏的效果等,例如弹窗。下面就一步一步解开v-model的神秘面纱。抓~~稳~~喽~~,老司机弯道要踩油门了~~~

提到v-model首先想到的就是咱们对于表单用户数据的双向数据绑定,操做起来很简洁很粗暴,例如:

<input type="text" v-model="msg">

data () {            
    return {                
        msg: ''            
    }        
}复制代码

其实v-model是个语法糖,上面这一段代码和下面这一段代码是同样的效果:

<input type="text" :value="msg" @input="msg = $event.target.value">
data () {
    return {
        msg: '' 
    }        
},复制代码

由此能够看出,v-model="msg"实则是 :value="msg" @input="msg = $event.target.value"的语法糖。这里其实就是监听了表单的input事件,而后修改:value对应的值。除了在输入表单上面可使用v-model外,在组件上也是可使用的,这点官网有提到,可是介绍的不是很详细,致使刚接触的小伙伴会有一种云里雾里不知所云的感受。既然了解了v-model语法糖本质的用法,那么咱们就能够这样实现父子组件的双向数据绑定:

以上原理实现方法,写法1:

父组件用法:

<empty v-model="msg"></empty>复制代码

子组件写法:

// 点击该按钮触发父子组件的数据同步
<div class="share-btn" @click="confirm">肯定</div>

// 接收父组件传递的value值
// 注意,这种实现方法,这里只能使用value属性名
props: {            
    value: {                
        type: Boolean,                
        default: false            
    }        
},
methods: {            
    confirm () {                
        // 双向数据绑定父组件:value对应的值 
        // 经过$emit触发父组件input事件,第二个参数为传递给父组件的值,这里传递了一个false值 
        // 能够理解为最上面展现的@input="msg = $event.target.value"这个事件
        // 即触发父组件的input事件,并将传递的值‘false’赋值给msg             
        this.$emit('input', false)            
    }        
}复制代码

这种方式实现了父子组件见v-model双向数据绑定的操做,例如你能够试一下实现一个全局弹窗组件的操做,经过v-model控制弹窗的显示隐藏,由于你要在页面内进行某些操做将他显示出来,控制其隐藏的代码是写在组件里面的,当组件隐藏了对应的也要父组件对应的值改变。

以上这种方式实现的父子组件的v-model通讯,虽可行,但限制了咱们必须popos接收的属性名为value和emit触发的必须为input,这样就容易有冲突,特别是在表单里面。因此,为了更优雅的使用v-model通讯而解决冲突的问题,咱们能够经过在子组件中使用model选项,下面演示写法2:

父组件写法:

<empty v-model="msg"></empty>复制代码

子组件写法:

<div class="share-btn" @click="confirm">肯定</div>

// model选项用来避免冲突
// prop属性用来指定props属性中的哪一个值用来接收父组件v-model传递的值
// 例如这里用props中的show来接收父组件传递的v-model值
// event:为了方便理解,能够简单理解为父组件@input的别名,从而避免冲突
// event的值对应了你emit时要提交的事件名,你能够叫aa,也能够叫bb,可是要命名要有意义哦!!!
model: {            
    prop: 'show',            
    event: 'changed'        
},
props: {
    // 因为model选项中的prop属性指定了,因此show接收的是父组件v-model传递的值            
    show: {                
        type: Boolean,                
        default: false            
    }        
},        
methods: {            
    confirm () {                
        // 双向数据绑定父组件传递的值
        // 第一个参数,对应model选项的event的值,你能够叫aa,bbb,ccc,起名随你 
        this.$emit('changed', false)            
    }        
}复制代码

这种实现父子组件见v-model绑定值的方法,在咱们开发中实际上是很经常使用的,特别是你要封装公共组件的时候。

最后,实现双向数据绑定的方式其实还有.sync,这个属性一开始是有的,后来因为被认为或破坏单向数据流被删除了,但最后证实他仍是有存在乎义的,因此在2.3版本又加回来了。

例如:父组件:

<empty :oneprop.sync="msg"></empty>

data () {
    return {
        msg: ''
    }
}复制代码

子组件:

<div class="share-btn" @click="changeMsg">改变msg值</div>

props: {            
    oneprop: {                
        type: String,                
        default: 'hello world'
    }        
},        
methods: {            
    changeMsg () {                
        // 双向数据流
        this.$emit('update:msg', 'helow world')           
    }        
}        复制代码

这样,即可以在子组件更新父组件的数据。因为v-model只使用一次,因此当须要双向绑定的值有多个的时候,.sync仍是有必定的使用场景的。.sync是下面这种写法的语法糖,旨在简化咱们的操做:

<empty
    :msg="message"
    @update:msg="message = $event"
></empty>复制代码

掌握了组件的v-model写法,在封装一些公共组件的时候就又轻松一些了吧。

这里再提一下:

  • vm.$emit(event ,[...args])这个api,其主要做用就是用来触发当前实例上的事件。附加参数都会传给监听器回调。子组件也属于当前实例。第一个参数:要触发的事件名称。后续的参数可选:即做为参数传递给要触发的事件。文档
  • 监听当前实例上的自定义事件,事件能够有$emit触发,也能经过hook监听到钩子函数,

vm.$on( event, callback ):一直监听;文档

vm.$once( event, callback ):监听一次;文档

vm.$off( [event, callback] ):移除监听;文档

监听$emit触发的自定义事件,上面已经有过用法了,监听钩子函数,在上面的定时器那块也有演示到。监听钩子函数的场景使用的很少,可是仍是要知道的。

  • vm.$attrs:能够获取到父组件传递的除class和style外的全部自定义属性。
  • vm.$listeners:能够获取到父组件传递的全部自定义事件

例如:父组件:

<empty
    :msg="message"
    :title="articleTitle"
    @confirm="func1"
    @cancel="func2"
></empty>复制代码

就能够在子组件中获取父组件传递的属性和事件,而不用在props中定义。子组件简单演示以下:

created() {            
    const msg = this.$attrs.msg; // 获取父组件传递的msg
    this.$listeners.confirm && this.$listeners.confirm(); //若组件传递事件confirm则执行
},复制代码

这在咱们写一些高级组件时候,会有用到的。


路由拆分管理

这里说的路由拆分指的是将路由的文件,按照模块拆分,这样方便路由的管理,更主要的是方便多人开发。具体要不要拆分,那就要视你的项目状况来定了,若是项目较小的话,也就一二十个路由,那么是拆分是很是不必的。但假若你开发一些功能点较多的商城项目,路由能够会有一百甚至几百个,那么此时将路由文件进行拆分是颇有必要的。否则,你看着index.js文件中一大长串串串串串串的路由,也是很糟糕的。


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

import Vue from 'vue'
import Router from 'vue-router'

// 公共页面的路由文件
import PUBLIC from './modules/public' 
// 投票模块的路由文件
import VOTE from './modules/vote' 

Vue.use(Router)

// 定义路由
const router = new Router({  
    mode: 'history',  
    routes: [    
        ...PUBLIC,    
        ...VOTE,  
    ]
})

// 路由变化时
router.beforeEach((to, from, next) => {    
    if (document.title !== to.meta.title) {        
        document.title = to.meta.title;    
    }    
    next()
})

// 导出
export default router复制代码

首先引入vue和router最后导出,这就很少说了,基本的操做。

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

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

而后看下咱们导入的vote.js吧:

/** 
 * 投票模块的router列表  
 */

export default [    
    // 投票模块首页    
    {        
        path: '/vote/index',        
        name: 'VoteIndex',        
        component: resolve => require(['@/view/vote/index'], resolve),        
        meta: {            
            title: '投票'        
        }    
    },    
    // 详情页    {        
    path: '/vote/detail',        
    name: 'VoteDetail',        
    component: resolve => require(['@/view/vote/detail'], resolve),
    meta: {            
        title: '投票详情'        
    }    
}] 复制代码

这里就是将投票模块的路由放在一个数组中导出去。整个路由拆分的操做,不是vue的知识,就是一个es6导入导出和结构的语法。具体要不要拆分,仍是因项目和环境而异吧。

这里的路由用到了懒加载路由的方式,若是不清楚,文字上面有介绍到。

还有这里的meta元字段中,定义了一个title信息,用来存储当前页面的页面标题,即document.title。

mixins混入简化常见操做

咱们在开发中常常会遇到金钱保留两位小数,时间戳转换等操做。每次咱们会写成一个公共函数,而后在页面里面的filters进行过滤。这种方法每次,可是感受每次须要用到,都要写一遍在filters,也是比较烦呢!!!可是,咱们猿类的极致追究就是懒呀,那这怎么能行~~~

兄弟们,抄家伙!上mixins!!!

import { u_fixed } from './tool'

const mixins = {    
    filters: {        
        // 保留两位小数        
        mixin_fixed2 (val) {            
            return u_fixed(val)        
        },
        // 数字转汉字,16000 => 1.60万        
        mixin_num2chinese (val) {            
            return val > 9999 ? u_fixed(val/10000) + '万' : val;        
    }    
}}
export default mixins复制代码

新建一个mixins.js,把咱们须要混入的内容都写在里面,例如这里混入了filters,把经常使用的几个操做写在了里面,你们能够自行扩展。

这样的话,在咱们须要的页面import这个js,而后声明一下混入就好,然后就能够像正常的方式去使用就行了。


例如,我如今能够直接在页面内使用咱们的过滤操做{{1000 | mixin_fixed2}}


打包以后文件、图片、背景图资源不存在或者路径错误的问题

先看下项目的config文件夹下的index.js文件,这个配置选项就好使咱们打包后的资源公共路径,默认的值为‘/’,即根路径,因此打包后的资源路径为根目录下的static。由此问题来了,若是你打包后的资源没有放在服务器的根目录,而是在根目录下的mobile等文件夹的话,那么打包后的路径和你代码中的路径就会有冲突了,致使资源找不到。

因此,为了解决这个问题,你能够在打包的时候把上面这个路径由‘/’的根目录,改成‘./’的相对路径。


这样的的话,打包后的图片啊js等路径就是‘./static/img/asc.jpg’这样的相对路径,这就无论你放在哪里,都不会有错了。可是,凡是都有可是~~~~~这里一切正常,可是背景图的路径仍是不对。由于此时的相对就变成了static/css/文件夹下的static/img/xx.jpg,可是实际上static/css/文件夹下没有static/img/xx.jpg,即static/css/static/img/xx.jpg是不存在的。此时相对于的当前的css文件的路径。因此为了解决这个问题,要把咱们css中的背景图的加个公共路径‘../../’,即让他往上返回两级到和index.html文件同级的位置,那么此时的相对路径static/img/xx.jpg就能找到对应的资源了。那么怎么修改背景图的这个公共路径呢,由于背景图是经过loader解析的,因此天然在loader的配置中修改,打开build文件夹下的utils文件,找到exports.cssLoaders的函数,在函数中找到对应下面这些配置:

找到这个位置,添加一上配置,就是上图红框内的代码,就能够把它的公共路径修改成往上返回两级。这样再打包看下,就ok了!

最后再郑重说一点,若是你的路由模式是history的,那么打包放在服务器,必需要后台服务器的配合,具体的能够看官方文档,这点很重要。否则你会发现白屏啊等各类莫名其妙的问题。牢记!!!


vue插件的开发、发布到github、设置展现地址、发布npm包

对于平时咱们经常使用的一些组件,咱们能够把它封装成插件,而后发布到github上,最后再发布成npm包,这样之后即可以直接从npm安装插件到咱们的项目中,省去了咱们拷贝的过程了,还能给别人分享呢!

因为插件的这一块内容比较多,我暂且放在另一篇文章吧,这里呢就附上连接吧

相关文章
相关标签/搜索