TypeScript有能力将类定义为抽象的。这意味着它们不能被直接实例化;只有非抽象的子类才可以。让我们来看看当涉及到构造函数的使用时这意味着什么。
制作一个抓取板
为了深入研究这个问题,让我们创建一个scratchpad项目来工作。我们将创建一个Node.js项目,并将TypeScript作为一个依赖项安装。
mkdir ts-abstract-constructors
cd ts-abstract-constructors
npm init --yes
npm install typescript @types/node --save-dev
现在我们已经建立了一个package.json 文件。我们也需要初始化一个TypeScript项目。
npx tsc --init
这将给我们一个tsconfig.json 文件,它将驱动TypeScript的配置。默认情况下,TypeScript会转译成比类更早的JavaScript版本。因此,我们将更新配置,使之以包括类的较新版本的语言为目标。
"target": "es2020",
"lib": ["es2020"],
让我们创建一个名为index.ts 的TypeScript文件。这个名字并不重要;我们只是需要一个文件来开发。
最后,我们将在我们的package.json ,添加一个脚本,将我们的TypeScript编译成JavaScript,然后用node运行JS。
"start": "tsc --project \".\" && node index.js"
在TypeScript中制作一个抽象类
现在我们准备好了。让我们在我们的index.ts 文件中添加一个带有构造函数的抽象类。
abstract class ViewModel {
id: string;
constructor(id: string) {
this.id = id;
}
}
考虑一下上面的ViewModel 类。假设我们正在构建某种CRUD应用程序;我们将有不同的视图。每个视图都会有一个相应的viewmodel ,它是ViewModel 抽象类的一个子类。
ViewModel 类在构造函数中有一个强制性的id 参数。这是为了确保每个视图模型都有一个id 值。如果这是一个真正的应用程序,id 可能是一个实体在某种数据库中被查询的值。
重要的是,ViewModel 的所有子类应该是。
- 完全不实现构造函数,让基类的构造函数成为子类的默认构造函数
- 实现他们自己的构造函数,调用
ViewModel基类的构造函数。
带着我们的抽象类去兜风
现在让我们看看我们能用我们的抽象类做什么。
首先,我们可以实例化我们的抽象类吗?我们不应该这样做。
const viewModel = new ViewModel('my-id');
console.log(`the id is: ${viewModel.id}`);
果然,运行npm start ,出现了以下错误(我们的编辑器VS Code也报告了这一错误)。
index.ts:9:19 - error TS2511: Cannot create an instance of an abstract class.
const viewModel = new ViewModel('my-id');
巨大的。然而,值得记住的是,abstract 是一个 TypeScript 的概念。当我们编译我们的TS时,虽然它抛出了一个编译错误,但它仍然转译出一个看起来像这样的index.js 文件。
"use strict";
class ViewModel {
constructor(id) {
this.id = id;
}
}
const viewModel = new ViewModel('my-id');
console.log(`the id is: ${viewModel.id}`);
我们可以看到,没有提到abstract ;它只是一个直接的class 。事实上,如果我们直接用node index.js 来执行这个文件,我们可以看到一个输出:。
the id is: my-id
所以,即使源代码不是有效的TypeScript,转译后的代码也是有效的JavaScript。这一切都提醒我们,abstract 是一个TypeScript构造。
没有新构造函数的子类化
现在让我们创建ViewModel 的第一个子类,并尝试将其实例化。
class NoNewConstructorViewModel extends ViewModel {
}
// error TS2554: Expected 1 arguments, but got 0.
const viewModel1 = new NoNewConstructorViewModel();
const viewModel2 = new NoNewConstructorViewModel('my-id');
正如TypeScript编译器告诉我们的,第二个实例是合法的,因为它依赖于基类的构造函数。第一个则不是,因为没有无参数的构造函数。
使用新的构造函数进行子类化
做完这些后,让我们尝试子类化并实现一个有两个参数的新构造函数(以区别于我们正在重载的构造函数)。
class NewConstructorViewModel extends ViewModel {
data: string;
constructor(id: string, data: string) {
super(id);
this.data = data;
}
}
// error TS2554: Expected 2 arguments, but got 0.
const viewModel3 = new NewConstructorViewModel();
// error TS2554: Expected 2 arguments, but got 1.
const viewModel4 = new NewConstructorViewModel('my-id');
const viewModel5 = new NewConstructorViewModel('my-id', 'important info');
同样,在尝试的实例中只有一个是合法的。viewModel3 不是,因为没有无参数的构造函数。viewModel4 不是,因为我们用新的构造函数覆盖了基类的构造函数,它有两个参数。因此,viewModel5 是我们的 "Goldilocks "实例化--它恰到好处。
同样值得注意的是,我们在NewConstructorViewModel 构造函数中调用super 。这调用了ViewModel 基(或 "超")类的构造函数。TypeScript强制要求我们传递适当的参数(在我们的例子中是一个单一的string )。
总结
我们已经看到,当我们有一个抽象类时,TypeScript确保构造函数的正确使用。重要的是,所有抽象类的子类要么。
- 完全不实现构造函数,让基类的构造函数(抽象构造函数)成为子类的默认构造函数
- 实现自己的构造函数,用正确的参数调用基类(或 "超")的构造函数。
The postTypeScript, abstract classes, and constructorsappeared first onLogRocket Blog.