【vue-cli 】超过万字的实战总结

11,018 阅读20分钟

文章会不定时更新!!!!请添加关注哦!!!(最后更新日期:2019,09)

也可能是因为接触vue时间也不长,经常落入不知名的‘坑’中,对于我这个菜鸟来说,每次‘落坑’无疑是一场不小的灾难。前两天有个朋友在问我,在使用vue中有没有遇到一些很难解决的问题,一下我也只能说出一两个,正所谓‘光说不练,假把式’,所以索性就抽时间总结一下我在项目中遇到的vue的问题,也贴出了效果图片,这样看起来也比较清晰。有写的不对的地方,在您时间还允许的情况下,还劳烦大家告诉我哦,我也好尽早修改,以免给看文章的其他同仁带来不必要的麻烦!(当前版本:"vue@2.5.3") -------------------在此谢过!-----------

说到vue的实战,不得不说vue项目创建,那么关于vue cli项目搭建,我在之前的文章中有专门写过,可参考我的第一篇文章: vue cli 框架搭建

  • 接下来给大家安利一个vue的网页运行工具 iVuewRun网页版的vue,可直接运行使用,还可以分享给小伙伴!

每个Vue实例在被创建之前都要经过一系列的初始化过程,这个过程就是vue的生命周期

方法名状态含义
beforeCreatecreating全部实例创建之前,获取不到props 和data中的数据
createdcreating实例创建成功,可以拿到data中的数据
beforeMountmounting此时data里面的数据和模板已生成html
mountedmounting挂载结束,模版中的 data 数据直接显示出来了,此时一般可以做一些ajax操作
beforeUpdateupdating当 data 数据发生变化调用,发生在虚拟 DOM 重新渲染和打补丁之前
updatedupdating数据更改导致的虚拟 DOM 重新渲染和打补丁
beforeDestroydestroying在 vue 实例销毁之前调用,此时实例任然可用,一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件
destroyeddestroying在 vue 实例销毁之后调用,vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁

⚠️注意:

  1. 如果在created阶段的发送ajax请求,此时页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态,请求建议放在mounted阶段,当然mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染 完毕再发生请求,可以用 vm.$nextTick

  2. 子组件完成挂载后,父组件才会挂载,销毁父组件时,先将子组件销毁后才会销毁父组件

参考地址

vue的父组件和子组件生命周期执行顺序:

加载渲染过程

父beforeCreate -》父created -》父beforeMount-》子beforeCreate -》子created-》子beforeMount-》子 mounted-》父mouted

子组件更新过程:

父beforeUpdate -》子 beforeUpdate -》子 update -》父update

父组件更新过程:

父beforeUpdate -》父update

销毁过程:

父beforeDestroy -》子beforeDestroy -》子 destroy  -》父 destroy

分享:自用的vue模版

<template>
   <div>
   </div>
</template>

<script>
   export default {
       props:{},
       data() {
           return {}
       },
       computed:{},
       components: {},
       created(){},
       mounted() {},
       methods: {},
       watch: {},
       destroyed(){},
   }
</script>

<style scoped lang="less">
</style>

一、添加css的多种方式。

vue的每个组件中,可以自定义cssjs,那么如果只希望当前的css只在当前页面生效,可以在style 的标签这样书写,这样当前页面的所有css样式,除当前组件,不会在其他组件生效并且不会影响到其他组件页面渲染。

<style scoped> </style>

如果你引入来sass到vue项目中,那么只需在当前组件添加lang属性即可:

sass $

<style scoped lang="scss"> </style>

亦你的项目引入的不是sass,是less,也是同样更改lang属性即可:

less @:

<style scoped lang="less"> </style>

动态传入style值的几种方式:


1.正常class样式:

<template>
<div>
    <p class="fs20">1.正常class样式: 2018年8月27日</p>
</div>
<template>

<style>
    .fs20 {
        font-size: 20px
    }
</style>

页面展现:


对于多个"../../../"查找引入文件时,可以先跳到最外层,即src层开始查找

关于引入外部的less文件或者img图片:

~@/xxx/...

关于引入外部js、template、component:

@/xxx/...

2.根据data中的className对应的class,可用于动态切换class:

<template>
<div>
    <p :class="className">2.动态切换class的值</p>
