Help us understand the problem. What is going on with this article?

C#erから見たTypeScriptの罠

More than 1 year has passed since last update.

日頃C#を生業としているものが、しばらくTypeScriptというものに触れてみた雑感。
タイプガードや引数プロパティ宣言など、便利だなーと思う部分も多い反面、型付けに関しては、C#のような静的型付け言語の感覚で使っていると危険だなと感じたという話です。

TypeScriptの型付けはあくまでもラベルであって、実体はJavaScriptそのものである。
ここを十分に意識しないと、さまざまなトラップに足を取られることがわかってきました。

対象としているTypeScriptのバージョンは、だいたいは2.6。一部すでに古くなった情報が混ざっているかもしれません。

thisの参照先

thisに苦労するのはjsあるあるであろうけれど、TypeScriptも例外ではない。というか型付けを間違うことがあって余計にタチが悪い。

class App {

    private appId = 12345;

    public constructor() {
        const btn = document.getElementById("buttonA");
        if (btn != null) {
            btn.addEventListener("click", this.onButtonClick);
        }
    }

    public onButtonClick() {
        alert(this.appId);
    }
}

上のコードはビルドエラーにならない。が、 alert(this.appId); のthisの参照先はbuttonエレメントなので、 this.appId は12345ではなく、undefinedとなる。

thisに対し、あなたはbuttonですよと教えてあげることはできる。

    public onButtonClick(this:HTMLButtonElement) {
        alert(this.id); //buttonA
    }

undefinedを排除しきれない

※本項に記載の内容は、version 2.7.1より、コンパイルオプション strictPropertyInitialization でチェックできるようになりました!

stringを引数に取るメソッドがあったとして

    public alertName(name: string) {
        alert(name);
    }

これを alertName(undefined) とか alertName(null) のように呼び出そうとすると、undefinedやnullをstring型の引数に割り当てることはできませんということで、ビルドエラーになる。

だからnull安全かというと、そういうわけではない。例えば以下のコード

class App {

    private name: string;

    public constructor() {
        this.alertName(this.name);
    }

    public alertName(name: string) {
        alert(name);
    }
}

これはビルドエラーにならないが、alertNameに渡される引数はundefinedとなる。
なので、公開メソッド内では引数のnullチェックが常に必要。

さらにはnull/undefinedだけでなく、型のチェックも不要とはいえない(次項)

型はいくらでも偽れる

JavaScriptである限り、any型との相互変換をするケースは避けられない。
これがなかなかやっかいで、一度anyにしてしまうと、もうTypeScriptの型チェックはまるでザルになる。

    const num = 123; //number
    const numAsAny = num as any; //any
    const str: string = numAsAny; //string

    alert(str.trim());

上のコードもビルドエラーにならず、実行時エラー(str.trim is not a function)になる。

例えばJSONで外部とやり取りするときとか、interfaceを定義しておいて受け取ったJSONから変換して使うとか、非常に便利なんだけども、本当にJSONがそのinterfaceの形をしているかどうかっていうのは、結局個々にチェックしないとわからない。

キャスト(型アサーション)も、ビルド時のチェックがあるにはあるが、完璧ではない。

interface Person {
    name: string;
    age: number;
}

var p1 = { name: "hoge" } as Person;                    //OK
var p2 = { name: "hoge", age: 10, a: "xxx" } as Person; //OK
var p3 = { name: "hoge", a: "xxx" } as Person;          //NG

上記、p3はビルドエラーにしてくれる。
p2がエラーにならないのは別に良いと思うが、p1はエラーにしてくれても良いように思う。

Interfaceとタイプガード

※本項に記載の問題は、version 2.7.1より修正されたようです。コメントでご指摘いただきました。

interface Animal と、それを実装する Dog Cat クラスを作成する。
なおこのとき、実装クラスでメンバをreadonlyにしても、ビルドエラーにはならない。

interface Animal {
    name: string;
}

class Dog implements Animal {
    readonly name: string;
}

class Cat implements Animal {
    name: string;
}

さて、何の動物かわからない pochi オブジェクト(実は猫)があったとして、これが犬かどうかを判定してみる。

const pochi: Animal = new Cat();

if (pochi instanceof Dog) {
    console.log("pochi is a Dog.");
}
else {
    //ビルドエラー
    console.log(pochi.name);
}

上記コードのelse節はビルドエラーになる。
このelse節で pochi は never型、つまり存在しえない型と判定されている。

正直、理由はよくわからない。
タイプガードの仕組みにより、if節で pochi instanceof Dog を条件とすれば、else節内では、pochiDog である可能性は排除された上で型付けが行われる。それは良い。
問題は、 pochiDog 以外の Animal である可能性まで排除されてしまっていることだ。

なお、 Dogname からreadonlyを外せばビルドが通るようになる。

正直、これに関してはバグなんじゃないかとも思うが、TypeScriptに対するバグ指摘には、「それはバグじゃなく仕様だ」というものがとにかく多いらしく、コントリビュートガイドラインにも「バグを見つけたと思っても、まずFAQを読め」とあって、ちょっと尻込みする…。


以上です。快適なTypeScriptライフを!

aakasaka
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした