Function-based API

前几天在路上看了 vue conf 关于 vue 3.0 的视频,其中提到了 vue cancel 掉 class API 改为使用 Funtion-based API 的事情,这件事的详情可见 the Class API proposal is being dropped / Function-based Component API

我几乎不是 vue 的用户,所以也无意讨论这个 API 怎么样,或是是不是抄袭/借鉴了 React Hooks API。在我看来,值得注意的是另外一个点:与 vue 自己当前的 API 相比,component 的定义形式少了些 declarative,多了些 imperative(这里我们说的是基于框架运行时的组件实现逻辑,因此 declarative 的获得,基于的是框架的行为与能力)。

declarative 不一定比 imperative 更好,它们的不同决定了它们有各自适合的场景。

如果我们先对比 vue template 与 jsx,显然前者更 declarative,带来的优势很明显:框架层面可以有更多的优化空间,限制也很明显:逻辑实现依赖额外的一套 DSL(Directives),且不够灵活。简单归纳的话,相比 imperative programming,declarative programming 是由编写者放弃一部分能力/自由度,从而获得特定纬度上的优化,再明显不过的 trade-off。所以也不难理解,为什么尤雨溪会反复强调其渲染性能上的优势。

用同样的思路来看 vue 的 legacy API(object/option-based)与 Function-based API 的话,明显前者更 declarative 一点。同理,后者会获得更大的自由度(你可以将原来 datacomputedmethods 这些 object property 形式定义的逻辑,写成 ECMAScript Statements 了,简单举例的话,本来不能把一个 data 字段的定义放在 if 里,现在可以了);然而这里这并不是 vue 采用 Function-based API 的最大原因——vue 主要是为了获得完备的类型推导能力——这事儿的源头是 vue 合并 propsdatamethods 这一设计在根本上的不合理(这也是 vue 要实现类型完备的 Class API 十分困难的原因)。那么后者失去了什么,即,前者有什么优势呢?不难发现,setup 的内容逻辑几乎没有性能优化的空间,因此我们大可不必拿前面分析模板 API 时的性能来说事儿。而不妨再深入想一下,我们提到的“框架层面有更多优化空间”,它的源头又何在?答案便呼之欲出:对于元(meta)信息的分析能力。

正如我们说 jsx 的逻辑几乎不存在可优化性,在于其逻辑存在在 plain JavaScript 中,对于框架来说,只能在运行时拿到该逻辑的执行结果,而不是该逻辑本身。什么意思呢?假设这样一份 UI 逻辑:遍历数组 arr,对于每项渲染一个 <li>;那么 vue 从模板中拿到的信息就是这份逻辑:遍历数组 arr,对于其中每项渲染一个 <li>,而 react 从 jsx 中能拿到的信息是:n<li>,而无从知晓这 n<li> 缘何而来。说这个是元信息就是因为这个本不是运行时代码正常操作的对象,它是描述逻辑本身的信息;那 jsx 的形式有没有可能拿到元信息呢?理论上也有:改造 compiler,将 AST 相关(甚至更高级的)信息注入到运行时去消费,再加点模式匹配在里边,操作起来不太有工程上的可行性就是了。

再回到前面说到的 vue 使用 Function-based API,它失去了什么;即,失去关于 setup 内容的元信息后,会有什么样的后果,有一个点 RFC 里自己也提到了:Runtime Reflection of Components,这个很好理解,反射本来就是用来在运行时获取元信息的。至于失去这一能力,是不是确如他在 RFC 里提到的那样,没有坏处,却有好处呢?