文末附录中有与 React Hooks 的一些细节对比。
类型推导
3.0 的一个主要设计目标是增强对 TypeScript 的支持。原本我们期望通过 Class API 来达成这个目标,但是经过讨论和原型开发,我们认为 Class 并不是解决这个问题的正确路线,基于 Class 的 API 依然存在类型问题。
基于函数的 API 天然对类型推导很友好,因为 TS 对函数的参数、返回值和泛型的支持已经非常完备。更值得一提的是基于函数的 API 在使用 TS 或是原生 JS 时写出来的代码几乎是完全一样的。下文会提供新 API 类型推导的更多细节,此外文末附录中有关于 Class API 类型问题的更多细节。
打包尺寸
基于函数的 API 每一个函数都可以作为 named ES export 被单独引入,这使得它们对 tree-shaking 非常友好。没有被使用的 API 的相关代码可以在最终打包时被移除。同时,基于函数 API 所写的代码也有更好的压缩效率,因为所有的函数名和 setup 函数体内部的变量名都可以被压缩,但对象和 class 的属性/方法名却不可以。
[h1]设计细节[/h1]setup() 函数
我们将会引入一个新的组件选项,
新的 API 使得动态地检视/修改一个组件的选项变得更困难(原来是一个对象,现在是一段无法被检视的函数体)。
这可能是一件好事,因为通常在用户代码中动态地检视/修改组件是一类比较危险的操作,对于运行时也增加了许多潜在的边缘情况(特别是组件继承和使用 mixin 的情况下)。新 API 的灵活性应该在绝大部分情况下都可以用更显式的代码达成同样的结果。
缺乏经验的用户可能会写出 “面条代码”,因为新 API 不像旧 API 那样强制将组件代码基于选项切分开来。
我们在 Class API RFC 和内部讨论中听到过好几次这样的声音,但我认为这是一种没有必要的担忧。虽然理论上新的 API 确实制约更少,但我认为 “面条代码” 的情况不太可能发生,这里详细解释一下。
基于函数的新 API 和基于选项的旧 API 之间的最大区别,就是新 API 让抽取逻辑变得非常简单 —— 就跟在普通的代码中抽取函数一样。也就是说,我们不必只在需要复用逻辑的时候才抽取函数,也可以单纯为了更好地组织代码去抽取函数。
基于选项的代码只是看上去更整洁。一个复杂的组件往往需要同时处理多个不同的逻辑任务,每个逻辑任务所涉及的代码在选项 API 下是被分散在多个选项之中的。举例来说,从服务端抓取一份数据,可能需要用到
props
复制代码
,
data()
复制代码
,
mounted
复制代码
和
watch
复制代码
。极端情况下,如果我们把一个应用中所有的逻辑任务都放在一个组件里,这个组件必然会变得庞大而难以维护,因为每个逻辑任务的代码都被选项切成了多个碎片分散在各处。对比之下,基于函数的 API 让我们可以把每个逻辑任务的代码都整理到一个对应的函数中。当我们发现一个组件变得过大时,我们会将它切分成多个更小的组件;同样地,如果一个组件的
setup()
复制代码
函数变得很复杂,我们可以将它切分成多个更小的函数。而如果是基于选项,则无法做到这样的切分,因为用 mixin 只会让事情变得更糟糕。
从这个角度看,基于选项 vs. 基于函数就好像基于 HTML/CSS/JS 组织代码 vs. 基于单文件组件来组织代码。
[h1]升级策略[/h1]新的 API 和 2.x 的 API 理论上完全兼容(只是多了一个
setup()
复制代码
选项) 。但是,新 API 的引入实际上会让相当一部分的旧选项长远来说变得没有必要。如果能够去掉对这些旧选项的支持,可以获得相当的代码尺寸和性能提升。
因此,3.0 我们计划提供两个不同的版本:
Class API 的类型问题
Class API 提案的主要目的是寻找一个能够提供更好的 TypeScript 支持的组件声明方式。但是由于 Vue 需要将来自多个选项的属性混合到同一个渲染上下文上,这使得即使用了 Class,要得到良好的类型推导也不是很容易。
以 props 的类型推导为例。要将 props 的类型 merge 到 class 的
this
复制代码
上,我们有两个选择:用 class 的泛型参数,或是用 decorator。
这是用泛型参数的例子: