Vue.js 的实用技巧(一)

Vue.js 的实用技巧(一)

Vue.js 2.0 已经发布了有一段时间,生态链也日渐完善。作者在使用 Vue.js 开发项目时收集的一些工具和使用技巧,分享给各位。

技巧一:vue build 零配置快速开始开发

vue-cli 是官方推出的一款 Vue 脚手架工具它可以很方便的搭建简单或者完整的 Vue 开发环境。最近它加入一个新功能 —— vue build

类似于 React 的 next.js 但又不完全等同。它的功能是可以让你不用任何配置,只写一个 *.vue 文件就可以通过 vue build 命令启动。非常适合于只想写一点 Vue 的 demo 或者小型项目的用户使用。如果你已经安装了 vue-cli 并更新到 2.8.0,那你现在就可以开始体验!

这是一个例子,创建一个 app.vue 文件并随意写些内容:

<template>
  <h1>
    Hello <span>Vue.js</span>
  </h1>
</template>

<style>
  h1 {
    font-family: 'Helvetica Neue';
    color: #2c3e50;
    text-align: center;
    margin-top: 30vh;
  }

  span {
    color: #42b983;
  }
</style>

在终端里输入 vue build app.vue 并打开 localhost:4000 就可以预览到结果了。

vue build app.vue

> WAIT  Compiling...
> DONE  Compiled successfully in 110ms

> Open http://localhost:4000

当然如果你想自定义配置也是支持的,具体内容可以参考 vue build 文档

技巧二:vue-loader 特别的使用姿势

vue-loader 是处理 *.vue 文件的 webpack loader。它本身提供了丰富的 API,有些 API 很实用但很少被人熟知。例如接下来要介绍的 preserveWhitespacetransformToRequire


1. 用 preserveWhitespace 减少文件体积

有些时候我们在写模板时不想让元素和元素之间有空格,可能会写成这样:

<ul>
  <li>1111</li><li>2222</li><li>333</li>
</ul>

当然还有其他方式,目的是为了去掉元素间的空格。其实我们完全可以通过配置 vue-loader 实现这一需求。


{
  vue: {
    preserveWhitespace: false
  }
}

它的作用是阻止元素间生成空白内容,在 Vue 模板编译后使用 _v(" ") 表示。如果项目中模板内容多的话,它们还是会占用一些文件体积的。例如 Element 配置该属性后,未压缩情况下文件体积减少了近 30Kb。

2. transformToRequire 再也不用把图片写成变量了

以前在写 Vue 的时候经常会写到这样的代码:把图片提前 require 传给一个变量再传给组件。

<template>
  <div>
    <avatar :default-src="DEFAULT_AVATAR"></avatar>
  </div>
</template>

<script>
  export default {
    created () {
      this.DEFAULT_AVATAR = require('./assets/default-avatar.png')
    }
  }
</script>

其实通过配置 transformToRequire 后,就可以直接配置。


{
  vue: {
    transformToRequire: {
      avatar: ['default-src']
    }
  }
}

于是我们代码就可以简化不少

<template>
  <div>
    <avatar default-src="./assets/default-avatar.png"></avatar>
  </div>
</template>

vue-loader 还有很多实用的 API 例如最近加入的 Custom Blocks,感兴趣的各位可以去文档里找找看。

技巧三:template + render function

在写 Vue 模板时,有时会遇到不得不手写 Render Function 的情况。如需要根据 prop 更改布局——Element 分页组件 ——或者根据 prop 判断生成指定标签。

比如我们想实现 Element 里的 input 组件的用法:

<field label="标题" type="input" />
<field label="内容" type="textarea" />

会渲染出一个 input 和 textarea

那么我们用 Vue 模板写就需要根据 type 判断当前渲染哪个组件。

<template>
  <div>
    <label>{{ label }}</label>

    <input v-if="type !== 'textarea'" :type="type">
    <textarea v-else></textarea>
  </div>
</template>

<script>
  export default {
    name: 'field',

    props: ['type', 'label']
  }
</script>

如果我们还需要传原生组件上的属性,例如 placeholder, name, disabled 以及各种校验属性和事件,那么重复代码就会非常多。但是如果我们用 jsx 写就会容易许多且代码也会更清晰。


export default {
  name: 'field',

  props: ['type', 'label'],

  render (h) {
    const tag = this.type === 'textarea' ? 'textarea' : 'input'
    const type = this.type === 'textarea' ? '' : this.type

    return (
      <div>
        <label>{ this.label }</label>
        { h(tag, { props: { type } }) }
      </div>
    )
  }
}
可是如果组件再复杂一些,需要加入表单验证的错误提示或者一些 icon 等内容,那么写模板就比写 Render Function 更容易阅读。那我们是否可以将两种方式结合起来?

在 Vue 里有一个强大的特性:Slots —— 给组件设置一个特殊的 slot 组件,让使用者可以传入自定义的模板内容。但是在实际使用中,我发现其实是可以给 slot 赋值的。还是用上面的例子,假设 label 部分我们想写成模板,input 的部分根据 type 生成特性的内容。那么我们模板可以写成:

<template>
  <div>
    <label>{{ label }}</label>
    <slot></slot>
  </div>
</template>

input 部分用 slot 代替,但是并不是让使用者自己定义,而是我们给这个 slot 赋值:

<script>
  export default {
    name: 'field',

    props: ['type', 'label'],

    created() {
      this.$slots.default = [ this.renderField() ]
    },

    methods: {
      renderField() {
        const h = this.$createElement
        const tag = this.type === 'textarea' ? 'textarea' : 'input'
        const type = this.type === 'textarea' ? '' : this.type

        return h(tag, { props: { type } })
      }
    }
  }
</script>

其中 renderField 就是我们手写的 Render Function,在组件 created 时调用并赋值给 this.$slots.default。意思就是我们用手写的 vnode 强制替换掉了 $slots.default 的 vnode,从而达到 vue template 和 Render Function 结合的目的。

但是这有个问题,这么做我们就破坏了 slot 的更新规则。看过源码可以知道 slots 是在父组件 的 vdom 更新时才更新的 slots,也就是说我们没法在组件内部触发 renderField 方法,除非用 watch,但是需要 watch 的 prop 多的话也很麻烦。

所以如果你只是需要在初始化时(created)用这种方式渲染组件,并且明白它的限制,其实还是可以发挥很大的用处的。例如 Element 的 notification,通过传入一段内容可以显示一个消息提醒。其实我们还可以传入 vdom 来显示一段自定义内容,在线例子

const h = this.$createElement

this.$notify({
  title: 'GitHub',
  message: h('div', [
    h('p', '[GitHub] Subscribed to ElemeFE/element notifications'),
    h('el-button', {}, '已读')
  ])
})

希望你喜欢这一期的分享,后面我们还会连载一些 Vue 实用技巧,下期见!

参考资料

文章被以下专栏收录

    只看代码的话,上 https://github.com/ElemeFe 。这一群人,关心的不是「如何写前端」而是「如何很好地运行一个 ( web ) APP」;这一群人,会在监控屏上加上弹幕,会让实习生自主招聘,会设计、编写、监控整个 APP 的生命周期;这一群人,玩的时候... 更卖力,就像从来没来过那般卖力,卖力地热爱生活。所以这些创作大多基于 ❤️