精华内容
下载资源
问答
  • vue项目难点及解决方法
    千次阅读
    2021-09-24 11:38:23

    写在前面

    马上到了金三银四的时间,很多公司开启了今年第一轮招聘的热潮,虽说今年是互联网的寒冬,但是只要对技术始终抱有热情以及有过硬的实力,即使是寒冬也不会阻挠你前进的步伐。在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很多时候都会被问到

    在这个项目中你有遇到什么技术难点,你是怎么解决的?

    其实这个问题旨在了解你在遇到问题的时候的解决方法,毕竟现在前端技术领域广,各种框架和组件库层出不穷,而业务需求上有时纷繁复杂,观察一个程序员在面对未知问题时是如何处理的,这个过程相对于只出一些面试题来考面试者更能了解面试者实际解决问题的能力

    而很多人会说我的项目不大,并没有什么难点,或者说并不算难点,只能说是一些坑,只要google一下就能解决,实在不行请教我同事,这些问题并没有困扰我很久。其实我也遇到过相同的情况,和面试官说如何通过搜索引擎解决这些坑的吧不太好,让面试官认为你只是一个API Caller,但是又没有什么值得一谈的项目难点

    我的建议是,如果没有什么可以深聊的技术难点,不妨在日常开发过程中,试着封装几个常用的组件,同时尝试分析项目的性能瓶颈,寻找一些优化的方案,同样也能让面试官对你有一个整体的了解

    在这篇文章中,我会分享在我目前公司的项目里,是如何在满足业务需求的基础上,让整个系统焕然一新的过程

    技术栈是Vue + Element的单页面应用



    起源

    在我刚入职的那会,编码能力不怎么

    更多相关内容
  • webpack项目中自动引入全局scss变量文件  假设我们有一个公共的scss变量文件variables.scss /*存放所有全局变量*/ $card-title:#C7D200; //首页 卡片标题颜色 $bc-color:#182037; $hoverColor: #7abef9; //链接...
  • 今天忙里偷闲,简单总结一下最近vue项目重构的一些技术要点。 vue数据更新, 视图未更新 这个问题我们经常会遇到,一般是vue数据赋值的时候,vue数据变化了,但是视图没有更新。这个不算是项目重构的技术要点,也和...
  • vue项目中遇到的难点面试.pdf
  • Vue项目遇到的一些难点

    千次阅读 2022-05-09 15:56:28
    解决方法:配合qs插件使用 也就是将对象格式化成 Form Data 类似jquery中的serialiZeArray()方法 2、使用vue-router时 URL模式引发的问题? Vue-router 提供一个mode参数 用来控制 URL 格式 默认使用的是 hash...

    1、使用axios.js 处理异步加载时发现请求时传递参数会直接发送js对象到后端 而不是发送Form Data 而且post 请求 会发送两次请求  第一次是 methodoptions 第二次才是post

    解决方法:配合qs插件使用 也就是将对象格式化成 Form Data  类似jquery中的serialiZeArray()方法

    2、使用vue-routerURL模式引发的问题?

    Vue-router 提供一个mode参数 用来控制 URL 格式 默认使用的是 hash格式 而我在项目中使用的是history格式

    对比:使用history 后发现手动切换页面一切正常 但刷新页面是会提示页面不存在(404) 原因就是后端把URL解析了,使用hash模式 URL中会有一个#号分割 后端默认不会解析#后面的参数

    3、Vue 项目中 用v-for循环本地图片 图片不显示

    解决方法:使用require动态引入图片  或者将图片放static文件夹里

    <img v-bind:src="require(item.imgurl)">

    4、elementui中 防止错误提示框多个弹出 可以直接调用(Message as  any).colseAll() ; 也可从新 Message()方法

    展开全文
  • 一、先总结出如下几点vue项目开发中常见的问题及解决办法。列表进入详情页的传参问题。本地开发环境请求服务器接口跨域的问题API接口的统一管理UI库的按需加载定时器问题rem文件的导入问题Vue-Awesome-Swiper基本能...

    一、先总结出如下几点vue项目开发中常见的问题及解决办法。

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

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

    API接口的统一管理

    UI库的按需加载

    定时器问题

    rem文件的导入问题

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

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

    fastClick的300ms延迟解决方案

    组件中写选项的顺序

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

    开启gzip压缩代码

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

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

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

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

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

    前往detail页面

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

    补充一下其他两种传参:

    如果传参通过前往Detail页面的话,这种传参方式,params中的参数不会出现在url中,所以一旦刷新页面,id就不存在了,很懊恼有木有!!!刚开始我就在这里纠结了好一阵~~实际是因为没有发现更好的办法,被自己蠢到了!!!

    还有一种传参的方式:动态路由传参。动态路由通过在路由文件中定义路由:{ path: '/detail/:id', name: 'Detail', component: Detail },就是这里/detail/:id,然后在页面进行传参前往d页面,在详情页可以通过this.$route.params.id获取传来的参数,url会显示为http://localhost:8080/#/detail/123,且刷新页面参数也会存在。对于传单个参数,这种方式实现了我们的需求,但是没法传多个参数。除非定义路由的时候,定义为/detail/:id/:param1/param2/param3,这样限制死了,必须要这样传参数。且个人角度看来,这种/detail/1/123/1d的url不如detail?id=1&user=123&identity=1来的优雅。

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

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

    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

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

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

    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文件中中添加插件配置 :

    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);

    最后在在页面中使用:

    按钮

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

    (5) 定时器问题:

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

    解决思路很简单:

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

    data() {

    return {

    timer: null // 定时器名称

    }

    },

    然后这样使用定时器this.timer = setInterval(……………………)

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

    beforeDestroy() {

    clearInterval(this.timer);

    this.timer = null;

    }

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

    (7) 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是回调

    // 更多参数用法,请参考文档

    1

    2

    3

    4

    5

    6

    7

    // 参数要写在data中

    data() {

    return {

    // swiper轮播的参数

    swiperOption: {

    // 滚动条

    scrollbar: {

    el: '.swiper-scrollbar',

    },

    // 上一张,下一张

    navigation: {

    nextEl: '.swiper-button-next',

    prevEl: '.swiper-button-prev',

    },

    // 其他参数…………

    }

    }

    },

    附上文档:npm文档,swiper3.0/4.0文档,更多用法,请参考文档说明。

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

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

    (9)fastClick的300ms延迟解决方案

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

    安装:

    cnpm install fastclick -S

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

    import FastClick from 'fastclick'; // 引入插件

    FastClick.attach(document.body); // 使用 fastclick

    (10)组件中写选项的顺序

    export default {

    name: '',

    mixins: [],

    components: {},

    props: {},

    data() {},

    computed: {},

    watch: {},

    created() {},

    mounted() {},

    destroyed() {},

    methods: {}

    };

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

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

    npm run build --report

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

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

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

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

    路由文件中,非懒加载写法:

    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)

    }

    ]

    })

    (12)开启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,但是还需要后台服务器的配置。配置是比较简单的,配置几行代码就可以了,一般这个操作可以叫运维小哥哥小姐姐去搞一下,没有运维的让后台去帮忙配置。

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

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

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

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

    大家都知道当

    编译前:

    .example {

    color: red;

    }

    编译后:

    .example[data-v-f3f3eg9] {

    color: red;

    }

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

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

    .parent >>> .child { /* ... */ }

    上述代码将会编译成:

    .parent[data-v-f3f3eg9] .child {

    /* ... */

    }

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

    展开全文
  • Vue项目开发中,遇到的各种痛点问题和解决方案

    千次阅读 多人点赞 2021-02-20 00:36:06
    Vue项目开发中,遇到的各种痛点问题和解决方案

    💌 作者简介

    1. 📖 个人介绍:小伙伴们,大家好!我是水香木鱼,【`前端领域创作者`】😜

    2. 📜 CSDN主页:水香木鱼

    3. 📑 个人博客:陈春波

    4. 🎨 系列专栏:后台管理系统

    5. 🌹 一键四连:关注💋+点赞👍+收藏⭐+留言📝

    6. 📢 人生箴言:即使没有万全准备,也要勇敢迈出第一步。


    目录

    💌 作者简介

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

    说下两者的区别:

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

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

    (3)query传参的url展现方式:/detail?id=1&user=123&identity=1&更多参数

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

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

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

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

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

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

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

    四.UI库的按需加载:

    这里以vant的按需加载为例,演示vue中ui库怎样进行按需加载:

    (1)安装: cnpm i vant -S

    (2)安装babel-plugin-import插件使其按需加载:  cnpm i babel-plugin-import -D

    (3)在 .babelrc文件中中添加插件配置 :

    (4)在main.js中按需加载你需要的插件:

    (5)使用组件:

    (6)最后在在页面中使用:

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

    (1)首先我们vue文件的样式都是写在 标签中的,加scoped是为了使得样式只在当前页面有效。

    (2)好了,下面说下优雅的解决方式:

    (3)编译后的结果就是:

    六.定时器问题:

    解决方法1:

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

    ②然后这样使用定时器:

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

    解决方案2:

    以下是完整代码:

    七.rem文件的导入问题:

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

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

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

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

    (1)安装 fastClick:

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

    十一.组件中写选项的顺序

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

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

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

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

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

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

    接口 (组件的接口)

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

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

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

    十二.路由懒加载(也叫延迟加载)

    (1)路由文件中,非懒加载写法:

    (2)路由懒加载写法:

    十三.开启gzip压缩代码

    (1)解决这个问题非常有效的手段之一就是前后端开启gizp(其他还有缓存、路由懒加载等等)。

    (2)gizp其实就是帮我们减少文件体积,能压缩到30%左右,即100k的文件gizp后大约只有30k。

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

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

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

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

    (1)编译前:

    (2)编译后:

    (3)上述代码将会编译成:

    十六.Hiper:一款令人愉悦的性能分析工具

    (1)在我们的编辑器终端中全局安装:

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

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

    (1)首先是第一种:导航完成之后获取

    (2)第二种方式:导航完成之前获取

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

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

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

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

    (4)下面演示写法2:

    (5)最后,实现双向数据绑定的方式其实还有.sync

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

    十九.路由拆分管理

    (1)例如这里储存了一个vote.js投票模块的路由文件和一个公共模块的路由文件。下面直接上index.js吧,而后在简单介绍:

    (2)首先引入vue和router最后导出,这就不多说了,基本的操作。

    二十.mixins混入简化常见操作

    (1)新建一个mixins.js,把我们需要混入的内容都写在里面,例如这里混入了filters,把常用的几个操作写在了里面,大家可以自行扩展。

    (2)这样的话,在我们需要的页面import这个js,然后声明一下混入就好,而后就可以像正常的方式去使用就好了。

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

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

    📖 博主致谢


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

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

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

    • UI库的按需加载

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

    • 定时器问题

    • rem文件的导入问题

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

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

    • fastClick 的300ms延迟解决方案

    • 组件中写选项的顺序

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

    • 开启gzip压缩代码

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

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

    • hiper打开速度测试

    • vue数据的两种获取方式+骨架屏

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

    • 路由的拆分管理

    • mixins混入简化常见操作

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

    • 后面会继续更新:

    • axios封装和api接口的统一管理(已更新,在上面的链接)

    • hiper打开速度测试

    • vue数据的两种获取方式+骨架屏

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

    • 路由的拆分管理

    • mixins混入简化常见操作

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

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

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

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

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

    图片

    上面的这个报错大家都不会陌生,报错是说没有访问权限(跨域问题)

    本地开发项目请求服务器接口的时候,因为客户端的同源策略,导致了跨域的问题。

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

    图片

    图片

    图片

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

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

    图片

    (3)注意:配置好后一定要关闭原来的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的请求地址。这样没什么大惊小怪的,代理而已:

    图片

    (4)好了,最后附上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库怎样进行按需加载:

    (1)安装: cnpm i vant -S

    (2)安装babel-plugin-import插件使其按需加载:  cnpm i babel-plugin-import -D

    (3)在 .babelrc文件中中添加插件配置 :

    libraryDirectory { 
        
        "plugins": [ 
            // 这里是原来的代码部分
            // …………
    
            // 这里是要我们配置的代码
            ["import", 
                { 
                    "libraryName": "vant", 
                    "libraryDirectory": "es", 
                    "style": true 
                }
            ] 
        ] 
    }
    

    (4)在main.js中按需加载你需要的插件:

    // 按需引入vant组件
    import {   
        DatetimePicker,   
        Button,   
        List 
    } from 'vant';
    

    (5)使用组件:

    // 使用vant组件
    Vue.use(DatetimePicker)  
        .use(Button)  
        .use(List);
    

    (6)最后在在页面中使用:

    <van-button type="primary">按钮</van-button>
    

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

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

    (1)首先我们vue文件的样式都是写在<style lang="less" scoped></style>标签中的,加scoped是为了使得样式只在当前页面有效。

    那么问题来了,看图:

    图片

    我们正常写的所有样式,都会被加上[data-v-23d425f8]这个属性(如1所示),但是第三方组件内部的标签并没有编译为附带[data-v-23d425f8]这个属性。

    所以,我们想修改组件的样式,就没辙了。

    怎么办呢,有些小伙伴给第三方组件写个class,然后在一个公共的css文件中或者在当前页面再写一个没有socped属性的style标签,然后直接在里面修改第三方组件的样式。

    这样不失为一个方法,但是存在全局污染和命名冲突的问题。约定特定的命名方式,可以避免命名冲突。但是还是不够优雅。

    我们怎么能允许这种情况出现呢?

    (2)好了,下面说下优雅的解决方式:

    通过深度选择器解决。

    例如修改上图中组件里的van-ellipsis类的样式,可以这样做:

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

    (3)编译后的结果就是:

    图片

    ①这样就不会给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,类似于其他需要在当前页面使用,离开需要销毁的组件(例如一些第三方库的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。下面说下怎么使用:

    1. 安装 cnpm install vue-awesome-swiper --save
    2. 在组件中使用的方法,全局使用意义不大:
    // 引入组件
    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需要配置哪些功能需求,自己根据文档进行增加或者删减。

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

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

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

    图片

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

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

    (1)安装 fastClick:

    cnpm install fastclick -S
    

    (2)在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

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

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

    - `methods`
    

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

    - `template`/`render`
    - `renderError`
    

    十二.路由懒加载(也叫延迟加载)

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

    (1)路由文件中,非懒加载写法:

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

    (2)路由懒加载写法:

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

    十三.开启gzip压缩代码

    spa这种单页应用,首屏由于一次性加载所有资源,所有首屏加载速度很慢。

    (1)解决这个问题非常有效的手段之一就是前后端开启gizp(其他还有缓存、路由懒加载等等)。

    (2)gizp其实就是帮我们减少文件体积,能压缩到30%左右,即100k的文件gizp后大约只有30k。

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

    // 2.0的版本设置不一样,本文写作时为v1版本。v2需配合vue-cli3cnpm i compression-webpack-plugin@1.1.11 
    

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

    build: {
        // 其他代码
        …………
        productionGzip: true, // false不开启gizp,true开启
        // 其他代码
    }
    

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

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

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

    这样一个场景:有三个页面,首页/或者搜索页,商品分类页面,商品详情页。

    我们希望从首页进入分类页面时,分类页面要刷新数据,从分类进入详情页再返回到分类页面时,我们不希望刷新,我们希望此时的分类页面能够缓存已加载的数据和自动保存用户上次浏览的位置。

    之前在百度搜索的基本都是keep-alive处理的,但是总有那么一些不完善,所以自己在总结了之后进行了如下的实践。

    解决这种场景需求我们可以通过vue提供的keepAlive属性。

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

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

    (1)编译前:

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

    (2)编译后:

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

    ①看完你肯定就会明白了,其实是在你写的组件的样式,添加了一个属性而已,这样就实现了所谓的私有作用域。

    ②但是也会有弊端,考虑到浏览器渲染各种 CSS 选择器的方式,当 p { color: red } 设置了作用域时 (即与特性选择器组合使用时) 会慢很多倍。

    ③如果你使用 class 或者 id 取而代之,比如 .example { color: red },性能影响就会消除。所以,在你的样式里,进来避免直接使用标签,取而代之的你可以给标签起个class名。

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

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

    (3)上述代码将会编译成:

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

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

    十六.Hiper:一款令人愉悦的性能分析工具

    图片

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

    (1)在我们的编辑器终端中全局安装:

    cnpm install hiper -g
    

    (2)使用:终端输入命令: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组件。
    5. 我们需要在当前页面进入之前,即在上一个页面的时候有一个加载的提示,比如页面顶部的进度条。
    6. 这样用户体验就比较友好了,而不至于因为请求的s速度慢一些导致半天没反应而用户又不知道的结果。
    7. 全局的页面顶部进度条,可以在main.js中通过router.beforeEach(to, from, next) {}来设置,当页面路由变化时,显示页面顶部的进度条,进入新路由后隐藏掉进度条。

    图片

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

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

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

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

    <input type="text" v-model="msg">
    
    data () {            
        return {                
            msg: ''            
        }        
    }
    

    (2)其实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语法糖本质的用法,那么我们就可以这样实现父子组件的双向数据绑定:

    (3)以上原理实现方法,写法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选项,

    (4)下面演示写法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绑定值的方法,在我们开发中其实是很常用的,特别是你要封装公共组件的时候。

    (5)最后,实现双向数据绑定的方式其实还有.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>
    

    (6)掌握了组件的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文件夹,里面存放各个模块的路由文件。

    (1)例如这里储存了一个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
    

    (2)首先引入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
    

    (1)新建一个mixins.js,把我们需要混入的内容都写在里面,例如这里混入了filters,把常用的几个操作写在了里面,大家可以自行扩展。

    (2)这样的话,在我们需要的页面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-cli初始化的项目,会默认安装webpack-bundle-analyzer插件,该插件可以帮助我们查看项目的体积结构对比和项目中用到的所有依赖。也可以直观看到各个模块体积在整个项目中的占比。很霸道有木有~~

    图片

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

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

    📖 博主致谢

    感谢大家阅读到结尾,本次的文章就分享到这里,总结了【Vue项目开发中,遇到的各种痛点问题和解决方案】,希望可以帮到大家,谢谢。

    如果您觉得这篇文章有帮助到您的的话不妨【`关注+点赞+收藏+评论+转发` 】支持一下哟~~😛您的支持就是我更新的最大动力。👇👇👇👇👇👇

    🛩往期精彩:

    vue项目build打包后提交到git上没有dist目录的解决方案

    vue全局自定义字体,提高项目字体美化

    vue实现隐藏浏览器右侧滚动条功能

    vue如何通过url携带token,并且打开新页面

    一文图解前端WebSocket 实时通信​​​​​​​

    展开全文
  • 近期一直在做一个xxx中心的项目,先来吐槽下内心的想法, 要开发的项目需求很不明确,需求两周两周的更改,感觉每天并没有特别多实际的产出,总是感觉有点儿浪费时间。 虽然这样,但是作为开发,我们只能服从上级...
  • 这里给大家分享一下vue中的一些技巧,希望对大家有用处。(话不多说上代码) 1,vue路由拦截浏览器后退实现表单保存类似需求... // 我所采用的方法,个人感觉比较简洁一些,少了一步引入赋值。 const router = new
  • vue项目中遇到的难点

    千次阅读 2021-09-03 17:49:21
    vue组件中click事件失效 解决:使用了bette-scroll插件做滚动。加.native也不好使,发现better-scroll的配置中没有设置 click:true,设置过之后click事件成功。 不同商品页,id变化,商品不变 监听$route...
  • 1 . 在使用 组件的时候遇到如下报错: You cannot set a form field before rendering a field associated with the value. You can use getFieldDecorator(id, ... 解决办法: 调整传入setFieldsValue方法的值。
  • vue商城项目难点,需要解决的bug 1 Bug swiper插件中,图片数据没有实时响应问题, 问题描述: vue是实时响应的 图片请求完成后 swiper 依旧在滑动,里面却没有图片 Ans:这是因为浏览器一开始标识pc,然后转换为...
  • 一.修改el-input的border样式 使用.el-input__inner进行样式css修改,如若无效则尝试/deep/.el-input__inner进行深度修改 二.实现点击button进行页面跳转 为button添加点击事件,@click="$router.push('path跳转路径'...
  • 注意点:建议不要使用ESLint 后面选No 就ok 大神跳过此处,否则在项目中只要编码格式有问题就会一直拨错!二.编辑器选择1、推荐使用Visual Studio Code工具打开testproject文件夹即可进行开发。(1)(打开终端快捷键 ...
  • 解决方法:我们在环境变量中添加我们安装的npm命令行执行文件路径。 设置成功: 问题二、vue-cli脚手架版本3.0以上和以下的切换(演示为以3.0以上降级到3.0以下) 注意:@vue/cli是3.0之后的,vue-
  • 主要介绍了vue+springboot前后端分离工程跨域问题解决方案解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Vue2supermarket项目难点解读 GitHub地址:git@github.com:2019083310/supermall.git 一.首页 1.轮播图的封装如何实现 2.数据请求结构以及实现方式 Home中data的结构: goods:{ 'pop':{page:0,list:[]}, 'new':{...
  • 因为工作要求需要用到ant design pro for vue,又因为是第一次接触,所以遇到了很多问题,所以打算吧遇到的问题和解决方法放在这,仅供自己之后回头复习和当做备忘录。 1.table控件内容与列名无法对齐 查了好久,...
  • Vue 项目中各种痛点问题及解决方案

    万次阅读 多人点赞 2020-09-29 08:08:00
    点击上方“程序员黑叔”,选择“置顶或者星标”你的关注意义重大!作者:愣锤原文:https://juejin.im/post/6844903632815521799最近要求使用vue进行前...
  • 记录前端项目开发过程中遇到的比较不错的问题
  • vue项目中遇到的问题汇总

    千次阅读 2020-12-21 12:00:20
    前:项目用到的技术栈为webpack+vue2.x+pug+stylus+elementUI1、IE9中, 请求服务器数据并用v-for渲染option标签出现只显示第一个字的问题解决方法:/*** 强制重绘页面的select 输入框,解决IE9只显示单个字符串问题...
  • 在本篇文章里小编给大家分享的是关于vue开发中遇到的问题总结,有兴趣的朋友们可以学习参考下。
  • 第一次 1.v-model => v-model:value 2.生命周期:beforeDestroy => beforeUnmount destroyed => unmounted ...Vue.defineAsyncComponent(() => import('')) 5.插槽solt-sc
  • 作者:yeyan1996https://juejin.im/post/5c76843af265da2ddd4a6dd0写在前面马上到了金三银四的时间,...在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很...
  • 作者:愣锤原文:https://juejin.im/post/6844903632815521799最近要求使用vue进行前后端分离开发微信公众号,不断摸索踩坑之后,总结出如下几点vue...
  • vue电商的难点和性能优化

    千次阅读 2021-03-09 15:55:24
    vue项目难点难点权限管理显示菜单的显示和隐藏跨域重写路由器对象上的push和replace方法性能优化缓存组件keep-alivevm.nextTick()事件委托(事件委派)节流和防抖 难点 权限管理 解析:在电商项目,只有在注册登录...
  • 尚品汇项目难点及解决

    千次阅读 2022-03-18 20:05:00
    解决方法:修改vue-router的配置文件,也就是router/index.js文件 //重写VueRouter.prototype身上的push方法VueRouter.prototype.push = function(location, resolve, reject) { //第一个形参:路由跳转的配置...
  • Case 1 vue项目在IE中自动读取缓存中的数据,不重新发请求 解决方案: // 在每个请求上增加时间戳 config.url = `${config.url}?_t=${+new Date()}`

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,293
精华内容 1,717
关键字:

vue项目难点及解决方法