Angularではアプリケーションを構築する際に使用する言語としてTypeScriptが主流となっています。そしてAngularでは至る所でTypeScriptのclassを使うことが求められます。ES2015にもclassがありますが、実際にTypeScriptでclassを書いてみると、ES2015のclassの感覚では書けないことがわかりました。C#やJavaのclassに近いという話ではありますが、念のためTypeScriptのclassがどんなものか把握するために、TypeScriptの公式ドキュメントの「classの部分(Classes・TypeScript)」をしっかり読んでみようと思い、今回日本語に翻訳してみました。
目次
Introduction: 始めに
Traditional JavaScript uses functions and prototype-based inheritance to build up reusable components, but this may feel a bit awkward to programmers more comfortable with an object-oriented approach, where classes inherit functionality and objects are built from these classes. Starting with ECMAScript 2015, also known as ECMAScript 6, JavaScript programmers will be able to build their applications using this object-oriented class-based approach. In TypeScript, we allow developers to use these techniques now, and compile them down to JavaScript that works across all major browsers and platforms, without having to wait for the next version of JavaScript.
従来のJavaScriptでは、コンポーネントを再利用するために関数やプロトタイプベースの継承を用います。しかし、この方法はオブジェクト指向のアプローチ(オブジェクト指向のアプローチでは、クラス継承機能やオブジェクトはclassによって作られます)に慣れているプログラマーにとっては若干扱いにくく感じるかもしれません。ECMAScript 6としても知られているECMAScript 2015を使うと、JavaScriptプログラマーもオブジェクト指向によるクラスベースのアプローチを使ってアプリケーションを構築することができるようになります。TypeScriptを使うと、デベロッパーたちはこれらの技術をすぐに使うことができ、すべての主要なブラウザで動くJavaScriptにコンパイルすることができます。またJavaScriptの次のバージョンを待つ必要もありません。
Classes: クラス
Let’s take a look at a simple class-based example:
シンプルなクラスベースの例を見てみましょう。
The syntax should look familiar if you’ve used C# or Java before. We declare a new class Greeter. This class has three members: a property called greeting, a constructor, and a method greet.
すでにC#やJavaを使っている方であれば、この構文は見慣れたものでしょう。ここではGreeterというclassを宣言しました。このclassは3つのメンバー(greetingプロパティ、コンストラクター関数、greetメソッド)を持っています。
You’ll notice that in the class when we refer to one of the members of the class we prepend this.. This denotes that it’s a member access.
classの中で、classのあるメンバーを参照する時に、this.を前につけていることに気づくでしょう。これはメンバーへのアクセスを意味します。
In the last line we construct an instance of the Greeter class using new. This calls into the constructor we defined earlier, creating a new object with the Greeter shape, and running the constructor to initialize it.
最後の行では、newを使ってGreeter classのインスタンスを作成しています。これにより最初に定義したコンストラクタが呼び出され、Greeterの形を持った新しいobjectが作られ、それを初期化するためにコンストラクタの処理が走ります。
Inheritance: 継承
In TypeScript, we can use common object-oriented patterns. Of course, one of the most fundamental patterns in class-based programming is being able to extend existing classes to create new ones using inheritance.
TypeScriptでは、一般的なオブジェクト指向パターンを使うことができます。なかでも、クラスベースのプログラミングの最も基本的なパターンのひとつは、継承を使って新しいclassを作成するために、既存のclassをextend(拡張)できるということです。
Let’s take a look at an example:
以下の例を見てみましょう。
This example covers quite a few of the inheritance features in TypeScript that are common to other languages. Here we see the extends keywords used to create a subclass. You can see this where Horse and Snake subclass the base class Animal and gain access to its features.
上記の例では、他の言語にも共通するTypeScriptの継承に関する機能がいくつか使われています。サブクラスを作るためにextendsが使われているのがわかるでしょう。それから、サブクラスであるHorseとSnakeがAnimalを継承し、Animalの機能にアクセスしていることがわかるでしょう。
constructor関数を含む継承先のclassはベースclassのconstructor関数を実行するためにsuper()を呼び出す必要があります。 例では、サブクラスに定義したメソッドを使って、どのようにベースclassのメソッドを上書きするかについてもわかるようになっています。ここではSnakeにもHorseにもAnimalのmoveメソッドを上書きするためにmoveメソッドが用意され、それぞれのclassに特化した機能を与えています。tomはAnimalとして宣言されていますが、その値はHorseとなっているので、tom.move(34)が呼び出されるとHorseの中でメソッドが上書きされるということに注意してください。 上記の例では、プログラムを通して宣言したメンバーには自由にアクセスてきるようにしてきました。他の言語のclassに慣れている方であれば、上記の例のような場合、自由にアクセスさせるにはpublicを使う必要があったのではないかと気づいたかもしれません。C#では、インスタンス化したclassのそれぞれのメンバーに外部からアクセスできるようにするには、明示的にpublicでメンバーに対してラベル付けする必要があります。TypeScriptの場合、それぞれのメンバーはデフォルトでpublicとなっています。 それでもやはりメンバーを明示的にpublicでマークしてもよいです。上記のセクションで紹介したAnimal classを以下のように書き直してみました。 メンバーがprivateとしてマークされると、以下の例のように、そのメンバーが属しているclassの外部からそのメンバーにアクセスできなくなります。 TypeScriptは構造的な型システムです。2つの異なる型を比較する時に、それらの由来がどこであるかに関わらず、すべてのメンバーの型が一致していたら、TypeScriptはそれらの型を一致しているとみなします。Derived classes that contain constructor functions must call super() which will execute the constructor function on the base class.
The example also shows how to override methods in the base class with methods that are specialized for the subclass. Here both Snake and Horse create a move method that overrides the move from Animal, giving it functionality specific to each class. Note that even though tom is declared as an Animal, since its value is a Horse, when tom.move(34) calls the overriding method in Horse:
Public, private, and protected modifiers: public修飾子 private修飾子 protected修飾子
Public by default: デフォルトはpublic
In our examples, we’ve been able to freely access the members that we declared throughout our programs. If you’re familiar with classes in other languages, you may have noticed in the above examples we haven’t had to use the word public to accomplish this; for instance, C# requires that each member be explicitly labeled public to be visible. In TypeScript, each member is public by default.
You may still mark a member public explicitly. We could have written the Animal class from the previous section in the following way:
Understanding private: privateを理解する
When a member is marked private, it cannot be accessed from outside of its containing class. For example:
TypeScript is a structural type system. When we compare two different types, regardless of where they came from, if the types of all members are compatible, then we say the types themselves are compatible.
However, when comparing types that have private and protected members, we treat these types differently. For two types to be considered compatible, if one of them has a private member, then the other must have a private member that originated in the same declaration. The same applies to protected members.
しかしprivateとprotectedのメンバーを持つ型を比較する際は、TypeScriptはそれらの型を異なったものとして扱います。2つの型を一致するものとみなすには、もし一方がprivateメンバーを持っていたとしたら、もう一方も同じ宣言を元とするprivateメンバーを持つ必要があります。protectedメンバーの場合も同様です。
Let’s look at an example to better see how this plays out in practice:
以下の例を見てみましょう。実際にどのように機能するか把握しやすくなるでしょう。
In this example, we have an Animal and a Rhino, with Rhino being a subclass of Animal. We also have a new class Employee that looks identical to Animal in terms of shape. We create some instances of these classes and then try to assign them to each other to see what will happen. Because Animal and Rhino share the private side of their shape from the same declaration of private name: string in Animal, they are compatible. However, this is not the case for Employee. When we try to assign from an Employee to Animal we get an error that these types are not compatible. Even though Employee also has a private member called name, it’s not the one we declared in Animal.
この例には、AnimalとAnimalのサブクラスであるRhinoがあります。それから形式的にはAnimalと同じように見えるEmployeeがあります。これらのclassのインスタンスを作り、それぞれを代入して何が起こるか見てみることにします。AnimalとRhinoは一致します。なぜならAnimalにおけるprivate name: stringという同じ宣言によって、それぞれ形式的にprivateの部分が共有されているからです。しかし、Employeeの場合はそうなりません。EmployeeをAnimalに代入しようとすると、それらの型が一致しないということでエラーとなります。Employeeはnameと名付けられたPrivateメンバーを持っているにも関わらず、Animalで宣言したものと同じとはみなされません。
Understanding protected: protectedを理解する
The protected modifier acts much like the private modifier with the exception that members declared protected can also be accessed by instances of deriving classes. For example,
以下の例のように、protectedと宣言されたメンバーは継承先のclassのインスタンスによってアクセスできるという例外を除いて、protected修飾子はprivate修飾子とほぼ同じように振る舞います。
Notice that while we can’t use name from outside of Person, we can still use it from within an instance method of Employee because Employee derives from Person.
Personの外部ではnameを使えませんが、それでもEmployeのインスタンスメソッド内からは使うことができます。なぜならEmployeeはPersonを継承しているからです。
A constructor may also be marked protected. This means that the class cannot be instantiated outside of its containing class, but can be extended. For example,
コンストラクタも同様にprotectedをつけてもよいです。この場合、protectedをつけたコンストラクタを持つclassは外部にインスタンス化されません。以下の例を見てください。
Readonly modifier: readonly修飾子
You can make properties readonly by using the readonly keyword. Readonly properties must be initialized at their declaration or in the constructor.
readonlyを使うことによりプロパティを読み取り専用にすることができます。readonlyをつけたプロパティは、宣言時やコンストラクタ内でのみ初期化が可能です。
Parameter properties: パラメータープロパティ
In our last example, we had to declare a readonly member name and a constructor parameter theName in the Octopus class, and we then immediately set name to theName. This turns out to be a very common practice. Parameter properties let you create and initialize a member in one place. Here’s a further revision of the previous Octopus class using a parameter property:
先ほどの例では、Octopus classにおいてreadonlyメンバーとしてname、コンストラクタの引数としてthaNameを宣言し、その後即座にtheNameをnameにセットするようにしていました。これはとても一般的な方法であることがわかっています。Parameter propertiesは、メンバーの作成と初期化を一か所で行うことを可能にしてくれます。以下の例は、Octopus classをparameterプロパティを使って書き直したものです。
Notice how we dropped theName altogether and just use the shortened readonly name: string parameter on the constructor to create and initialize the name member. We’ve consolidated the declarations and assignment into one location.
nameメンバーの作成と初期化を行うために、どのようにtheNameを完全に取り除き、コンストラクタの引数に、短縮されたreadonly name: stringを使ったかわかったでしょうか。宣言と代入がひとつの場所に統合されました。
Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or readonly, or both. Using private for a parameter property declares and initializes a private member; likewise, the same is done for public, protected, and readonly.
parameterプロパティは、コンストラクタの引数にアクセス修飾子、readonly、またはその両方のプリフィックスを付けることによって宣言します。parameterプロパティにprivateをつけると、privateメンバーの宣言と初期化が行われます。public、protected、readonlyをつけた場合も同様です。
Accessors: アクセサー
TypeScript supports getters/setters as a way of intercepting accesses to a member of an object. This gives you a way of having finer-grained control over how a member is accessed on each object.
TypeScriptはオブジェクトのメンバーへのアクセスを横取り(intercept)する方法としてgetters/settersをサポートしています。これにより、各オブジェクトがどのようにメンバーにアクセスするかについてきめ細かい制御が可能となります。
Let’s convert a simple class to use get and set. First, let’s start with an example without getters and setters.
getとsetを使うためにシンプルなclassを書き直していきましょう。まず、以下のようにgetterとsetterを使わずに実装を始めます。
While allowing people to randomly set fullName directly is pretty handy, this might get us in trouble if people can change names on a whim.
誰でもfullyNameを直接好きなようにセットできることは大変便利なことではありますが、もし誰かが気まぐれで名前を変更できてしまったとしたら、それはそれでたまったものではありません。
In this version, we check to make sure the user has a secret passcode available before we allow them to modify the employee. We do this by replacing the direct access to fullName with a set that will check the passcode. We add a corresponding get to allow the previous example to continue to work seamlessly.
そこで以下のように、ユーザーがemployeeの情報を変更できるようにする前に、ユーザーが有効な秘密のパスコードを確実に持っているということをチェックするようにします。これを実行するために、fullNameへのダイレクトなアクセスを、パスコードをチェックするsetに置き換えます。それから、これまでの例のサンプルがシームレスに動き続けるようにsetに対応するgetを追加します。
To prove to ourselves that our accessor is now checking the passcode, we can modify the passcode and see that when it doesn’t match we instead get the message warning us we don’t have access to update the employee.
アクセサーがパスコードを確認していることを証明するために、パスコードを変更して、パスコードが一致していない時は、代わりに「employee情報を更新するためのアクセス権がない」という警告のメッセージを受け取るかを見ます。
アクセサーについて注意すべき点は以下の2つです。 まず1つ目は、アクセサーはECMAScript 5またはそれ以上に出力するためのコンパイラーをセットする必要があるということです。ECMAScript 3へ出力した場合は、サポートされません。2つ目は、setがなくgetだけあるアクセサーは自動的にreadonlyとして推測されるようになっているということです。これは自分のコードから.d.tsファイルを生成した時に助かります。なぜなら、そうなっていることでプロパティが変更できないということがわかるようになるからです。 ここまではclassのインスタンスメンバーについてのみ紹介してきました。instance(インスタンス)メンバーは、インスタンス化された時にインスタンスのオブジェクト上に現れるものでした。TypeScriptでは、さらにstatic(静的)メンバーも作成することができます。staticメンバーはインタンス上というよりはむしろclass自身に現れます。以下の例では、すべてのgridに共通な値を持つoriginに対してstaticをつけています。インスタンスはclassの名前を頭につけることでこの値にアクセスします。インスタンスにアクセスされるものの頭にthis.をつけるのと同じように、ここではGrid.を静的にアクセスされるものの頭につけます。 抽象class(abstract class)は、他のclassの継承元となるベースclassです。抽象classは直接インスタンス化する必要はありません。interfaceとは異なり、抽象classは、機能を実装したメンバーを含めてもよいです。抽象classを定義する際は、抽象クラス内に抽象メソッドを定義するのと同様に、abstractを使います。 抽象class内のabstractがつけられたメソッドは、機能の実装を含めず、継承先のclass内で実装するようにします。抽象メソッドはinterfaceのメソッドと同じ構文となります。どちらもメソッドのbodyを含めることなくメソッドのシグネチャを定義します。しかし抽象メソッドはabstractを必ずつけるようにします。アクセス修飾子は任意でつけても構いません。 TypeScriptでは、classを宣言する時、実は同時に複数の宣言が作られています。まず1つ目は、classのinstance(インスタンス)の型です。 ここで、let greeter: Greeterと書くと、Greeterは、Greeterクラスのインスタンスの型として使われます。これは他のオブジェクト指向言語を使っているプログラマーにとっては、ほとんど当たり前のことです。 それから、2つ目はconstructor function(コンストラクタ関数)を呼ぶもうひとつの値を作っています。これは、newしてclassのインスタンスを新たに作る時に呼ばれる関数です。実際にこれがどのように見えるか確認するために、上記の例で作られたJavaScriptのコードを見てみましょう。 この例では、let Greeterには、コンストラクタ関数が代入されることになります。newを呼び出し、この関数を実行する時、classのインスタンスを取得します。コンストラクタ関数はまたclassのstatic(静的)メンバーの全てを含んでいます。つまり、classにはinstance(インスタンス)に関わる部分とstatic(静的)に関わる部分があると言い換えることもできます。 この違いを明らかにするためにコードを以下のように修正してみましょう。 この例において、greeter1はこれまでと同様に動きます。Greeter classをインスタンス化し、そのオブジェクトを使います。これはこれまでに見てきたものです。 次に、classをダイレクトに使ってみます。ここでは、greeterMakerという新しい変数を作ります。この変数には、class自身(他の言い方をするとコンストラクタ関数)が代入されます。ここで型の宣言として、typeof Greeterを使います。これは、インスタンスの型というよりむしろ「Greeter class自身の型を与えてくれる」ものです。もっと正確に言うと、コンストラクタ関数の型である「Greeterと呼ばれるsymbolの型を与えてくれる」ものとなります。この型にはGreeter classのインスタンスを作るコンストラクタに加え、Greeterのstaticメンバーのすべてが含まれています。greeterMakerをnewして、Greeterのインスタンスを新たに作り、これまでと同様に呼び出すことで、これを確認してみてください。 前のセクションで触れたように、classを宣言すると2つのものが作られます。classのインスタンスを表している型とコンストラクタ関数です。classは型を作るので、interfaceが使える場面で、同じようにclassを使うことができます。 以上、TypeScriptの公式ドキュメントの「classの部分(Classes・TypeScript)」の日本語訳となります。上記の内容を元に、ES2015のclassと機能を比較してみると、以下のようになりました。 実際にTypeScriptとES2015のclassを比較してみると、TypeScriptのclassがいかに充実しているかがわかりますね。JavaやC#に備わっている機能がしっかりサポートされています。逆にこれまでES2015のclassしか触ってきてない方には、覚えなくてはいけない概念や機能がたくさんありますね。 Angularでも、TypeScriptのclassの機能はバリバリ使うようになっているので、しっかり押さえておくとよいでしょう。次は、これもAngularでよく使うものとなりますが、decoratorsやGenericsなどを翻訳してみようと思っています。A couple of things to note about accessors:
First, accessors require you to set the compiler to output ECMAScript 5 or higher. Downlevelling to ECMAScript 3 is not supported. Second, accessors with a get and no set are automatically inferred to be readonly. This is helpful when generating a .d.ts file from your code, because users of your property can see that they can’t change it.
Static Properties: 静的プロパティ
Up to this point, we’ve only talked about the instance members of the class, those that show up on the object when it’s instantiated. We can also create static members of a class, those that are visible on the class itself rather than on the instances. In this example, we use static on the origin, as it’s a general value for all grids. Each instance accesses this value through prepending the name of the class. Similarly to prepending this. in front of instance accesses, here we prepend Grid. in front of static accesses.
Abstract Classes: 抽象クラス
Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly. Unlike an interface, an abstract class may contain implementation details for its members. The abstract keyword is used to define abstract classes as well as abstract methods within an abstract class.
Methods within an abstract class that are marked as abstract do not contain an implementation and must be implemented in derived classes. Abstract methods share a similar syntax to interface methods. Both define the signature of a method without including a method body. However, abstract methods must include the abstract keyword and may optionally include access modifiers.
Advanced Techniques: 上級テクニック
Constructor functions: コンストラクタ関数
When you declare a class in TypeScript, you are actually creating multiple declarations at the same time. The first is the type of the instance of the class.
Here, when we say let greeter: Greeter, we’re using Greeter as the type of instances of the class Greeter. This is almost second nature to programmers from other object-oriented languages.
We’re also creating another value that we call the constructor function. This is the function that is called when we new up instances of the class. To see what this looks like in practice, let’s take a look at the JavaScript created by the above example:
Here, let Greeter is going to be assigned the constructor function. When we call new and run this function, we get an instance of the class. The constructor function also contains all of the static members of the class. Another way to think of each class is that there is an instance side and a static side.
Let’s modify the example a bit to show this difference:
In this example, greeter1 works similarly to before. We instantiate the Greeter class, and use this object. This we have seen before.
Next, we then use the class directly. Here we create a new variable called greeterMaker. This variable will hold the class itself, or said another way its constructor function. Here we use typeof Greeter, that is “give me the type of the Greeter class itself” rather than the instance type. Or, more precisely, “give me the type of the symbol called Greeter,” which is the type of the constructor function. This type will contain all of the static members of Greeter along with the constructor that creates instances of the Greeter class. We show this by using new on greeterMaker, creating new instances of Greeter and invoking them as before.
Using a class as an interface: classをinterfaceとして使う
As we said in the previous section, a class declaration creates two things: a type representing instances of the class and a constructor function. Because classes create types, you can use them in the same places you would be able to use interfaces.
まとめ
TypeScript Class ES2015 Class コンストラクタ関数 ○ ○ メンバープロパティを持てる ○ × extendsによる継承 ○ ○ public修飾子 ○ × private修飾子 ○ × protected修飾子 ○ × readonly修飾子 ○ × パラメータープロパティ ○ × static修飾子 ○ △
staticメソッドには対応抽象クラス & 抽象メソッド ○ × アクセサー(getter/setter) ○ ○ 静的型付け ○ ×
コメント
ピンバック: [Angular6] サクッとコンパクトにチュートリアルを訳してみた! - まんくつ
ピンバック: TypeScriptを用いて、DAZ Scriptでクラスを利用する | lps da DAZ Studio user