Vue中你不知道但却很实用的黑科技

最近数月一直投身于 iView 的开源工作中,完成了大大小小 30 多个 UI 组件,在 Vue 组件化开发中积累了不少经验。其中也有很多带有技巧性和黑科技的组件,这些特性有的是 Vue 文档中提到但却容易被忽略的,有的更是没有写在文档里,今天就说说 Vue 组件的高级玩法。

写在前面

本文所讲内容大多在 iView 项目中使用,大家可以前往关注,并结合源代码来研究其中的奥妙。项目地址:
https://github.com/iview/iview

目录

  • 递归组件

  • 自定义组件使用 v-model

  • 使用$compile()在指定上下文中手动编译组件

  • 内联模板inline-template

  • 隐式创建 Vue 实例

递归组件

递归组件在文档中有介绍,只要给组件指定一个 name字段,就可以在该组件递归地调用自己,例如:

var iview = Vue.extend({
  name: 'iview',
  template:
    '
' + // 递归地调用它自己 '' + '
' })

这种用法在业务中并不常见,在 iView 的级联选择组件中使用了该特性
(https://github.com/iview/iview/tree/master/src/components/cascader)
效果如下图所示:

图中每一列是一个组件(caspanel.vue),一开始想到用 v-for来渲染列表,但后面发现扩展性极低,而且随着功能的丰富,实现起来很困难,处理的逻辑很多,于是改写成了递归组件:

props 比较多,可以忽略,但其中关键的两个是datasublist,即当前列数据和子集的数据,因为预先不知道有多少下级,所以只需传递下级数据给组件本身,如果为空时,递归就结束了,Vue 这样设计的确很精妙。
注:该方法在 Vue 1.x 和 2.x 中都支持。

自定义组件使用 v-model

我们知道,v-model是在表单类元素上进行双向绑定时使用的,比如:


这时data就是双向绑定的,输入的内容会实时显示在页面上。在 Vue 1.x 中,自定义组件可以使用 props 的.sync双向绑定,比如:

在 Vue 2.x 中,可以直接在自定义组件上使用 v-model了,比如:

在组件my-component中,通过this.$emit('input')就可以改变data的值了。
虽然 Vue 1.x 中无法这样使用,但是如果你的组件的模板外层是 inputselecttextarea等支持绑定 v-model 特性的元素,也是可以使用的,比如 my-component 的代码是:

那也可以使用上面2.x的写法。

使用$compile()在指定上下文中手动编译组件

注:该方法是在 Vue 1.x 中的使用介绍,官方文档并没有给出该方法的任何说明,不可过多依赖此方法。
使用$compile()方法,可以在任何一个指定的上下文(Vue实例)上手动编译组件,该方法在 iView 新发布的表格组件 Table 中有使用:
https://github.com/iview/iview/tree/master/src/components/table/cell.vue
由于表格的列配置是通过一个 Object 传入 props 的,因此不能像 slot 那样自动编译带有 Vue 代码的部分,因为传入的都是字符串,比如:

{
    render (row) {
        return `${row.name}`
    }
}

render函数最终返回一个字符串,里面含有一个自定义组件 i-button,如果直接用{{{ }}}显示,i-button 是不会被编译的,那为了实现在单元格内支持渲染自定义组件,就用到了$compile()方法。
比如我们在组件的父级编译:

// 代码片段
const template = this.render(this.row);    // 通过上面的render函数得到字符串
const div = document.createElement('div');
div.innerHTML = template;
this.$parent.$compile(div);    // 在父级上下文编译组件
this.$el.appendChild(cell);    // 将编译后的html插入当前组件

这样一来, i-button就被编译了。
在某些时候使用$compile()确实能带来益处,不过也会遇到很多问题值得思考:

  • 这样编译容易把作用域搞混,所以要知道是在哪个Vue实例上编译的;

  • 手动编译后,也需要在合适的时候使用$destroy()手动销毁;

  • 有时候容易重复编译,所以要记得保存当前编译实例的id,这里可以通过 Vue 组件的_uid来唯一标识(每个Vue实例都会有一个递增的id,可以通过this._uid获取)

另外,Vue 1.x 文档也有提到另一个$mount()方法,可以实现类似的效果,在 Vue 2.x 文档中,有 Vue.compile()方法,用于在render函数中编译模板字符串,读者可以结合来看。

内联模板inline-template

内联模板并不是什么新鲜东西,文档中也有说明,只是平时几乎用不到,所以也容易忽略。简短解说,就是把组件的 slot 当做这个组件的模板来使用,这样更为灵活:



    {{ data }}



因为使用了 inline-template 内联模板,所以子组件不需要