TypeScript 4.3 Beta 変更点
TypeScript 4.3 Betaが出たので、公式ブログを読んで気になった点からまとめていきたいと思います。
Separate Write Types on Properties
getterとsetterで異なる型を指定できるようになりました。
v4.3 Beta 以前
例えばsetterで、文字列を受け取って数字に変換するような処理を書きたいとき。
class Thing {
#size = 0;
get size(): number {
return this.#size;
}
// Error! 'get' and 'set' accessor must have the same type.
set size(value: string | number) {
this.#size = typeof value === 'string' ? parseInt(value) : value;
}
}
setterの引数の型に、getterと違う型を指定すると「getterと同じ型にして!」と怒られてしまいます。
上記のコードに、無理やり"42"
を入れようとしてもやはり怒られます。
let thing = new Thing();
thing.size = 42; // OK
thing.size = "42"; // Error! Type 'string' is not assignable to type 'number'.
v4.3 Beta 以降
v4.3 Beta では、getterは常にnumber
であることを保証した上で、setterには個別の型を設定できるようになりました。
なので先程怒られていたコードも、問題なく通ります。
class Thing {
#size = 0;
get size(): number {
return this.#size;
}
// OK! \(^o^)/
set size(value: string | number) {
this.#size = typeof value === 'string' ? parseInt(value) : value;
}
}
let thing = new Thing();
thing.size = 42;
thing.size = "42"; // OK! \(^o^)/
注意点として、getterとsetterで異なる型を使うときは、getterの型は常にsetterの型に割り当て可能である必要があります。
override and the --noImplicitOverride Flag
override 修飾子と--noImplicitOverride
フラグが新しく追加されました。
v4.3 Beta 以前
親クラスと同名のメソッドを子クラスに定義すると、子クラスでメソッドを上書きすることができます。これをオーバーライドと呼びます。
v4.3 Beta 以前では、オーバーライドによるミスが起こりがちでした。
以下その具体例です。SpecializedComponent
という子クラスが親クラスのshow
とhide
メソッドをオーバーライドしています。
class SomeComponent {
show() {
// ...
}
hide() {
// ...
}
}
class SpecializedComponent extends SomeComponent {
show() {
// ...
}
hide() {
// ...
}
}
おっと!だれかが親クラスのshow
とhide
メソッドを消して、代わりにsetVisible
メソッドに置き換えたようです。
しかし、子クラスの方は何も更新されていません...
class SomeComponent {
- show() {
- // ...
- }
- hide() {
- // ...
- }
+ setVisible(value: boolean) {
+ // ...
+ }
}
class SpecializedComponent extends SomeComponent {
show() {
// ...
}
hide() {
// ...
}
}
親クラスのshow
とhide
メソッドが消されたので、子クラスのshow
とhide
は最早オーバーライドではなくなりました。
オーバーライドではなく、こんどは子クラス独自のメソッドという扱いになります。
エラーにはなるわけではないので、見落としてしまうということはありそうです。
子クラスに残ってしまったメソッドは、元々これがオーバーライドによって定義されたものかもよく分からなくなり、もう呼ばれることもないため、無駄なメソッドになってしまいます。
v4.3 Beta 以降
v4.3 Beta では override 修飾子の追加で、これを回避できるようになります。
この修飾子が付いている場合、TypeScriptは親クラスに同名のメソッドが存在するか確認してくれます。なのでもし親クラスの方を誤って消してしまっても、以下のようにエラーで怒ってくれるようになります。
class SomeComponent {
setVisible(value: boolean) {
// ...
}
}
class SpecializedComponent extends SomeComponent {
// 親クラスに同名のメソッドが無いから、override修飾子付けちゃだめだよ!
// Error: This member cannot have an 'override' modifier because it is not declared in the base class 'SomeComponent'.
override show() {
// ...
}
}
これはめちゃ便利な気がしますが、そもそも override 修飾子をつけるのを忘れてしまったら意味ないです。
なので、v4.3 Betaでは同時に --noImplicitOverride
というフラグが用意されました。
このフラグがtrueのとき、override 修飾子を付け忘れてても以下のようにエラーで怒ってくれるようになります。
class SomeComponent {
show() {
// ...
}
}
export class SpecializedComponent extends SomeComponent {
// 親クラスに同名のメソッドが存在するから、override修飾子付けてね!
// Error: This member must have an 'override' modifier because it overrides a member in the base class 'SomeComponent'.
show() {
// ...
}
}
このフラグを使えば、親クラスに同名のメソッドがすでに存在してて、意図せずオーバーライドしてしまうというケースも回避できそうです。
Always-Truthy Promise Checks
strictNullChecks を有効にしている場合、条件式にPromiseオブジェクトを入れると、エラーになるようになりました。
v4.3 Beta 以前
Promiseオブジェクトは truthy として評価されるらしいです。
といっても、そもそも truthy が何か自分はよく分かってなかったので調べてみます。以下mdnからの引用です。
JavaScript において、 truthy は Boolean コンテキストに現れた時に true とみなされる値のことです。 falsy として定義された値 (つまり、false, 0, -0, 0n, "", null, undefined, NaN) を除くすべての値は truthy です。
Promiseオブジェクトは、false, 0, -0, 0n, "", null, undefined, NaN
のいずれにも当てはまらないので、たしかに truthy であると言えます。
その上で、以下のコードを見てみます。
async function foo(): Promise<boolean> {
return false;
}
async function bar(): Promise<string> {
if (foo()) {
return "true";
}
return "false";
}
if文の条件式である foo()
の戻り値はPromiseオブジェクトで、truthyとして評価されます。そのため、barの戻り値は常に"true"
となります。
しかし、おそらくこのコードを書いた人は await を書き忘れているだけなので、実際書き手が期待している値は"false"
だと思われます。
v4.3 Beta 以前のバージョンでは、Playground で見ても何もエラーは出てないので、見落としがちかもです。
v4.3 Beta 以降
v4.3 Betaでは、ちゃんと怒ってくれるようになりました。これで await 忘れのミスがを防げそうです!
async function foo(): Promise<boolean> {
return false;
}
async function bar(): Promise<string> {
if (foo()) {
// Error!
// This condition will always return true since this 'Promise<boolean>' appears to always be defined.
// Did you forget to use 'await'?
return "true";
}
return "false";
}
Discussion