Vue 开发经验小记(持续更新)

7,357

使用 vue 开发过程中遇到的问题或知识点总结,持续更新中…

最近更新:2019-11-29

1.国际化

国际化插件:vue-i18n

2.强制换行与禁止换行

让多行内容显示一行,多余的用...表示

white-space : nowrap
overflow: hidden
text-overflow : ellipsis

内容超过宽度时强制换行

overflow: hidden;
word-wrap:break-word;
overflow-wrap: break-word;

注:CSS3中将 <' word-wrap '> 改名为 <' overflow-wrap '>,用时最好两个都写上

3.显示宽高相等的图片,宽度为屏幕宽度,高度与宽度相等

<div class="image-header">
  <img :src="food.image"/>
</div>

.image-header
    position: relative
    width:100%
    height: 0
    padding-top : 100%
    img
        position: absolute
        left: 0
        top: 0
        width: 100%
        height: 100%

重点是父元素的height设为0,padding-top设为100%

4.转换时间的工具类

/**
 * Created by solo on 2018/6/6.
 */

export function formatDatetime(date, fmt) {
  if(/(y+)/.test(fmt)){
    fmt = fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4-RegExp.$1.length))
  }

  let obj = {
    "M+": date.getMonth() + 1,
    "d+": date.getDay(),
    "h+": date.getHours(),
    "m+": date.getMinutes(),
    "s+": date.getSeconds()
  }

  for(let key in obj){
    if(new RegExp(`(${key})`).test(fmt)){
      let str = obj[key] + ''
      fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
    }
  }

  return fmt

}


function padLeftZero(str) {
  return ("00" + str).substr(str.length)
}

使用

let date = new Date(timestamp)
let fmtDate =  formatDatetime(date, 'yyyy-MM-dd hh:mm')

也可以使用第三方的库: moment.jsdayjs

5.给组件绑定原生事件

<custom @click.native='handleClick'></custom>

只需要在@click后面加上.native就可以直接处理原生点击事件了

6. vue中组件间传值

6.1 父子组件间传值

  • 父组件给子组件传值,直接通过props传值
<custom content="hello world"></custom>
  • 子组件给父组件传值,通过 emit发送事件
this.$emit('chooseType', type)

父组件接收事件:

<custom content="hello world" @chooseType="handleType"></custom>

6.2 非父子组件传值

主要通过事件总线传值

在根节点给 Vue 挂载一个空的 Vue 对象

Vue.prototype.bus = new Vue();

需要发送事件的组件里

this.bus.$emit("change", params)

接收事件的组件

this.bus.$on("change", (msg) => {
    //do yourself work
})

7. 动态组件

动态切换显示的组件

<component :is='type'></component>

data(){
	components:{
        component-one,
        component-two
	}
    return{
        type: 'component-one'
    }
}

<component> 是vue官方提供的标签,通过更改 is 指向的子组件名来动态切换组件。

8. v-once 指令

只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令-->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

9.过渡和动画

9.1 过渡

.fade-enter-active, .fade-leave-active{
	transition: opacity 2s
}
.fade-enter, .fade-leave-to{
	opacity: 0
}

9.2 动画结合 Animate.css

//引入 animate.css
<link rel="stylesheet" type="text/css" href="animate.css">

//布局
<transition enter-active-class="animated bounce" leave-active-class="animated shake">
	<p v-if="show">hello world</p>
</transition>
<button @click='toggleShow'>toggle</button>

要定义 enter-active-classleave-active-class 的类名,且必须有 animated,想要什么动画效果就写在第二个位置上

解决第一次显示没有动画的bug

<transition 
	appear 
	enter-active-class="animated bounce" 
	leave-active-class="animated shake" 
	appear-active-class="animated bounce">
	<p v-if="show">hello world</p>
</transition>

<transition> 上添加 appearappear-active-class 即可。

9.3 同时使用过渡和动画

<transition 
    name="fade"
    type='transition'
    appear 
    enter-active-class="animated bounce fade-enter-active" 
    leave-active-class="animated shake fade-leave-active" 
    appear-active-class="animated bounce">
    <p v-if="show">hello world</p>
</transition>

enter-active-classleave-active-class 加上相应的类名 fade-enter-activefade-leave-active ,然后在样式中定义过渡效果即可。

.fade-enter-active, .fade-leave-active{
	transition: opacity 2s
}
.fade-enter, .fade-leave-to{
	opacity: 0
}

动画执行的总时长是根据动画还是过渡来定呢?可以手动指定:

//指定整体动画时间为过渡动画时间
type='transition'

还可以自己指定动画总时长:

//指定动画时长为10秒
:duration="10000"

//分别指定进场时长5秒和出场动画时长10秒
:duration="{enter: 5000, leave: 10000}"

9.4 多个组件和元素的过渡

  • 多个元素过渡
<div id="app">
	<transition name="fade" mode="out-in">
		<div v-if="show" key="hello">Hello world</div>
		<div v-else key="bye">Bye world</div>
	</transition>
	<button @click="toggleShow">Add</button>
</div>

需要给元素加 key, 防止vue复用元素导致没有动画效果。