</div>
<template>
<script>
    export default {
        data() {
            return {
                className: "classA"
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
    .classA {
        color: yellowgreen
    }
</style>

页面展示:


3.给当前的class添加判断:当isOktrue时添加class,为false时不添加:

<template>
<div>
    <p :class="{colorRed:isOk}">
        3.添加判断:当isOk为true是添加class,为false时不添加
    </p>
    <Checkbox v-model="isOk">Checkbox</Checkbox>
</div>
<template>
<script>
    export default {
        data() {
            return {
                isOk: true,
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
   .colorRed {
        color: red
    }
</style>

页面展示:


4.以数组的方式,一次添加多个class:

<template>
<div>
    <p :class="[classC,classD]">4.以数组的方式,一次添加多个class</p>
</div>
<template>
<script>
    export default {
        data() {
            return {
                classC: "classC",
                classD: "classD"
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
 .classC {
        font-size: 16px;
        font-weight: 600;
    }
    .classD {
        color: blue
    }
</style>

页面展示:


5.使用三元运算符判断切换class样式,当isOktrue时用的是classA,当为false的时候用的是classB:

<template>
<div>
    <p :class="isOk?classA:classB">5.使用三元运算符判断切换class样式</p>
</div>
<template>
<script>
    export default {
        data() {
            return {
                isOk: true,
                classA: "classA",
                classB: "classB"
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
   .classA {
        color: yellowgreen
    }
    .classB {
        color: green
    }
</style>

页面展示:

6.绑定动态的style的样式:

<template>
<div>
    <p :style="{color:color,fontSize:font}">6.绑定style的样式</p>
</div>
<template>
<script>
    export default {
        data() {
            return {
                color: "red",
                font: "18px",
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
 
</style>

页面展示:

7.给style 绑定对象:

<template>
<div>
    <p :style="styleObject">7.给style 绑定对象</p>
</div>
<template>
<script>
    export default {
        data() {
            return {
             styleObject:{
                color:"pink",
                fontWeight:"600"
            }
        },
        components: {},
        mounted() {},
        methods: {},
        watch: {}
    }
</script>

<style>
 
</style>

页面展现:

二、关于循环中的imgsrc赋值的问题

vue中的循环是使用v-for 来实现的,在标签中注入v-for,在接下来使用到的地方就可以直接使用。

<template>

    <div  v-for="item in cityList">

        <div>城市名称:{{item.title}}</div>

        <div>城市ID:{{item.id}}</div>

        <div>城市图片:<img src={{item.img}}></div>  //这行是报错的

    </div>

</template>

<script>
     export default:{
         data(){
            return:{
                cityList:[{
                   title:'北京'id:001,
                        img:'static/logo.png'
             },
             {
                   title:'上海'id:002,
                        img:'static/logo.png'
             }]
         }
        }
    }

</script>

报错如下:(这里意思是在“src”属性插值将导致404请求。使用v-bind:src 简写为:src 代替)

[HMR] bundle has 1 errors
client.js?d90c:161 ./~/_vue-loader@12.2.2@vue-loader/lib/template-compiler?{"id":"data-v-60d18b79","hasScoped":true,"transformToRequire":{"video":"src","source":"src","img":"src","image":"xlink:href"}}!./~/_vue-loader@12.2.2@vue-loader/lib/selector.js?type=template&index=0!./src/components/vuetest.vue
(Emitted value instead of an instance of Error) 
  Error compiling template:
  
  <div>
     <h1>vue测试页面</h1>
     
       <div  v-for="item in cityList">
  
            <div>城市名称:{{item.title}}</div>
  
            <div>城市ID:{{item.id}}</div>
  
            <div>城市图片:<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/11/26/15ff7324b8313b05~tplv-t2oaga2asx-image.image"></div>  
  
     </div>
  </div>
  
  - src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/11/26/15ff7324b8313b05~tplv-t2oaga2asx-image.image": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.

 @ ./src/components/vuetest.vue 10:2-340
 @ ./src/router/index.js
 @ ./src/main.js
 @ multi ./build/dev-client ./src/main.js

因为vue官网在介绍v-bind时,不可以再使用{{}},例如href的两种使用:

<template>
    <div>
        <a :href='msg'>获取动态数据</a> 
        <a href='http://www.baidu.com'>百度</a> 
    </div>    
</template>
<script>
   export default:{
        data(){
          return:{
            msg:'http://www.baidu.com'
          }  
       }

}
</script>

正确代码如下:

<template>

    <div  v-for="item in cityList">

        <div>城市名称:{{item.title}}</div>

        <div>城市ID:{{item.id}}</div>

        <div>城市图片:<img :src='item.img'></div>  

    </div>

</template>

<script>
     export default:{
         data(){
            return:{
                cityList:[{
                   title:'北京'id:001,
                        img:'static/logo.png'
             },
             {
                   title:'上海'id:002,
                        img:'static/logo.png'
             }]
         }
        }
    }
</script>

三、关于v-ifv-show的区别

在vue中有两种隐藏元素的方式,那就是 v-ifv-show,但是两者有什么区别呢?什么时候用v-if,什么时候用v-show呢?

  • 1.先说最大的区别,`v-if` 通过条件判断来渲染条件块,当为假值时,当前条件块的所有`DOM`元素不进行渲染;`v-show`同样也是条件判断,但如果`v-show`的值为假值时,当前条件块虽不会在页面显示,但已经渲染完毕,只是属性设置成了`display:none`.总结就是`v-if` 是通过条件判断来添加和删除`DOM`元素。`v-show`是通过`display:block`和`display:none`来控制元素的显示隐藏。
  • 2.`v-if` 是有惰性的,如果初始条件为假值,则直接什么也不做,只有在条件变为真时才开始局部编译;`v-show`是在任何条件都被编译,然后被缓存,而且`DOM`元素保留,即使为假值时,在后台仍然可以看到`DOM`元素已经被渲染出来。
  • 3.`v-if`适合在条件不太可能变化时使用,v-show适合频繁切换。
  • 4.`v-if`后面可以跟`v-else`,或`v-else-if`,但`v-show`不可以

    v-ifv-show两个值都为true时的渲染结果,都正常渲染 v-ifv-show的值都为假值时:页面没有渲染,v-if未渲染dom元素,v-show渲染成功,但被添加了styledisplay:none

    四、关于在vue中如何操作DOM元素。

    我们都知道vue框架中我们一般操作的都是数据,那么假如我们要操作dom元素使用什么方法呢?下面就来介绍一下!

    假如有以下元素,我们要获取这个h2元素的文本,需要给此元素添加ref属性,并赋予名字。

    <h2 ref='foo'>我是ref的值</h2>
    

    接下来就可以使用这个方法获取到它的文本(注意是this.$refs不是this.$ref):

    console.log(this.$refs.foo.innerHTML')
    

    那么如何改变h2中的文本呢?

    this.$refs.foo.innerHTML='我是新值
    


    这样就可以和以前一样,轻松的操作dom元素了,但是vue还是以操作数据为核心,所以建议尽量少的使用以上方法。

    五、探究router-link中的tag属性。

    vue路由的router-link标签中有一个属性tag,是我今天在查阅资料时发现的,感觉比较有意思,推荐给大家。

    那么我们就给<router-link to='/about'>的标签上添加上tag属性,并赋值:

    <router-link to='/fenceCenter' >中心点</router-link>
    <router-link to='/vuetest'tag='li'>vue测试</router-link>
    

    那么我们看看它和我们正常渲染的有什么不同



  • 上面的是我们正常渲染的 vue会自动解析成``标签的形式;
  • 下面是我们加了`tag`属性的渲染成了赋值的`
  • `标签。

    是不是很神奇呢? tag除了可以赋值li,还可以赋值成你想要的所有的标签哦! p,span,h1,div....都可以哦!快去动手试试吧!!


    六、vue中的指定路由跳转router

    在我们的实际业务中,有时需要在某一组件内,点击一个按钮或者是点击一个链接切换跳转到其他组件,也就是跳转路由,我们可以试试下面的方法:

    //直接跳转
    this.$router.push('/map')
    
    //条件允许时跳转
    if(this.data){
        this.$router.push('/map')
    }
    

    注意:router需要挂在到vue实例上,这样才可以获取到this.$router,而且push后面的括号中的路由地址,也需要在vuerouter中有注册,最后,在('')中填写你需要跳转的路由即可完成跳转。

    七、vue的路由router中的go方法

    上面刚刚有讲到路由router,接下来再讲一个和router相关的方法go,这个方法是用作前进后退导航来使用的,有时实际业务需要我们添加一个返回上一页面的功能,那么我们就可以用go来实现。当为‘-1’时就可以后退到上一个路由页面。

    this.$router.go('-1')
    

    八、轻松编写vue组件

    vue是一个单页面应用,那么就会涉及到组件的问题,例如A页面为一个主页面,A1,A2,A3为3个子页面,A1,A2,A3页面的内容分别比较复杂,需要单页面来编辑,这时我们就需要把A1,A2,A3写成3个组件,然后全部加载A的主页面上;又或有这样的情况,当子页面的复用率比较高时,同样可以采取使用组件的方式来实现。总之,你可以把你想实现的写成组件,这样第一方便修改,第二页面干净整洁,第三;让别人看起来一目了然。 下面我们就看看,组件到底是怎么实现吧!!!

    A页面:

    <template>
      <div>
        <h2 style="color:red">我是主页面</h2> 
        <content><content>      //我是添加的组件
      </div>
    </template>
    
    <script>
    import content from './content'    //找到组件的位置,引入
      export default {
        name: "",
        data() {
            return {
            }
        },
       components:{
          content      //将组件写入模板内才可生效,当有多个组件,看用逗号分开
       },
        mounted() {
        },
        methods: {}
      }
    </script>
    
    <style scoped>
    </style>
    

    A页面的子页面(content组件页面): 组件页面就是普通页面,当然组件页面的事件也是会带到主页面的。

    <template>
    
      <div @click="foo()">我是组件页面</div>
      
    </template>
    <script>
      export default {
        name: "",
        data() {
          return {
          }
        },
        mounted() {
        },
        methods: {
            foo(){
                alert("我是好人")
            }
        }
      }
    </script>
    
    <style scoped>
    </style>
    

    这时我们来看一下效果:



    这样你想要的效果就有了哦!

    那么还需要注意一点:如果你的组件名字是驼峰式写法,那么按照以下方式修改:

    <template>
    
      <div>我是主页面<content-f> </content-f>   //这里要这样写</div>
      
    </template>
    <script>
    import contentF from './contentF'         //找到组件的位置 引入
      export default {
        name: "",
        data() {
            return {
            }
        },
       components:{
          contentF     //将组件写入模板内才可生效,当有多个组件,看用逗号分开
       },
        mounted() {
        },
        methods: {}
      }
    </script>
    
    <style scoped>
    </style>
    
    

    九、在vue中观察者watch的用法

    vue的官网是这么介绍的:把它命名为 观察者模式 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的 watcher。这是为什么vue通过watch。 选项提供一个更通用的方法,来响应数据的变化。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。 通俗点讲,就是在vue中有一个watch,它可以监听你的数据是否发生变化,一旦发生变化,可以进行某些操作,那么我们就来试试:

    <template>
       <div>
           <button @click="foo()">点击按钮</button>
           <div>{{value}}</div>
       </div>
    </template>
    <script>
       export default {
           name: "",
           data() {
               return {
                   value:1,
               }
           },
           mounted() {},
           watch:{
               value:function(val,oldval){
                   console.log(val,oldval)
               }
           },
           methods: {
               foo() {
                   this.value=5
               }
           }
       }
    </script>
    
    <style scoped>
    </style>
    

    我们点击一下,看一下结果:当value被改变时,会在后台打印出当前的值和改变前的值:



    但当我们再次重复上一次的动作时,并不会再次打印结果,那是因为value值改变成5后,再次点击,还是同样的值,value的值并没有再次发生变化。


    watch 里面有一个deep的参数,可以监听object中属性的变化。(我们上面使用的是number,而同样使用上面的方法,并不能监听到对象的属性值的变化) 下面我们来使用deep来检测一下对象属性:

     <template>
        <div>
            <button @click="foo()">点击按钮</button>
            <div>{{value}}</div>
        </div>
    </template>
    <script>
        export default {
            name: "",
            data() {
                return {
                    value:{
                        a:1,b:2
                    }
                }
            },
            mounted() {},
            watch:{
                value:{
                    handler(val,oldVal){
                        console.log("lalala",val,oldVal)
                    } ,
                    deep: true,
                    immediate: true
                }
            },
            methods: {
                foo() {
                    this.value.a=5;
                    this.value.b=6;
                }
            }
        }
    </script>
    
    <style scoped>
    </style>
    
    
    

    十、关于assetsstatic的区别,静态资源到底该放哪里?

    在我们搭建好的vue-cli的项目中会自动给我们建两个文件夹来存放我们的数据,图片之类的资产,这两个分别是assetsstatic两个,那么我们怎么来区分这两个文件夹呢?在通过直译我们了解到assets的中文意思为资产,而static的中文意思为静态的,静止的

    原因如下:

    assets目录中的文件会被webpack处理解析为模块依赖,只支持相对路径形式。

    static/ 目录下的文件并不会被webpack处理:它们会直接被复制到最终的打包目录(默认是dist/static)下。

    下面来一段代码,来现身说法:

    
    <template>
     <div class="hello">
       <div v-for="item in cityList" :key="item.id">
           <div >{{item.title}}</div>
           <div>城市图片:<img :src="item.img"></div>
       </div>
     </div>
    </template>
    
    <script>
     export default {
       name: 'HelloWorld',
       data() {
         return {
           cityList: [{
               title: '北京',
               id: "001",
               img:"../assets/logo.png" //这行是不被识别的
             },
             {
               title: '上海',
               id: "002",
               img: "static/logo.png" //这个可以被正确显示
             }
           ]
         }
       }
     }
    </script>
    
    
    
    

    页面展示效果如下:


    那么我们看看在不写在js中的图片引入是否也会出现问题呢?

    <template>
      <div class="hello">
        <img src="../assets/logo.png" alt="logo"> 
        <img src="static/logo.png" alt="logo"> 
      </div>
    </template>
    
    <script>
      export default {
        name: 'HelloWorld',
        data() {
          return {
           
          }
        }
      }
    </script>
    
    
    
    

    来看一下页面的反应:


    效果证实,我们在html中的引入方式是不会受到影响的。 那如果我们必须要在js中使用assets中的图片,该怎么写呢?

    <template>
      <div class="hello">
        <div v-for="item in cityList" :key="item.id">
          <div >{{item.title}}</div>
          <div>城市图片:<img :src="item.img"></div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'HelloWorld',
        data() {
          return {
            cityList: [{
                title: '北京',
                id: "001",
                img:require("../assets/logo.png") //使用require()的方式引入即可
              },
              {
                title: '上海',
                id: "002",
                img: "static/logo.png"
              }
            ]
          }
        }
      }
    </script>
    
    
    

    好啦,接下来就是见证奇迹的时刻了!!


    综上所述,总结为以下两点:

    1.任何放在 static中文件需要以绝对路径的形式引用:/static/imgurl

    2.static放不会变动的文件,assets放可能会变动的文件。

    参考:juejin.im/post/684490… OBKoro1


    十一、关于打包后空白页面的问题

    vue-cli在执行 npm run build 打包后,

    会在dist 目录下生成以下文件 可是当我们执行命令打包后,打开index.html的时候,显示空白页面,而且报错,指明文件找不到,如下:

    接下来让我们打开cofig文件夹的index.js,找到build这个对象,将

    assetsPublicPath: '/'
    

    这个路径地址改成

    assetsPublicPath: './',
    

    重新执行命令,重新打包,再打开index.htnl,页面即可显示:

    assetsPublicPath属性作用是指定编译发布的根目录,'/'指的是项目的根目录 ,'./'指的是当前目录。当我们打包后,index和static是放在dist目录下,而static是和index是平级的,所以不应该在根目录查找文件。

    参考:juejin.im/post/684490… OBKoro1

    十二、关于异步懒加载路由的写法

    在vue-cli自带的脚手架的路由是如下这样写的:

    import Vue from 'vue'
    import Router from 'vue-router'
    import bus from '../bus'
    
    import HelloWorld from '@/components/HelloWorld'
    
    Vue.use(Router)
    
    export default new Route({
      routes: [
        {
        path: '/',
        name: 'HelloWorld',
        component: HelloWorld
       
        },
       
      ]
    });
    
    
    

    使用的是import 的方式引入进来的,那么我们需要使用异步的方式,该怎么写呢?

    import Vue from 'vue'
    import Router from 'vue-router'
    import bus from '../bus'
    
    Vue.use(Router)
    
    var router = new Router({
      routes: [
        {
          path: '/',
          name: 'HelloWorld',
          component: (resolve) => require(['@/components/HelloWorld'], resolve),
        },
        {
          path: '/login',
          name: 'login',
          component: (resolve) => require(['@/login'], resolve),
          
        }
      ]
    });
    bus.router = router
    
    export default router
    
    
    

    第二种方式:

    const Index = () => import( '@/views/index')
    const routers = [
        {
            path: '/',
            name: 'index',
            component: Index
        }
    ]
    
    
    

    十三、写一个自定义指令

    在vue中除了可以写组件,还可以使用自定义指令来实现某些特殊的操作,下面就拿一个类似官方的获取input焦点的“基本款”的栗子🌰来演示。 我有个习惯,就是把相同的东西归类,所以,我先去建了一个文件夹来存放我的指令,如下图:

    详细代码如下: 在这里定义并导出我的自定义指令,指令里包含我想实现的操作。(当前是一个获取焦点的示例

    export const focus = (el) => {
        el.focus()
    }
    
    

    在使用的页面,首先将指令文件引入,然后配置到directive中,在需要使用到标签内添加,注意指令前需添加:”v-“,如下所示: 我的指令name为focus,在标签内写成 “v-focuss”:

    <template>
      <div>
      <input type="text" v-focus>
      </div>
    </template>
    
    <script>
     import { focus } from "../../directives/index"
      export default {
        name: "",
        data() {
            return {}
        },
        directives: {focus}, //自定义指令
        components: {},
        mounted() {},
        methods: {}
      }
    </script>
    
    <style scoped>
    
    </style>
    
    

    今日在掘金闲逛,看到vue的偏门操作,分享给大家: juejin.im/post/684490…

    vue的自定义指令直接操作dom较方便:

    先看directives/index页面 :

    接下来看一下调用页面:

    最后我们来看一下页面:

    再升级!添加参数的自定义指令

    页面的数据也可以写成一个变量,使用变量来控制样式:

    下面是自定义指令的生命周期(我们可以在指令的生命周期中做我们想处理的事件): 1.bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。 2.inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。 3.update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。 4.componentUpdated:被绑定元素所在模板完成一次更新周期时调用。 5.unbind:只调用一次,指令与元素解绑时调用

    好啦!大功告成,小伙伴们,动手试试吧!

    十四、关于打包后大图片请求不到到问题

    今日在项目完成后,进行打包测试,本地开发好好的,但打包后发现登录页面到背景图片拿不到,但是其他logo小图片是可以正常显示,于是就很郁闷,第一反应,想着应该是图片太大了,未被压缩,然后,就找到了如下位置:

    想着把limit的范围放大,可是那如果还有更大的图片呢?这并不是个优雅的办法。于是想到了万能到度娘,就有了如下解决方法: my.oschina.net/u/1778998/b… css引入图片再打包后,style-loader无法设置自己的publicPath,所以只要改变style-loader中的publicPath即可,在build/util.js文件中ExtractTextPlugin的css路径,手动添加publicPath参数。

    添加后,再执行一次build,图片就可以正常显示。

    十五、跳过无需编译及避免出现{{}}

    在我们的项目中应该会有很多情景,标签内的内容是不需要编译的,可是我们的代码并不知道,vue提供了一个可以直接跳过编译的指令,供我们添加在纯静态标签上。

    <div  v-pre> 直接跳过不需要编译的标签,加快编译效率</div>
    

    还有一种情形,在页面有用到{{}}赋值时,有时我们页面阻塞或加载跟不上、频繁刷新时,可能会显示未赋值的{{}},等拿到值后才能更新出来,这样给用户一种很不友好的体验,同样vue也帮我们想到了,vue提供了一个可以等待编译结束,才会显示标签内容到指令.

    
    <div v-cloak> {{message}}直接结束编译,避免出现闪烁花括号,配合css样式书写</div>
    
    //配合css 样式完成
    <style scoped>
        [v-cloak]{display: none;}
    </style>
    

    十六、写一个自定义过滤器

    vue有自己自带的过滤器供我们使用,那我们如何来写一个自定义过滤器呢?下面跟我一起操作吧!

    先去创建一个filter文件夹,来存放你的过滤器,并在文件中写出你想执行的过滤器的方法

    在页面的调用(类似自定义指令)

    OBKoro1 的《你或许不知道的Vue的这些小技巧》很实用,推荐给大家!

    十七、vuex的简单实现

    我们都知道vuex是用来实现vue各个组件数据传输功能的,不区分父子组件,全局即可调用,让我们的工程有了一个总的title,下来就让试试: 先来建一个store,用来存放我们的初始化状态,以及更改的方法: 接下来让我们引入必备的依赖,以及相应的默认导出:

    代码如下:

    import Vue from 'vue';
    import Vuex from 'vuex';
    import { state as userState, actions as userActions, mutations as userMutations } from './stores.js';
    
    Vue.use(Vuex);
    
    const state = Object.assign({}, userState);
    const mutations = Object.assign({}, userMutations);
    const actions = Object.assign({}, userActions);
    
    
    export default new Vuex.Store({
        state,
        mutations,
        actions
    });
    
    

    最后让我们来写一下定义的初始值以及需要改变的方法:

    代码如下:

    import {appRouter} from '../assets/data/menu';
    
    // 初始值
    export const state = {
        data: "0",
        menuList: appRouter,
        pageTitle:'初始值'
    
    }
    
    //如何改变 setData方法名
    export const mutations = {
        setData(state, val) {
            state.data = val
        },
        changePage(state,val){
            state.pageTitle = val
        }
    
    }
    //实现改变
    export const actions = {
        setData(contex, val) {
            contex.commit("setData", val)
        },
        changePage(contex,val){
            contex.commit("changePage", val)
        }
    }
    

    使用页面,直接调用即可,不用其他依赖:

    // 获取全局变量
     console.log( this.$store.state.pageTitle)  // 初始值
     
     
     
    // 更改全局变量
    this.$store.commit('changePage', '门店管理')
    

    十八、通过路由传参,获取参数,用于记录状态等

    如下图情况:

    我们需要在点击操作列的详情进入下一个页面,那么我们是需要拿到此行的id 值,并且请求数据,然后再去渲染页面,那么我们如何通过路由来实现参数的传递呢? 借鉴

    第一种:通过path,query 来传参,参数会在地址栏中显示

    // 传参
    <div>
        <router-link tag="a" :to="{path:'detail',query:{id:1}}" >详情</router-link>
    </div>
    
    // 接收参数
    <script>
    export default{
        mounted (){
           const id = this.$route.query.id;   //可以拿到从列表页传过来的id  
        }
    }
    
    </script>
    

    第二种:通过name,param 来传参,参数不会在地址栏中显示

    // 传参
    <div>
        <router-link tag="a" :to="{name:'detail',params:{id:1}}" >详情</router-link>
    </div>
    
    // 接收参数
    <script>
    export default{
        mounted (){
           const id = this.$route.params.id;   //可以拿到从列表页传过来的id  
        }
    }
    
    </script>
    

    **⚠️注意:取参数时用的是this.$route,而不是this.$router

    上面两个大同小异,接下来我们试试传动态的参数:

    // 传参
    <div>
        <router-link tag="a" :to="{name:'detail',params:{id:val}}" >详情</router-link>
    </div>
    
    // 接收参数
    <script>
    export default{
        data () {
            val:1212
        },
        mounted () {
           const val = this.$route.params.val;   //可以拿到从列表页传过来的val值  
        }
    }
    
    </script>
    

    继续升级,接下来我们试试,传多个动态参数,每个key用逗号分割即可:

    // 传参
    <div>
        <router-link tag="a" :to="{name:'detail',params:{id:id,name:name,age:age}}" >详情</router-link>
    </div>
    
    // 接收参数
    <script>
    export default{
        data () {
            id:1212,
            name:"张小一",
            age:10
        },
        mounted () {
           const id = this.$route.params.id;   //可以拿到从列表页传过来的id值 
           const name = this.$route.params.name;   //可以拿到从列表页传过来的name值  
           const age = this.$route.params.age;   //可以拿到从列表页传过来的age值  
        }
    }
    
    </script>
    

    再升级,我们接下来传一个数组改如何操作

    // 传参
    <div>
        <router-link tag="a" :to="{name:'detail',params:{list:[1,2,3,4]}}">详情</router-link>
    </div>
    
    // 接收参数
    <script>
    export default{
        data () {
           arr:
        },
        mounted () {
           const list = this.$route.params.list;   //可以拿到从列表页传过来的list数组  
        }
    }
    
    </script>
    

    !!!⚠️注意:一般情况下不建议使用路由传过多的参数

    十九、关于跳转路由后,清楚计时器的解决方法

    某天有看到一篇大佬的文章,讲述的是跳转页面后,原页面计时器仍然在执行的问题,正好自己也有写国一个实时的钟表的demo,于是就实验了一番:

    页面如下:

    在每次进入页面后调用了setTimeout,每秒去更新时间,为了验证切换路由后,是否还会调用计时器,我在计时器后面加了一个打印数值来观察,如下:

    ...
    ...
    ...
     this.inif.oSecond = seconds;
     this.inif.seconds = seconds * 6;
     this.inif.minute = minutes * 6 + seconds * (6 / 60);
     this.inif.hour = (hours - 12) * 30 + minutes * (360 / 12 / 60);
     
     //调用计时器
     this.timer = setTimeout(this.time, 1000)
     
     //打印参考值
     console.log(this.inif.seconds)
    

    可以看到后台的打印情况:

    现在我们切换路由去往其他的页面,看看计时器是否停止:

    实际证明,它并没有停止,计时器依然在执行。这样是非常消耗性能的。接下来我们来看看,大神是怎么解决的:

    第一种解决方案:

    beforeDestroy 生命周期内进行清除:

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

    鉴于上面的方法,大神给出解释:我们的代码独立于清理代码,此种操作使得我们比较难于程序化的清理我们建立的所有东西。

    第二种解决方案:

    通过$once来监听定时器,在beforeDestroy钩子可以被清除

    ...
    ...
    ...
    
    this.timer = setTimeout(this.time, 1000)
    
    this.$once('hook:beforeDestroy', () => {
        clearInterval(this.timer);
    })
    
    

    两种解决方案都可以将我们的问题解决,相对来说第二种较优雅!!! 借鉴于 chinaBerg 大神

    二十、关于打包后在dist文件夹的js里面会有很多.map文件

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

    解决方案:

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

    二十一、监听当前vueurl路径,发生变化时刷新页面

    此需求是由于项目中一个列表中详情点击后需要重新请求当前列的id,重新绘制当前页面,如下:

    首先我将我需要跳转的信息使用组合完整,如下:

     this.$router.push({
                        path: '/crowdmanage/crowddetails',
                        query: {
                            name: 'detail',
                            id: 17
                        }
                    });
    

    点击后url确实发生了变化,但vue并没有检测到,也就是url变化了,页面并没有重新去请求??????到底是什么情况????---(此时第一感觉是:我的跳转方式哪里出现了问题吗?为什么vue 检测不到呢?或者我要换成原生的跳转??.....) 去咨询了团队的前端小哥哥,小哥哥给出了以下方式解决:---》》》》

    由于我的页面组件里面嵌套了小的组件,所以我需要在跳转到的.vue的页面添加:

     watch: {
                $route(to, from) {
                    this.initData();
                }
            }
    

    this.initData()是所有的请求信息,当url发生变化后,watch就可以监听到,然后重新去请求数据。啦啦啦啦啦 解决了。。。。。感谢小哥哥!!!

    二十二、编写动态组件

    此时场景为:当一个组件里面有3块内容,而且这三块内容每次展示都只展示一个tab,不会同时展现,如下图:

    此时,我们可以把这三个写成动态组件,将其包裹在一个index文件里。

    首先,我们先建一个主index文件,用于包裹三个子文件,再建三个子的.vue文件,这四个文件并列即可。

    index.vue 主文件内容如下(以下tabs来自UI组件iview):

    <template>
        <div>
            <Tabs size="small" @on-click="tabChange" v-model="tabContent">
                <TabPane v-for="(item,index) in tabList" :key="index" :label="item.label" :name="item.name"></TabPane>
            </Tabs>
            <--用:is的方式绑定动态变量,变量改变,随之显示的组件切换-->
            	<component :is="tabView"></component>
        </div>
    </template>
    
    <script>
      //引入三个子组件
        import A from "./A"
        import B from "./B"
        import C from "./C"
        export default {
            data() {
                return {
                    tabContent:"",
                    tabView: "",
                    tabList: [{
                            name: "a",
                            label: "标签a"
                        },
                        {
                            name: "b",
                            label: "标签b"
                        },
                        {
                            name: "c",
                            label: "标签c"
                        }
                    ],
                }
            },
            // 注册组件
            components: {
                A, // 标签a
                B, // 标签b
                C, // 标签c
            },
            mounted() {
                this.tabChange(name)
            },
            methods: {
                //切换tab事件
                tabChange(name) {
                    this.tabView="A";
                    this.tabContent = name || "a";
                    if (name === "a") {
                        this.tabView = A;
                    } else if (name === "b") {
                        this.tabView = B
                    }else if (name === "c") {
                        this.tabView = C
                    }
                },
            },
            watch: {}
        }
    </script>
    
    <style scoped lang="less">
    
    </style>
    
    

    子组件的A.vue的文件内容如下:

    <template>
        <!-- 第一个tab页的内容  -->
        <div>
            我是标签a的内容
        </div>
    </template>
    
    <script>
        export default {
            data() {
                return {
                  
                }
            },
            props: {},
            components: {},
            mounted() {},
            methods: {},
            watch: {}
        }
    </script>
    
    <style scoped lang="less">
    
    </style>
    
    

    其他几个子文件也如同A文件一样,这样点击不同的tab 就会请求不同的.vue文件。

    二十三、keep-alive 缓存动态组件,以及它的参数,主要用于保留组件状态或避免重新渲染。

    <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

    Props

    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
    • max - 数字。最多可以缓存多少组件实例。
    <!-- 基本 -->
    <keep-alive>
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 多个条件判断的子组件 -->
    <keep-alive>
      <comp-a v-if="a > 1"></comp-a>
      <comp-b v-else></comp-b>
    </keep-alive>
    
    <!-- 和 `<transition>` 一起使用 -->
    <transition>
      <keep-alive>
        <component :is="view"></component>
      </keep-alive>
    </transition>
    
    

    注意,``是用在其一个直属的子组件被开关的情形。如果你在其中有 `v-for` 则不会工作。如果有上述的多个条件性的子元素,`` 要求同时只有一个子元素被渲染。

    • includeexclude属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
    <!-- 逗号分隔字符串 -->
    <keep-alive include="a,b">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 正则表达式 (使用 `v-bind`) -->
    <keep-alive :include="/a|b/">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 数组 (使用 `v-bind`) -->
    <keep-alive :include="['a', 'b']">
      <component :is="view"></component>
    </keep-alive>
    
    • max 最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
    <keep-alive :max="10">
      <component :is="view"></component>
    </keep-alive>
    

    二十四、父子组件传递参数 props

    首先来写一个父子组件传参的例子🌰: 父组件内容:

    <template>
      <div>
        下面是引入的组件:
        <template-a :isItem="item"><template-a/>
      </div>
    </template>
    <script>
      import templateA from "./templateA"
      export default {
        data () {
          return {
            item:"000"
            
          }
        },
        components: {
          templateA
        },
        methods: {
          
        },
        mounted () {
          
        }
      }
    </script>
    <style>
    
    </style>
    
    

    子组件内容:

    <template>
      <div>
      我是templateA 组件,这是从父组件传过来的参数: {{isItem}}
      </div>
    </template>
    <script>
    
      export default {
        data () {
          return {
            
          }
        },
        props:{
          isItem:{
            type:String,
            default:""
          }
        },
        components: {
        
        },
        methods: {
          
        },
        mounted () {
          
        }
      }
    </script>
    <style>
    
    </style>
    

    props 配置多个类型

    例如有时我们在传递参数的时候,不确定拿到是什么类型,有可能是Number有可能是String,此时我们就可给props的默认类型时添加一个数组,如下:

    props:{
        data: {     
    		type: [String, Number],
    		default: ''
    	},
    }
    
    

    这样在拿到参数的时候,无论是string还是number都不会因为类型错误而报错,加大了传参的容错率。 详细参考

    • 补充:props 执行的比data早,所以在data中可以拿到props中的传值。

    二十五、 非常好用的计算属性 computed

    在我们的日常编码中,往往会遇到很多需要判断的条件、计算数据或者控制权限等,如下:

    条件判断: AA&&BB&&cc  ;
    计算数据: A+B-C-Dmessage.split('').reverse().join('');
    控制权限: A === B || B===C

    以上的内容我们可以在标签或者模版{{}}中进行判断,计算,但如果逻辑较多,或者条件较复杂,这样还在标签或者模版上进行操作的话,我们的代码就看起来会很难理解,而且很冗余,这时,我们可以把这些复杂的逻辑判断放到 computed 中进行计算,返给模版一个变量即可:

    <template>
      <div id="example">
        <p>未计算前的值: "{{ message }}"</p>
        <p>计算完毕返回的值: "{{ reversedMessage }}"</p>
      </div>
    </template>
    <script>
    
      export default {
        data () {
          return {
             message: 'Hello',     
          }
        },
        computed: {
        // 计算属性的 getter 在这里面进行一系列的计算,最后return出一个结果给模版
          reversedMessage: function () {
          // `this` 指向 vm 实例
            return this.message.split('').reverse().join('*')
          }
       },
        components: {
          
        },
        methods: {
          
        },
        mounted () {
          
        }
      }
    </script>
    <style>
    
    </style>
    

    页面展示效果如下:

    计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。

    参考:vue-computed

    二十六、组件之间的传参 bus

    关于组件和组件之间的参数传递(非父子),vue推荐使用bus来进行传递,可进行上行、下行、平行、斜行各种传递,下面我们就来用bus来验证。 首先需要先将它创建出来,可以放在公用的工具js中,最后统一引入到全局mian.js中或者直接在mian中都可以,根据你情况创建并引入就行:

    import Vue from "vue";
    
    export default new Vue()
    

    此时就可以直接用了,下面让我们来传个参数试试:

    A页面:

    <template>
      <div id="example">
        <Button @click="searIconClick" slot="append" icon="ios-search"></Button>
      </div>
    </template>
    <script>
    
      export default {
        data () {
          return {
             message: 'Hello',     
          }
        },
        methods: {
          // 当点击按钮后,将message使用$emit传给B页面
          searIconClick(){
            this.bus.$emit("searInfo", this.message);  
          }
        },
      }
    </script>
    
    

    B页面:

    <template>
      <div id="example">
           {{value}}
      </div>
    </template>
    <script>
    
      export default {
        data () {
          return {
             value: '',     
          }
        },
    
        components: {
          
        },
        methods: {
    
        },
        mounted () {
           this.bus.$on('searInfo', (message) => {
                    this.value = message;
                });
        }
      }
    </script>
    <style>
    
    </style>
    
    

    A 页面发送的事件名B页面接受的事件名必须一致,而且需是全项目唯一的,不然会被其他传参影响,或者影响其他传参,我之前就出过这样的错,让小姐姐找问题找来很久😂 !

    二十七、 计算属性conputed,用来处理数据计算

    在我们项目中,有时需要进行一些计算,只要数据改变,计算实时更新,如数据不发生变化,缓存计算结果。 效果展示地址:

    二十八、 vue中的锚链接跳转

    vue中的锚链接和普通的html不同,关于vue中的锚链接可以参考vue 中的 scrollBehavior 滚动行为

    ----------------废话不多说,直接上代码-----------

    router.js中的router 实例中


    const router = new VueRouter({
                routes,
                mode: 'history',
                scrollBehavior(to, from, savedPosition) {
                if (to.hash) {
                    return {
                        selector: to.hash
                    }
                }
            }
        })
      
    export default router;
    

    vue中点击跳转的位置 使用<a>链接包起来

    <div>
        <a href="#populationInformation">A</a>
    </div>
    
    <div>
        <a href="#peopleCounting">B</a>
    </div> 
    <div>
        <a href="#trafficAnalysis">C</a>
    </div>
    

    在需要跳转到的位置

    <div id='populationInformation '> A跳转到此</div>
        
    <div id='peopleCounting'> B跳转到此</div>
        
    <div id='trafficAnalysis '>C跳转到此</div>
    

    要保证<a>标签的 href 的地址要和下面id的值是相同的才可以完成相应的跳转, 至于在router中的配置也是必须的

    二十九、 vue如何获取当前页面的url地址

    当场景需要我们获取url地址,以做某些判断操作时,我们可以使用下面的方法来获取。

    1.获取全部url

    console.log("完整的url地址:",window.location.href)  
    
    // 完整的url地址: https://juejin.im/editor/posts/5a1a6a6551882534af25a86b
    
    

    2.获取路由路径

    console.log("路由路径:",this.$route.path)
    
    // 路由路径:/activity/list
    
    

    3.获取路径参数

    console.log("路径参数:",this.$route.query)
    
    // 路径参数: {name: "detail", id: "109", tabView: "self", groupID: ""}
    
    

    (参考:十八、通过路由传参,获取参数,用于记录状态等)

    三十、两种前端下载方式

    1.通过后端接口直接通过url跳转到下载地址即可

    window.location.replace(http://172.******/exports/${id}/download`)
    

    2.通过后端接口前端实现下载请求

     this.$axios.get(`${this.$config.apiDomain}/crowds/exports/${row.id}/download-crowd`)
        .then(({data})=>{
        // data 直接传入now Blob中
            const blob = new Blob([data], { type: 'cache-control,expires,pragma,content-type' })
            const downloadElement = document.createElement('a')
            const href = window.URL.createObjectURL(blob)
            downloadElement.href = href
            downloadElement.download = row.id+'.csv'
            document.body.appendChild(downloadElement)
            downloadElement.click()
    		document.body.removeChild(downloadElement) // 下载完成移除元素
    		window.URL.revokeObjectURL(href) // 释放掉blob对
    	})
    
    

    借鉴地址

    三十一、给组件添加原生事件

    现阶段我们前端开发者做项目一般手里都有各种各样的UI库,例如vueUI库有 iview,elementUI 等等,UI库的组件都会提供一些需要的事件,你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on.native 修饰符:

    <base-input v-on:focus.native="onFocus"></base-input>
    

    参考地址


    值得推荐

    • vue的代码风格指南 里面有vue 推荐的代码编写风格,强烈推荐的,推荐及谨慎使用的各种风格示例,推荐大家看看,有助于开发效率提升。
    • vue的网页运行工具 iVuewRun网页版的vue,可直接运行使用,还可以分享给小伙伴!

    接下来我还会持续追加,看文章的小伙伴们可以添加一下关注哦!

    作者:Christine    
    出处:https://juejin.im/post/6844903512493539341
    版权所有,欢迎保留原文链接进行转载:) 
    

    如果你对我对文章感兴趣或者有些建议想说给我听👂

    邮箱:christine_lxq@sina.com

    最后:
            祝各位工作顺利!
                            -小菜鸟Christine