Vue.js 递归组件

Recursive Component in Vue.js

Apr 27, 2019

见习魔法师

在阅读这篇文章之前,建议先阅读这篇文章 r{{ changeThemeOnce({primary: '#39C5BB', secondary: '#92E8C6', accent: '#BCFF64'}) }}

在数学与计算机科学中,递归是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以自相似方法重复事物的过程。递归不仅可以被用于函数中,许多事物的结构就是递归的,例如,数据结构中树的结构就是一个典型的递归的结构;其子树本身也是树。

这样的结构可以简单地描述为:

type Tree<T> = {
    value: T
    children: Tree<T>[]
}
不难发现,评论本身就是一个树形结构,评论有回复,回复本身也是一个评论,因此为了更好地呈现评论地结构,相应的DOM结构也应当是递归的。

为了制造一个递归的DOM,最朴素的想法便是使用一个递归函数Comment => Node,然而遗憾的是,在Vue中最小的复用单位是组件。

当然,这难不倒我们睿智的YYX大神,你只用给一个组件定义好Props,然后给予一个适当的名字就好了嘛。

<template>
    <div>
        {{ tree.value }}
        <ul>
            <li v-for="child in tree.children" :key="child.value">
                <recursive :tree="child"></recursive>
            </li>
        </ul>
    </div>
</template>
<script>
export default {
    name: 'recursive',
    props: {
        tree: {
            type: Object
        }
    }
}
</script>
说实话,it sucks

我为此必须使用一个文件,我使用递归组件的理由可能有许多,有时可能不需要复用,但是如果一直逼着我要创建一个相对较重组件,总是十分糟心的。

当然你可以不用创建文件,使用Vue.component等方法将会污染全局命名空间,这个过程是不可控的(因为有时候我偷懒想要取一个简单的名字) 这个过程本身也比较糟心

总之,为了递归使用一个组件,其中的语法噪音太多。

看见 Component => Node这个type的函数,自然会想到Vue的JSX写法,虽然需要组件,但是组件本身可以有很多的递归函数,可以灵活地组合,不必每一个递归组份都额外写一个组件。

比起原本的template写法,JSX写法只用写出组件中的render: (CreateElement, RenderContext<Props>) => VNode方法即可。

之前中了React 和 TSX的毒,故这里直接介绍TSX写法,但是不得不吐槽Vue对TS的支持真的不是十分理想。

比起单文件组件,JSX写法的官方文档很少,更不用说TSX&Class写法了。

现在Component的写法也有很多,有class写法和原本的plain object写法。

class 写法需要一个辅助的依赖vue-tsx-support

export default tsx.componentFactoryOf<{
  tree: Tree<string>
}>().create({
  props: {
    tree: Object
  },
  render() {

    return this.renderTree(this.tree as Tree<string>)
  },
  methods: {
    renderTree<T>(tree: Tree<T>) {
      return (
        <div>
          {tree.value}
          <ul>
            {
              tree.children.map((t, i) => (
                <li key={i}>{this.renderTree(t)}</li>
              ))
            }
          </ul>
        </div>
      )
    }
  }
})

这个蛋疼的是Props是一个Value,而TS不支持类型为一等公民,因此必须传入Object,在使用的时候再转换成对应类型;为了在写的时候有友好的类型提示,这个Props你还必须写两遍,这个是最糟糕的。而且目前大多数Vue的库对TSX的支持还是不友好,我写代码的时候是全程没有type hint的,十分糟心。

希望有一天能够支持函数组件,组件可以是一个函数,像react那样。那么Vue就成了

Roselia{{ btn('哦,我喜欢这个主题!', saveCurrentTheme, 'primary') }}