可以指定切换模式,mode="out-in":先出后进,mode="in-out":先进后出

  • 多个组件过渡跟多个元素过渡类似

9.5 vue中列表过渡

使用 transition-group 属性

<div id="app">
	<transition-group name="fade">
		<div v-for="item in list" :key="item.id">
			{{item.title}}
		</div>
	</transition-group>
	<button @click="add2List">Add</button>
</div>


<style type="text/css" >
	.fade-enter-active, .fade-leave-active{
		transition: opacity 2s
	}
	.fade-enter, .fade-leave-to{
		opacity: 0
	}
</style>

10. img 标签的 src 动态绑定

1)路径固定的图片

路径前加 require()

<img :src="bookingManageImg" slot="icon"/>

bookingManageImg(){
    return this.selectedTab === "bookingManage" ? 	require('../assets/manage_focus.png') : require('../assets/manage_normal.png')
},

2)for 循环里图片的路径不固定

如果在循环里还直接用 require() 的话,webpack 会将图片来当做模块来用,因为是动态加载的,所以 url-loader 将无法解析图片地址,所以会报错找不到模块。

解决办法是采用拼接的方式: require('../assets/icons/' + item.icon + '.png'),对象里只存图片的名字,图片路径是固定的,所以直接用字符串写上去。

list 的数据格式:

const list = [
    {
      name: "美食",
      icon: "food"
    },
    {
      name: "电影",
      icon: "movie"
    },
]

布局文件:

<div class="item" v-for="item in list">
  <img :src="require('../assets/icons/' + item.icon + '.png')" class="icon">
  <div class="name">{{item.name}}</div>
</div>

11. vuex 在页面刷新后状态丢失解决办法

刷新页面后,存在 vuex 的数据会丢失,给调试带来不便。把用户的登录信息放到 sessionStorage 中避免丢失。

const USER_INFO = "userInfo";

