読者です 読者をやめる 読者になる 読者になる

リズムのじかん

javascript、typescriptなど中心に書きます。

typescript初心者が躓いたこと

typescriptを始めて1ヶ月くらい経ちました。
そんなtypescript初心者がはまったところをまとめます。

typescriptを始めた理由

趣味でwebサービスを作っていて、言語はjavascriptなのですが、昼間の本業が忙しいと1週間とか1ヶ月とか間が空いてしまいます。
そうすると前に何をコーディングしていたか忘れてしまう。。。
このオブジェクトはどんな関数持ってたっけ?とか、この関数の戻り値の型はなんだっけ?とか。
javascriptに型チェックはないですが、型は意識してコーディングしている)
そしてやる気を無くしてしまう。。

そこで、
型を付けてコードの自動補完とかしてくれたら、どうにかなりそう(・∀・)
と始めたのがtypescriptです。
他のラッパー言語も考えましたが、coffeescriptは型チェックないし、haxeは気になるけどjQueryとかの外部ライブラリはどうするんだろう?、dartは何か嫌だwという感じでtypescriptにしました。

躓いたところ

Q1. 即時関数どう書くの?

A. いちおうそのまま書けます。

とりあえずtypescript始めようってことで、既存のjavascriptソースの拡張子をtsに置き換えて、typescriptっぽく型を付けようとしたのですが、早速何をどう書いていいかわからない。
javascriptで書いたほうが早いんじゃないか?、、と思いながら格闘します。

最初に躓いたのが即時関数はtypescriptでどう書くの?

//javascript
var isReady = (function(user){
if(!user.logined()){
  return false;
}
_.each(user.scores, function(score){
  if(score < 0){
    return false;
  }
});
return true;
})(currentUser);

いちおうそのまま書けます。

//typescript
var isReady = ((user:User):boolean => {
if(!user.logined()){
  return false;
}
_.each<number, boolean>(user.scores,(score) => {
  if(score < 0){
    return false;
  }
});
return true;
})(currentUser);

または、typescriptなら名前付き関数として定義して呼び出したほうが自然な気がします←好みレベルの問題と思われる。

// typescript
private isReady(user:User):boolean  {
if(!user.logined()){
  return false;
}
_.each<number, boolean>(user.scores,(score) => {
  if(score < 0){
    return false;
  }
});
return true;
}

twitterでコメントいただきましたが、ラムダ式()=>{ }の部分はfunction(){ }とも書けます。
このときthisの意味合いが異なるので使い分けが必要です。以下が参考になります。
http://phyzkit.net/typescript/#chapter3_section9

Q2. 型はどこまで書けばいいの?

A. 右辺の型が明確であれば、左辺の型付けは不要。

最初は以下のように一生懸命型を書いてました。

// javascript
var d = Q.defer();
// typescript
var d:Q.Deferred<User> = Q.defer<User>();

javascriptより書く量多い、、つらい、、と思ってましたが、結論としては右辺の型が決まっていれば左辺の型は書かなくてOKです。

var d = Q.defer<User>();

これなら我慢できる。よく考えたらC#型推論と同じですね、C#の人が作った言語ですし。

ただしwebstormを使っていると、右辺を型変換した場合に入力補完されないことがあるようです。

// コンパイルは通るが、webstormで入力補完してくれない。visualstudioは知らない。
var Author = <IAuthor>mongoose.model('Author', AuthorSchema);
// 以下のように書くと、webstormで入力補完してくれます。
var Author:IAuthor = <IAuthor>mongoose.model('Author', AuthorSchema);

Q3. import, export, module, declare, referenceの使い分けがよくわからない。。

A. とりあえず自己流で使い分け。

typescriptのimport, export, module, declare, referenceなどは、公式のハンドブックを見ても、結局どれ使えばいいの?という感じでよくわかりません。(declareとかは明確ですが)
いろいろ試行錯誤して個人的には以下で納得しています。

declare

外部ライブラリ(jQueryなど)に型付けを付け加えるときに使う。TSDを使えばよいので、個人で書くことはない。

reference

グローバルで読み込むスクリプトに対して使うといい感じ。
クライアントサイドだど、htmlで読み込んだスクリプト対して使う。

<script src="/bower_components/requirejs/require.js"></script>

サーバーサイドだと、nodeや、グローバルに読み込ませたスクリプトに対して使う。

global._ = require('underscore')
module

使っていない。業務ロジックと関係のないFramework層の何かを作るときに使うはずだと勝手に思っている。

import, export

クラスのimportとexportに使う。
1ファイル1クラス構成にして、export = [クラス名] と書くと簡単。
もちろんクラス以外もexportするが、1ファイル1エクスポートが簡単。

// サンプル:Author.ts
import Score = require('./Score');
class Author {
  constructor(public scores: Score[]) {
     // initialize
  }
  public sum():number {
    // scores loop
    return 0;
  }
}
export = Author

※export XXX, export YYYと複数エクスポートするのは、使わなくて良いと思う。(moduleを書くときには使うと思われる)

Q4. インタフェースをimportするのは無駄じゃない?

A. コンパイル後のjsでは、不要なスクリプトの読み込みは発生しません。

typescriptで型付けのためにクラスやインタフェースをimportしますが、jsでは必ずしもそのスクリプトを読み込む必要はありません。例えば、

  • インタフェースは実装を持たないため、読み込む必要がない。
  • クラスは、コンストラクタを呼び出す場合は読み込んでnewしますが、インスタンスの関数を呼び出すだけれであれば読み込む必要はない。

無駄なスクリプトの読み込みはしたくないけど、importしないとコンパイル通らないし、、と最初ものすごく悩みましたが、コンパイラは不要なスクリプトの読み込み処理を生成しないようです。いい感じですね。
Q3のtsファイルをコンパイルすると以下のjsファイルになります。(commonjs形式でコンパイルした場合)

// Author.js
//var Score = require('./Score'); <= この行は生成されない
var Author = (function () {
    function Author(scores) {
        this.scores = scores;
    }
    Author.prototype.sum = function () {
        return 0;
    };
    return Author;
})();
module.exports = Author;

Q5. クラスじゃなくて関数をexportしたいんだけど。。

A. クラスのstaticメソッドにするのが簡単。

関数をエクスポートしたい場合、最初はハンドブックにあるように、

// draw.ts
export function draw() {
  // 処理
}

と書いていました。この関数を使う側は以下のようになります。

import draw = require('./draw');
// draw(); <=これはNG。
draw.draw() //これはOK。

draw.tsは関数自体をexportするわけではなく、ひとつオブジェクトをはさみます。
それならファイル名をそれっぽい名前にして、読み込む変数もそれっぽい名前にして、、、でも何か違う気がする。。
いろいろ悩みましたが、個人的にはclassのstaticメソッドとして定義するのがよいと思います。
javaとか.netっぽい書き方になります。

// Score.ts
class Score {
  constructor(public value:number) {
     // initialize
  }
  public static draw(){
     // 処理
  }
}
export = Score;

使う側は以下のようになります。

import Score = require('./Score');
Score.draw();

他にもいろいろ学んだことがありますが、個別の記事で書いていきます。