3个简单的技巧让你的 vue.js 代码更优雅

754次阅读  |  发布于3年以前

前言

近来入坑了一个Vue项目,感觉掉进了祖传屎山中,可读性极差,更别说可维护性了。故借此专栏提几点关于Vue代码可读性的建议,觉得有用的点个赞,觉得建议不合理的发表评论批评一下,有更好的建议欢迎发表评论补充一下。

一、善用组件让代码更有条理性

千万不要把一个页面的实现代码都梭哈在一个.vue文件中,除非这个页面非常简单,不然这个.vue文件中的代码会又长又臭。

Vue提供组件的目的不仅仅是为了复用,也可以用来分割代码,甚至善用组件可以优化页面的渲染更新速度。这是因为Vue页面渲染更新时不会去更新页面中的组件,除非组件的props或者slot所引用的数据发生变化。

可以按以下步骤来将一个Vue页面分割成一个个组件让代码更有条理性

1.1、提取UI组件

如何定义UI组件呢?个人建议按有无处理服务端数据来区分UI组件和业务组件。例如加载弹窗、二次确认弹窗、消息提示框等等属于UI交互组件。

将UI组件提取出来后,可以把UI交互的代码和业务交互的代码剥离开来。切记不能UI组件中写业务代码,这样UI组件将无法复用。

举一个反例,在二次确认弹窗中添加二次确认后要处理的业务代码,导致UI组件将无法复用。我们可以模仿ElementUI中二次确认弹窗的调用来实现一个二次确认弹窗组件。

this.$confirm(message, title, options)
  .then(res =>{})
  .catch(err =>{})

这样业务代码可以写在then的回调函数中,组件的核心实现代码如下所示:

//confirm.vue
<template>
  <div v-show="show">
    //...
    <div @click="ok"></div>
    <div @click="cancel"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      show: false,
    }
  },
  methods: {
    ok() {
      this.show = false;
      this.resolve();
    },
    cancel() {
      this.show = false;
      this.resolve();
    },
  }
}
</script>
//index.js
import Vue from 'vue';
import options from './confirm.vue';
const Confirm = Vue.extend(options);
let confirm = undefined;
const ConfirmInit = (options = {}) => {
  return new Promise((resolve, reject) => {
    options.resolve = resolve;
    options.reject = reject;
    confirm = new Confirm({
      el: document.createElement('div'),
      data: options
    })
    document.body.appendChild(confirm.$el);
    Vue.nextTick(() => {
      if (confirm) confirm.show = true;
    })
    return confirm;
  })
}
Vue.prototype.$confirm = ConfirmInit;
//main.js
import 'components/confirm/index.js';//全局注册二次确认弹窗confirm组件

1.2、按模块提取业务组件

一个页面可以分为多个区域,比如头部、底部、侧边栏、商品列表、成员列表等等,每个区域可以当作一个模块来提取业务组件。

1.3、按功能提取功能组件

按模块提取完业务组件,此时业务组件有可能还是很庞大的,故要按功能在进一步地提取功能组件。

功能有大有小,提取要注意把握几个原则:

二、利用v-bind使组件的属性更具有可读性

如果想要将一个对象的所有属性都作为prop传入组件componentA,可以使用不带参数的v-bind。例如,对于一个给定的对象params

params: {
  id: 1,
  name: 'vue'
}

优化前

<componentA :id="params.id" :name="params.name"></componentA>

优化后

<componentA v-bind="params"></componentA>

三、利用attrs与attrs与attrs与listeners来封装第三方组件

1、$attrs

在封装第三方组件中,经常会遇到一个问题,如何通过封装的组件去使用第三方组件自身的属性和事件。

比如封装一个elementUi组件中的Input输入框组件myInput,当输入错误的内容在输入框下面显示错误的提示。

myInput组件代码如下所示:

<template>
  <div>
    <el-input v-model="input"></el-input>
    <div>{{errorTip}}</div>
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: String,
      default: '',
    },
    errorTip: {
      type: String,
      default: '',
    }
  },
  data() {
    return {
    }
  },
  computed: {
    input: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit('input', val)
      }
    }
  }
}
</script>

这样调用myInput组件,其中errorTip为输入框输入错误的提示。

<myInput v-model="input" :errorTip="errorTip"></myInput>

如果要在myInput组件上添加一个disabled属性来禁用输入框,要如何实现呢?一般同学会这么做

<template>
  <div>
    <el-input v-model="input"
      :disabled="disabled"></el-input>
    <div>{{errorTip}}</div>
  </div>
</template>
<script>
export default {
  props: {
    //...
    disabled: {
      type: Boolean,
      default: false
    }
  },
  //...
}
</script>

过一段时间后又要在myInput组件上添加el-input组件其它的属性,el-input组件总共有27个多,那该怎么呢,难道一个个用prop传进去,这样做不仅可读性差而且繁琐,可以用$attrs一步到位,先来看一下attrs的定义。

$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件

<template>
  <div>
    <el-input v-model="input"
      v-bind="$attrs"></el-input>
    <div>{{errorTip}}</div>
  </div>
</template>

这还不够,还得把inheritAttrs选项设置为false,为什么呢,来看一下inheritAttrs选项的定义就明白了。

默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrsfalse,这些默认行为将会被去掉。而通过 $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。注意:这个选项不影响 class 和 style 绑定。

简单来说,把inheritAttrs设置为falsev-bind="$attrs" 才生效。

<template>
  <div>
    <el-input v-model="input"
      v-bind="$attrs"></el-input>
    <div>{{errorTip}}</div>
  </div>
</template>
<script>
export default {
  inheritAttrs: false,
  props: {
    value: {
      type: String,
      default: '',
    },
    errorTip: {
      type: String,
      default: '',
    }
  },
  data() {
    return {
    }
  },
  computed: {
    input: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit('input', val)
      }
    }
  }
}
</script>

这样就可以很清楚的把el-input组件的属性和myinput组件的属性区分开来了,组件的props选项的可读性大大提高。

2、$listeners

那么如何实现在myIpput组件上使用el-input组件上自定义的事件呢,可能你的第一反应是this.$emit

<template>
  <div>
    <el-input v-model="input"
      v-bind="$attrs"
      @blur="blur">
    </el-input>
    <div>{{errorTip}}</div>
  </div>
</template>
<script>
export default {
  //...
  methods: {
    blur() {
      this.$emit('blur')
    }
  }
}
</script>
<myInput 
  v-model="input"
  :errorTip="errorTip"
  @blur="handleBlur">
</myInput>

el-input组件有4个自定义事件,还不算多,假如遇到自定义事件更多的第三方组件,要怎么办,难道一个一个添加进去,不仅会增加一堆非必要的methods,而且可读性差很容易和myInput自身的methods混在一起。其实可以用$listeners一步到位,先来看一下$listeners的定义。

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。

<template>
  <div>
    <el-input v-model="input"
      v-bind="$attrs"
      v-on="$listeners">
    </el-input>
    <div>{{errorTip}}</div>
  </div>
</template>
<script>
export default {
  //...
}
</script>
<myInput 
  v-model="input"
  :errorTip="errorTip"
  @blur="handleBlur">
</myInput>

在myInput组件中只要在el-input组件上添加v-on="$listeners",就可以在myInput组件上使用el-input组件自定义的事件。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8