export default new Vuex.Store({
  state: {
    userInfo: JSON.parse(sessionStorage.getItem(USER_INFO))
  },
  mutations: {
    setUserInfo(state, userInfo){
      //存储到 sessionStorage 中以防刷新页面后状态丢失
      sessionStorage.setItem(USER_INFO, JSON.stringify(userInfo));
      state.userInfo = userInfo
    }
  }
}

12. 返回记住滚动条位置

详细解析见文章:Vue 返回记住滚动条位置详解

13. 修改页面 Title

首先在 router.js 里,每个路由加上 meta ,设置 title

routes: [
  {
    path: '/login',
    name: 'login',
    component: Login,
    meta:{
      title:'登录'
    }
  },
  {
    path: '/home',
    name: 'home',
    component: Home,
    children: [],
    meta:{
      title:'主页'
    }
  }
]

然后在 main.js 里通过前置路由动态修改 title

router.beforeEach((to, from, next) => {
  /* 路由发生变化修改页面title */
  if (to.meta.title) {
    document.title = to.meta.title;
  }
  next();
})

14. 打包时启用 Gzip 压缩

先安装 webpack 插件

npm install --save-dev compression-webpack-plugin

再在 vue.config.js 里添加如下代码:

const CompressionPlugin = require("compression-webpack-plugin")

module.exports = {

  // 基本路径
  baseUrl: './',
  // 输出文件目录
  outputDir: 'dist',
  // 启用 Gzip 压缩
	configureWebpack: () => {
    module.exports = {
      configureWebpack:config=>{
        if(progress.env.NODE_ENV === 'production'){
          return{
            plugins: [

              new CompressionPlugin({
                test:/\.js$|\.html$|.\css/, //匹配文件名
                threshold: 10240,//对超过10k的数据压缩
                deleteOriginalAssets: false //不删除源文件
              })
            ]
          }
        }
      },
    }
  },
}

Vue CLI 3 默认没有 vue.config.js ,在根目录新建一个就好,位置跟 package.json 同级。

15. vue 与 安卓原生应用通信

我有一篇专门讲解vue与安卓双向通信的文章:

Android webview 与 js(Vue) 交互

16. 如何在样式中使用 scss 的声明的全局变量

sass 声明的变量如:

$color-primary: #409EFF;
$color-success: #67C23A;
$color-warning: #E6A23C;
$color-danger: #F56C6C;
$color-info: #909399;

普通的引用方法为

<style scoped lang="scss">
  @import "../../public/css/index";
  .home {
    color: $color-primary;
  }
</style>

需要先在要使用的文件中引入声明的文件,然后才能使用。

这样比较麻烦,代码冗余。可以使用更优雅的方式:sass-resources-loader

使用 sass-resources-loader 需要两步:

  1. 安装依赖

    npm install sass-resources-loader
    
  2. vue.config.js 里配置。

    这里使用的是 Vue-CLI 3. 将代码中的 resources 路径换成自己的路径即可。

    // vue.config.js
    module.exports = {
      chainWebpack: config => {
        const oneOfsMap = config.module.rule('scss').oneOfs.store
        oneOfsMap.forEach(item => {
          item
            .use('sass-resources-loader')
            .loader('sass-resources-loader')
            .options({
              // Provide path to the file with resources
              resources: './path/to/resources.scss',
    
              // Or array of paths
              resources: ['./path/to/vars.scss', './path/to/mixins.scss']
            })
            .end()
        })
      }
    }
    

其他环境的详细配置说明见 sass-resources-loader 官网

配置完之后,就可以在任意文件里使用 sass 声明的变量啦。

17. 子组件中改变父组件通过 props 传递过来的属性

官方是不推荐子组件直接改变父组件传递过来的属性的,如果你这么做了,会有警告。

但有时的确是需要在子组件中改变父组件的属性,因为省事啊……比如子组件中有 Dialog,Dialog 的显示与隐藏要通过父组件控制,同时子组件关闭了 Dialog 要同步更新父组件中属性值。

当然有很多 "正确" 的方式可以做到,比如 vuex,比如用父子组件的通信,子组件改变值了发个通知通知父组件更新对应的值。

但是,上面两种方法都比较麻烦。我就想在父组件中给子组件传递个变量,子组件改变它的值了,父组件中的变量也会自动更新。

这就用到一个 "漏洞",把要传递的值封装成一个对象,改变对象中的属性值,就不会出现警告。因为对象还是原来的对象,只是里面的值变了。

父组件如下。注意 data 中的 visible: {value: false} 是个对象,不能写成 visible: false,会出现警告。

<template>
  <child :visible="visible"/>
</template>

<script>
  export default {
    components: {
    	child
    },
    data(){
    	return{
    		visible: {value: false}
    	}
    }
  }
</script>

子组件如下:

<el-dialog :visible.sync="visible.value">

当子组件改变值时改变的是 visible 对象中的 value 属性。这样就可以通过子组件直接改变父组件的值了。

18. 九宫格的实现

实现类似的九宫格代码:

<template>
  <div class="view-home">
    <div class="category-wrapper">
      <div class="category-name">分类一</div>
      <div class="icons">
        <div class="item" v-for="item in list">
          <img :src="require('../assets/icons/' + item.icon + '.png')" class="icon">
          <div class="name">{{item.name}}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>

  const LIST = [
    {name: "电影",icon: "movie"},
    {name: "美食",icon: "food"},
    {name: "美发",icon: "hair"},
    {name: "周边游",icon: "around"},
    {name: "酒店",icon: "hotel"},
    {name: '代购',con: "dg"}
  ];

  export default {
    components: {},
    data() {
      return {
        list: ICON_LIST,
      }
    },
  }
</script>

<style scoped lang="scss">
  .view-home {
    display: flex;
    flex-direction: column;
    width: 100%;
    padding: px2rem(10);
    box-sizing: border-box;

    .category-wrapper {
      width: 100%;
      display: flex;
      flex-direction: column;
      background-color: white;

      .category-name {
        font-size: $font-size-normal;
        color: $text-main;
        padding: px2rem(12);
        border-bottom: px2rem(1) solid $border-third;
      }

      .icons {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;

        .item {
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          width: 25%;
          padding: px2rem(10) 0;

          .icon {
            width: px2rem(40);
            height: px2rem(40);
          }

          .name {
            font-size: $font-size-small;
            color: $text-normal;
            margin-top: px2rem(10);
          }
        }
      }
    }
  }
</style>

重点:

  1. 想要自动换行,通过以下三行代码实现:

    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    
  2. 每行几个图标,通过 item 的宽度控制。如果每行四个图标,那 item 的宽度就是 25%,如果每行5个图标,那每个 item 的宽度就是 20%.

19. 超出宽度横向滑动

当子组件的宽度超过父组件,实现横向滑动。
父组件可以是整个屏幕的根元素,也可以是某个特定的元素。只要设置好 css 即可。
设父元素的 class=parent,子元素的 class=child

.parent{
    //其他样式省略,只列出控制横向滑动必须的代码
    display: flex;
    overflow-x: auto;
    overflow-y: hidden; 
    
    .child{
        // 其他样式省略,只列出控制横向滑动必须的代码
        //这句话的意思是不会被压缩大小
        flex-shrink: 0;
    }
}

在你的 css 代码中加上这几行,就可以实现横向滑动啦。

20. 只显示 n 行,多余的用省略号表示

经常有需求是只显示两行或三行,多余的用省略号表示。
适用范围: 因使用了WebKit的CSS扩展属性,该方法适用于WebKit浏览器及移动端;

注:
-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。
常见结合属性:
display: -webkit-box: 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示。
-webkit-box-orient: 必须结合的属性,设置或检索伸缩盒对象的子元素的排列方式。

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;

21. flex 布局中,单个元素靠右对齐

如下图,姓名、性别、评论图标这一行有三个元素,是 flex 布局,前两个元素靠左,评论图标靠右。

已知父元素的布局为

display: flex;
flex-direction: row;
align-items: center;

实现起来有三种方法:

  1. 给姓名和性别两个元素再加一层 div, 并把这个 div 设置 flex: 1 。缺点是多了层嵌套,有点麻烦。
  2. 给评论图标这个元素设置
    flex: 1;
    text-align: right;
    
  3. 给评论图标这个元素设置
    margin-left: auto;
    

后两种方法都比较简单,推荐。