typescript 混入
One of TypeScript's main selling points is that it allows developers to use something like classes and interfaces to write a fundamentally prototype-based language.
TypeScript的主要卖点之一是,它允许开发人员使用类和接口之类的东西来编写基本基于原型的语言。
I say something like, because TypeScript cannot, and does not, add true class-oriented capabilities to the language, which can make for some surprising gotchas. Still, its remarkably clean syntax does a good job of cleaning up the visual cacophany of working with prototypes.
我之所以说类似的东西 ,是因为TypeScript不能也不会为该语言添加真正的面向类的功能,这可能会带来一些令人惊讶的陷阱。 尽管如此,其非常清晰的语法仍然可以很好地消除使用原型的视觉隐患。
After this article, you'll be able to:
在这篇文章之后,您将能够:
- Use classes to easily create similar objects; 使用类轻松创建相似的对象;
- Use interfaces to describe and enforce the shape and behavior of different classes; and 使用接口来描述和执行不同类的形状和行为; 和
- Combine classes and interfaces to create robust, self-documentining components. 组合类和接口以创建健壮的自记录组件。
I assume you're using Node to run the samples; I'm running v6.0.0.
我假设您正在使用Node运行示例; 我正在运行v6.0.0。
I've uploaded all of the code for this article to my repository on GitHub. If you don't want to type, clone it down, and run:
我已将本文的所有代码上载到GitHub上的存储库中 。 如果不想输入,请克隆下来并运行:
git checkout Part_2A-Using_Classes_and_Interfaces
git checkout Part_2A-Using_Classes_and_Interfaces
If you're on Windows, you should be able to just run tsc
. You'll get a frightening bundle of errors, but you can safely ignore them.
如果您使用的是Windows,则应该只需运行tsc
。 您会收到一系列令人恐惧的错误,但可以放心地忽略它们。
If you're on Mac or *nix, you'll still run tsc
, but you can also pipe the errors to nowhere if you'd like:
如果您使用的是Mac或* nix,仍然可以运行tsc
,但是如果您愿意,也可以将错误传递到任何地方:
# Compile all TS files, and silence irrelevant errors.
tsc --target ES5 > /dev/null 2>&1
Either way, you should end up with a folder called built
, which contains the compiled JavaScript.
无论哪种方式,您都应该以一个名为built
的文件夹结尾,该文件夹包含已编译JavaScript。
使用课程 ( Using Classes )
The syntax for TypeScript's classes is almost identical to that of JavaScript's native classes, which we'll take as our starting point.
TypeScript类的语法几乎与JavaScript本机类的语法相同,我们将以此为起点。
"use strict";
// ListComponent maintains a list of things that a user can
// add to and delete from. This is a pure JavaScript class.
class ListComponent {
constructor () {
this.things = [];
}
}
// A ListItem is a wrapper around the items in our list.
// This is also pure JavaScript, for now.
class ListItem {
constructor (name) {
this.name = name;
}
}
These are perfectly valid JavaScript classes, but ut you'll get errors if you run them through the TypeScript transpiler:
这些是完全有效JavaScript类,但是如果通过TypeScript编译器运行它们,则会出错:
![Property does not exist errors](https://i.imgsafe.org/655cdb1.png)
This is one of the few places where the TypeScript compiler will reject raw JavaScript. Let's find out what's wrong, and fix it.
这是TypeScript编译器拒绝原始JavaScript的少数几个地方之一。 让我们找出问题所在并加以解决。
类属性 (Class Properties)
In JavaScript, classes can only contain method definitions. Adding properties is a syntax error.
在JavaScript中,类只能包含方法定义。 添加属性是语法错误。
In TypeScript, you must add instance properties if you assign to them using this
. Otherwise, your code won't compile.
在TypeScript中,如果使用this
分配实例属性,则必须添加实例属性。 否则,您的代码将无法编译。
// list_classes.ts
"use strict";
class ListComponent {
// Adding a property is as simpleas adding its name and type
// at the top of the class.
things : Array<ListItem>; // Or -- things : ListItem[];
constructor () {
this.things = [];
}
}
class ListItem {
name : string;
constructor (name) {
this.name = name;
}
}
You'll need to add a property for every instance variable you plan to access with this
.
您需要为计划使用this
访问的每个实例变量添加一个属性。
Create a new ListItem
, and print its name:
创建一个新的ListItem
,并打印其名称:
const item = new ListItem('Thing to Do');
console.log(item.name); // 'Thing to Do'
. . . And that does away with the first of our errors.
。 。 。 这消除了我们的第一个错误。
访问修饰符 (Access Modifiers)
We could also have declared our instance variables with the public
keyword:
我们还可以使用public
关键字声明实例变量:
// list_classes_public.ts
"use strict";
class ListComponent {
public things : Array<ListItem>; // Or -- things : ListItem[];
constructor () {
this.things = [];
}
}
class ListItem {
public name : string;
constructor (name) {
this.name = name;
}
}
The public
keyword is called an access modifier, and tells the compiler how much of the code should be able to use the property it prefixes.
public
关键字称为access修饰符 ,它告诉编译器应该有多少代码可以使用其前缀的属性。
TypeScript provides two access modifiers:
TypeScript提供了两个访问修饰符:
public
. You can read or write public properties from anywhere. These behave identically to object properties in vanilla JavaScript.public
。 您可以从任何地方读取或写入公共属性。 它们的行为与原始JavaScript中的对象属性相同。private
. You can only read or write a private property from inside of the class that "owns" the property.private
。 您只能从“拥有”该属性的类的内部读取或写入私有属性。
In other words, you can use private properties to implement methods inside of your class, but you can't touch them from outside.
换句话说,您可以使用私有属性在类内部实现方法,但不能从外部接触它们。
Private properties help improve consistency and code safety, because the only place you can use them is from within the class. Outsiders can't muck with your data from elsewhere.
私有属性有助于提高一致性和代码安全性,因为您只能在类内部使用它们。 局外人无法从其他地方获取您的数据。
Let's see what happens if we declare our things
and name
properties private
.
让我们看看如果我们声明things
并name
属性为private
会发生什么。
// list_classes_private.ts
"use strict";
// ListComponent maintains a list of things that a user can
// add to and delete from. This is a pure JavaScript class.
class ListComponent {
private things : Array<ListItem>;
constructor () {
this.things = [];
}
}
// A ListItem is a wrapper around the items in our list.
// This is also pure JavaScript, for now.
class ListItem {
private name : string;
constructor (name) {
this.name = name;
}
}
const item = new ListItem('Thing to Do');
console.log(item.name); // Property 'name' is private
The classes are still valid, but you'll notice that the last line causes a compiler error. TypeScript reports: Property 'name' is private and only accessible within the class 'ListItem'.
这些类仍然有效,但是您会注意到最后一行会导致编译器错误。 TypeScript报告: 属性“名称”是私有的,只能在类“ ListItem”中访问。
The most idiomatic way to fix this would be to provide a getter.
解决此问题的最惯用的方法是提供吸气剂 。
"use strict";
class ListComponent {
// We have to rename the private property so we can name
// our "get" method "things".
private _things : Array<ListItem>;
constructor () {
this._things = [];
}
// Adds a "getter", which returns the private property.
get things () : Array<ListItem> { return this._things; }
}
class ListItem {
private _name : string;
constructor (name) {
this._name = name;
}
get name () : string { return this._name; }
}
const item = new ListItem('Thing to Do');
console.log(item.name); // 'Thing to Do'
If you're not familiar with get
methods, check the documentation. Conceptually, they're just functions that JavaScript calls when you access a property with the same name as the get method.
如果您不熟悉get
方法,请查看文档。 从概念上讲,它们只是当您访问与get方法同名的属性时JavaScript调用的函数。
There are two things to notice.
有两件事要注意。
- We've renamed our
things
property to_things
, and ourname
property to_name
; and 我们已将things
属性重命名为_things
,并将name
属性重name
为_name
; 和 - We've added methods called
get things
andget name
. 我们添加了名为get things
和get name
。
We've renamed our properties from thing
and name
to _thing
and _name
so we can name our get methods sensibly. There's nothing special about the underscore prefix -- you could have renamed the private properties list_of_things
and silicon_unicorn
, if you'd wanted -- but using an underscore-prefixed identifier for private members and exposing them via an unprefixed getter is a convenient convention.
我们已经将属性从thing
和name
为_thing
和_name
以便我们可以合理地命名get方法。 下划线前缀没有什么特别的-如果愿意,您可以重命名私有属性list_of_things
和silicon_unicorn
但是使用私有成员使用带下划线前缀的标识符并通过无前缀的getter公开它们是一个方便的约定。
Renaming the properties lets us preserve our public interface: The rest of our code doesn't even know we've started using getters under the hood.
重命名属性使我们可以保留我们的公共接口:其余代码甚至都不知道我们已经开始在幕后使用getter。
If you're wondering why we'd go through all this trouble in the first place, try setting the value of item.name
:
如果您想知道为什么我们首先要解决所有这些麻烦,请尝试设置item.name
的值:
// list_item_private_getter.ts
item.name = 'Thing Done';
console.log(item.name); // TypeError: Cannot set property name of #<ListItem> which only has a getter
You'll notice that the TypeScript compiles just fine, but that JavaScript throws at runtime when you try to change the name
property.
您会注意到TypeScript可以很好地进行编译,但是当您尝试更改name
属性时,JavaScript会在运行时抛出。
This is the main advantage to exposing private properties via getters: It prevents them from being set by clients.
这是通过getter公开私有属性的主要优点: 防止由client设置私有属性。
You can also define a set
method, which allows you to change the name of item
as we just attempted. This sometimes makes sense. If you're going to do it that way, though, you can usually get away with avoiding getters/setters altogether, and just declare your properties public.
您还可以定义一个set
方法,该方法允许您像我们刚尝试的那样更改item
的名称。 有时候这很有意义。 但是,如果要这样做,通常可以完全避免使用getter / setter方法,而只需将属性声明为公共即可。
As a general rule, declaring variables private and exposing them via a getter, but providing no setter, is a good pattern. If you need a setter, provide one. But don't do so unless you have a very good reason: You should generally keep your objects as close to immutable as possible.
作为一般规则,声明变量私有,并通过一个getter暴露他们,但不提供二传手,是一个很好的模式。 如果您需要二传手,请提供一个。 但是,除非您有充分的理由,否则请不要这样做:通常应使对象尽可能接近不变。
用私有方法隐藏实现 (Hiding Implementations with Private Methods)
TypeScript also allows you to declare private methods.
TypeScript还允许您声明私有方法。
Let's say we want to validate a user before s/he can add items to a list, but we don't want the validation method available outside the class. Easy: We'd declare it private
, which will produce compile-time warnings if we try to access it directly.
假设我们要先验证用户,然后才能将其添加到列表中,但是我们不希望该验证方法在类外可用。 容易:我们将其声明为private
,如果尝试直接访问它,则会产生编译时警告。
// list_classes_private_methods.ts
"use strict";
class ListComponent {
private _things : Array<ListItem>;
constructor () {
this._things = [];
}
get things () : Array<ListItem> { return this._things; }
get length () : number { return this._things.length; }
add (item : ListItem, password : string) : boolean {
if (this.validate(password)) {
this._things.push(item);
return true;
} else
return false
}
private validate (password : string) : boolean {
if (password === '12345')
return true
else
return false
}
}
class ListItem {
private _name : string;
constructor (name) {
this._name = name;
}
get name () : string { return this._name; }
}
const PASS = '12345';
const item = new ListItem('Thing to Do');
const list_one = new ListComponent();
const list_two = new ListComponent();
console.log(list_one.add(item, PASS)); // true
console.log(list_one.length); // 1
console.log(list_two.add(item, 'WRONG password')); // false
console.log(list_two.length); // 0
// console.log(list_one.validate('12345')); // Compiler error
If you try to run list_one.validate('12345')
, you'll get a compiler error.
如果尝试运行list_one.validate('12345')
,则会出现编译器错误。
子类 (Subclasses)
TypeScript also lets you use subclasses, just as does vanilla JavaScript:
TypeScript还允许您使用子类,就像原始JavaScript一样:
"use strict";
class CompletedListItem extends ListItem {
completed : boolean;
constructor (name : string) {
super(name);
this.completed = true;
}
}
There isn't much more to say about subclasses in TypeScript. For details as to how it all works under the hood, check out my articles on JavaScript's classes; and for some thoughts on designing with subclasses, read the article accompanying this one, on Designing with Classes & Interfaces.
关于TypeScript中的子类,您无需多说。 有关幕后工作原理的详细信息,请查看我关于JavaScript类的文章; 并对有关使用子类进行设计的一些想法,请阅读与此文章相关的文章“ 使用类和接口进行设计” 。
物业简写 (Property Shorthand)
Finally, TypeScript offers a shorthand for creating and assigning properties.
最后,TypeScript提供了用于创建和分配属性的快捷方式。
"use strict";
class Person {
constructor (public first_name : string, public last_name : string) { }
}
This code is equivalent to:
此代码等效于:
"use strict";
class Person {
public first_name : string;
public last_name : string;
constructor (first_name : string, last_name : last_name) { }
}
Using this shorthand, we can rewrite our ListComponent
like this:
使用此速记,我们可以像这样重写ListComponent
:
// list_classes_shorthand.ts
"use strict";
class ListComponent {
constructor (private _things : ListItem[]) { }
get things () : ListItem[] { return this._things; }
}
class ListItem {
constructor (private _name) { }
get name () : string { return this._name; }
}
const item = new ListItem('Thing to Do');
console.log(item.name); // Property 'name' is private
item.name = 'Fish';
console.log(item.name); //
告诫 (A Word of Caution)
TypeScript's private
access modifier is handy, but it is just syntactical sugar. Its compile-time checks against your code do not prevent other code, or even your code, from accessing private properties at runtime.
TypeScript的private
访问修饰符很方便,但这只是语法糖。 它针对您的代码进行编译时检查不会阻止其他代码,甚至您的代码在运行时访问私有属性。
As an example, while list_one.validate('12345')
generates a compiler error, list_one['validate']('12345')
does not. That'll actually work just fine.
作为一个例子,而list_one.validate('12345')
生成编译器错误, list_one['validate']('12345')
没有 。 这实际上会很好。
When you use bracket notaton, JavaScript first evaluates the expression inside of the brackets, and then attempts to access the property on the object with the name of the result. In contrast, TypeScript can analyze direct property access, as in list_one.validate
, without the intermediate step of evaluating an expression.
当您使用方括号注解时,JavaScript首先会评估方括号内的表达式,然后尝试使用结果名称访问对象上的属性。 相比之下,TypeScript可以像list_one.validate
那样分析直接属性访问,而无需评估表达式的中间步骤。
It's just a guess, but I reckon the transpiler doesn't guard against such evaluated property accesses because it would be unreliable: In general, the value of an expression is constrained by its runtime context, which can't be fully determined at compile-time.
这只是一个猜测,但我认为转译器不会防范这种评估的属性访问,因为这将是不可靠的:通常,表达式的值受其运行时上下文约束,在编译时无法完全确定该表达式的值。时间。
The takeaway is twofold:
要点是双重的:
- While the private modifier is handy, it can't guarantee privacy, and you shoudn't rely on it to do so; and 尽管private修饰符很方便,但是它不能保证隐私,因此您不应该依赖它。 和
- TypeScript's OOP sugar is just sugar. You can still add and delete methods, see and access
private
properties, and do other such blasphemy at runtime, if you're clever enough. 打字稿的OOP糖是糖刚 。 如果足够聪明,您仍然可以添加和删除方法,查看和访问private
属性,以及在运行时进行其他此类亵渎行为。
If you absolutely need private data in your classes, you may be better off using closures, or using one of the several techniques Dr Rauschmayer discusses in Exploring ES6.
如果您在班级中绝对需要私有数据,则最好使用闭包,或者使用Rauschmayer博士在“探索ES6”中讨论的几种技术之一 。
使用介面 ( Using Interfaces )
Classes collect a set of properties and methods into a blueprint you to create objects that differ in state, but otherwise share the same capabilities and shape.
类将一组属性和方法收集到一个蓝图中,以创建状态不同但对象具有相同功能和形状的对象。
Interfaces allow you to collect a set of properties and behaviors into a blueprint that classes can promise they'll implement, but do not define those properties, nor provide definitions for the methods.
接口允许您将一组属性和行为收集到一个蓝图中,类可以保证它们将实现它们,但不定义这些属性,也不提供方法的定义。
TypeScript uses interfaces for two things: To guarantee that objects created from an implementing class will have a certain shape, or that they'll expose a certain API -- or list of public methods. Each implementing class gets to decide how it will implement that API.
TypeScript使用接口做两件事:确保从实现类创建的对象具有某种形状 ,或者它们将公开某种API或公共方法列表。 每个实现类都将决定如何实现该API。
That's all a little abstract, so let's frame it with an example.
有点抽象,所以让我们用一个例子来构架。
加强形状 (Enforcing Shape)
The shape of an object is the set of data that it contains. To expect an object to have a certain shape is to expect it to hold certain data. We expect a User object to have a name
and an email
, for example. These two properties are part of the User object's shape.
对象的形状是它包含的数据集。 期望对象具有某种形状就是期望它保存某些数据。 例如,我们希望User对象具有name
和email
。 这两个属性是用户对象形状的一部分。
An interface is a tool for enforcing a particular shape. If a class implements an interface, it must contain the instance variables and methods listed in the interface.
界面是用于强制执行特定形状的工具。 如果一个类实现了一个接口,则它必须包含该接口中列出的实例变量和方法。
Creating an interface is much the same as creating a class, except you use the interface
keyword instead of class
. Otherwise, you list properties and their types identically:
创建接口与创建类几乎相同,除了使用interface
关键字而不是class
。 否则,您将相同地列出属性及其类型:
// interfaces.ts
"use strict";
interface User {
name : string;
email : string;
}
Any object that implements the User
interface must have a name
and email
property, or the TypeScript compiler will scream at us with an "Interface not implemented properly" error.
任何实现User
界面的对象都必须具有name
和email
属性,否则TypeScript编译器将向我们大喊“接口未正确实现”错误。
Using an interface is like creating a subclass, but you use implements
in place of extends
.
使用接口就像创建一个子类,但是您可以使用implements
来代替extends
。
// interfaces.ts
class RegisteredUser implements User {
// Shorthand
constructor (public name : string, public email : string) { }
}
We can also specify certain properties as optional. If we want to add a avatar
property but acknowledge that users don't have to have a picture, you mark the property with a question mark.
我们还可以将某些属性指定为可选。 如果我们要添加一个avatar
属性,但承认,用户不必有一个图片,您标记带问号的属性。
// interfaces_optional.ts
"use strict";
interface User {
name : string;
email : string;
avatar? : Object;
}
class RegisteredUser implements User {
constructor (public name : string, public email : string) { }
}
class ImageUser implements User {
constructor (public name : string,
public email : string,
public avatar : Object) { }
}
方法签名 (Method Signatures)
Interfaces also allow you to specify methods that a class must implement, while allowing the classes themselves decide on the implementation details.
接口也允许你指定的方法 ,一个类必须实现,同时允许他们自己决定的实施细则中的类。
Including method signatures in an interface is as simple as adding a function signature to the interface.
在接口中包含方法签名就像向接口添加函数签名一样简单。
// interfaces_methods.ts
"use strict";
interface User {
// PROPERTIES
name : string;
email : string;
avatar? : Object;
// API
print () : void;
}
class RegisteredUser implements User {
constructor (public name : string, public email : string) { }
print () : void {
console.log(`Name: ${this.name} | Email: ${this.email} | No avatar.`);
}
}
class ImageUser implements User {
constructor (public name : string,
public email : string,
public avatar : Object) { }
print () : void {
console.log(`Name: ${this.name} | Email: ${this.email} | Has avatar.`);
}
}
const user = new RegisteredUser('Peleke', 'resil.design@gmail.com');
user.print();
Three things to notice.
需要注意的三件事。
- If you omit
print
in either class, you'll get a compiler error; 如果您在任何一个类中都省略了print
,则会出现编译器错误; - If you declare
print
with the wrong type in either class, you'll get a compiler error; and 如果在任何一个类中声明类型错误的print
,都会出现编译器错误。 和 - Each class implements
print
differently. 每个类实现print
不同 。
This last point is important. Different classes will often expose the same abstract behavior -- User
s of all types should be able to print
themselves for instance -- but, in general, they might want to do things differently.
最后一点很重要。 不同的类通常会公开相同的抽象行为-例如,所有类型的User
都应能够print
自己-但通常,他们可能希望以不同的方式做事。
Interfaces describe such behavior in the abstract, but allow their implementing classes to define that behavior as they see fit.
接口以抽象的方式描述了这种行为,但是允许其实现类定义它们认为合适的行为。
使用类创建Mixin ( Using Classes to Create Mixins )
Occasionally, we want classes to expose some API that would be best expressed in terms of an interface -- say, Printable
, or Identifiable
-- but which they'll all implement in the same way.
有时,我们希望类公开一些API,这些API最好用接口来表示-例如Printable
或Identifiable
但它们都将以相同的方式实现。
One possibility is to create subclasses, but this approach fails on two major fronts.
一种可能性是创建子类,但是这种方法在两个主要方面都失败了。
- Classes can only inherit from a single parent class. If we need to implement multiple sets of behavior -- both
Printable
andIdentifiable
, for instance -- then subclassing doesn't work. 类只能从单个父类继承。 如果我们需要实现的行为多套- 无论是Printable
和Identifiable
,例如-然后继承不起作用。 - If the objects that need to implement the interfaces aren't closely related, subclassing is the wrong abstraction. To have a
Book
andPerson
both inherit from anIdentifiable
base class because they both have an ID makes no sense from the standpoint of building a class hierarchy. 如果需要实现接口的对象不是紧密相关的,则子类化是错误的抽象。 让Book
和Person
都从可Identifiable
基类继承,因为从建立类层次结构的角度来看,它们都具有ID。
The correct abstraction in this case is the interface. But writing the same implementation in each class that implements it is suboptimal. On
在这种情况下,正确的抽象是接口。 但是,在实现它的每个类中编写相同的实现都不理想。 上
one hand, it's more typing. And typing sucks. More seriously, writing the same code in several places is brittle, and if you ever need to change what you intend to be a common implementation, you'll have to rewrite the code in every class that implements the interface.
一方面,它更具打字性。 而且打字很烂。 更严重的是,在多个位置编写相同的代码很容易,如果您需要更改打算成为通用实现的内容,则必须在实现该接口的每个类中重写代码。
What we'd want is a way to create an interface that allows us to include implementations. Languages like Groovy solve the problem directly with traits -- in effect, interfaces that can host default method implementations.
我们想要的是一种创建允许我们包含实现的接口的方法。 诸如Groovy之类的语言直接通过特征来解决问题-实际上是可以承载默认方法实现的接口。
JavaScript doesn't directly support traits,but has made use of the mixin pattern for quite some time to achieve the same ends. TypeScript provides a little sugar to create mixins by using classes with the implements keyword.
JavaScript不直接支持特征,但是已经使用了mixin模式相当长的时间来达到相同的目的。 TypeScript通过将类与Implements关键字一起使用提供了一些糖来创建mixin。
局部类 (Partial Classes)
Creating a class to use as a mixin is almost identical to creating an interface. There are two differences:
创建用作mixin的类几乎与创建接口相同。 有两个区别:
- You use the
class
keyword, instead ofinterface
; and 您使用class
关键字,而不是interface
; 和 - You include method definitions. 您包括方法定义。
// mixins.ts
"use strict";
class Printable {
text : string;
print () : void {
console.log(this.text);
}
}
class Identifiable {
name : string;
id : number;
identify () : void {
console.log(`My name is ${this.name}, and my ID is ${this.id}`);
}
}
class Book implements Printable, Identifiable { // LINE A
name : string;
id : number;
text : string;
constructor (name : string, id : number, text : string) {
this.name = name;
this.id = id;
this.text = text;
}
// DUMMY methods to satisfy the compiler. If we don't have these, TS
// will complain with "Interface not implemented properly".
// Printable
print () : void { }
// Identifiable
identify () : void { }
}
applyMixins(Book, [Printable, Identifiable]);
const bad_novel = new Book('A Buncha Nonsense', 2190, 'This book/Is awful. FIN');
bad_novel.print();
// NECESSARY EVIL ::
// From TS docs :: http://www.typescriptlang.org/docs/handbook/mixins.html
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
Notice that these "mixin classes" do not contain constructors. That means you can't instantiate them. Instead, you'll use them purely to collect method definitions and property signatures.
请注意,这些“ mixin类” 不包含构造函数。 这意味着您无法实例化它们。 相反,您将仅使用它们来收集方法定义和属性签名。
Note Line A. The reason we can get away with something as weird as implementing a class is because TypeScript treats classes that you implement as interfaces. In other words, it checks that the implementing class contains the same method signatures and instance variables as the "class" you're implementing, but ignores method implementations. We'll see how to get them back in a minute.
注意行A。 我们之所以可以避免像实现类这样奇怪的事情,是因为TypeScript将您实现的类视为接口。 换句话说,它检查实现类是否包含与要实现的“类”相同的方法签名和实例变量,但忽略方法实现。 我们将在一分钟内看到如何让他们回来。
Once you've defined your mixin classes, you create your extending class using implements
. And, just as you do when implementing a normal interface, you provide an implementation for the methods that it exposes.
定义mixin类后,即可使用implements
创建扩展类。 并且,就像实现普通接口时一样,您还为其公开的方法提供了实现。
The only difference is that, here, we implement a no-op, dummy implementation. This is for two reasons. If we don't do this, the compiler will complain that our class doesn't implement its interfaces properly.
唯一的区别是,这里我们实现了无操作的虚拟实现。 这有两个原因。 如果我们不这样做,编译器会抱怨我们的类没有正确实现其接口。
Of course, we don't want the no-op. We want the method definition inside the partial class. To apply this, we have to use a helper function provided in the TypeScript documentation: applyMixins
. It replaces your dummy implementations with the real ones in your partial classes. You'll have to use applyMixins
for every mixin class you create.
当然,我们不要无人驾驶。 我们希望在部分类中定义方法。 要应用此功能,我们必须使用TypeScript文档中提供的帮助函数: applyMixins
。 它在部分类中用真实的实现替换了虚拟的实现。 您必须为创建的每个mixin类使用applyMixins
。
If you decide to use mixins, I recommend collecting all the applyMixins
calls into their own module, or at least into their own chunk of the program. That way you can keep track of where the magic happens.
如果您决定使用mixin,我建议将所有applyMixins
调用收集到它们自己的模块中,或者至少收集到它们自己的程序块中。 这样,您可以跟踪魔术发生的位置。
Frankly, I find all this to be more trouble than it's worth. But you'll see this pattern in the wild, whether you use it or not, so it's important to know that it's available.
坦白说,我发现所有这一切都比其价值更大。 但是,无论是否使用它,您都会在野外看到这种模式,因此了解它的可用性很重要。
结论 ( Conclusion )
In this article, you've seen how to:
在本文中,您已经了解了如何:
- Build classes with access modifiers; 用访问修饰符构建类;
- Use interfaces to define the "shape" of a class's data; 使用接口定义类数据的“形状”;
- Use interfaces for modular development of a class's API; and 使用接口对类的API进行模块化开发; 和
- Create mixins using partial classes as interfaces. 使用部分类作为接口创建mixin。
As you can imagine, TypeScript's classes, interfaces, and mixins are powerful tools for defining complex behaviors and hierarchies. If you'd like to take a deeper dive into the details of how classes work under the hood, read up on "Class" & Prototypes.
您可以想象,TypeScript的类,接口和混合插件是用于定义复杂行为和层次结构的强大工具。 如果您想更深入地了解类的工作原理,请阅读“类”和“原型” 。
In the companion to this article, Part IIB, we'll take a look at some terminology and desgn principles from the world of OOP; and after that, we'll dive into TypeScript's Types & Generics.
在本文的伴随部分IIB中,我们将研究OOP领域的一些术语和设计原则。 然后,我们将深入研究TypeScript的类型和泛型。
As always, feel free to leave questions in the comments, or shoot them to me on Twitter (@PelekeS).
与往常一样,随时在评论中留下问题,或在Twitter( @PelekeS )上向我射击。
翻译自: https://scotch.io/tutorials/from-javascript-to-typescript-pt-iia-using-classes-interfaces-mixins
typescript 混入