TypeScriptの学習①
はじめに
このTypeScript Deep Diveは、TypeScriptの長所や機能が理解しやすく、丁寧に説明されており、初学者にとって非常に効果的な資料で、各機能の要点を抑えた記事を書きます。
なぜ、TypeScriptを使うのか?
TypeScriptの主なゴールは2つで
- JavaScriptに型システムを導入し、より安全に「プログラム」を書くため
- JavaScriptの機能を、古いJavaScript環境でも使えるようにするため
TypeScriptのおさえておきたい3つポイント
- 型システム
- 型推論
- 型指定
型システム(Type System)について
モダンなプログラム言語は、さまざまな型システムを備えてあります。
型は、リファクタリング時に開発速度を高めます。型があることによって、コードを書いている時点でエラーに気づくことができ,より早くエラーを修正することができます。
JavaScriptでは実行時に、はじめてエラーに気づいて、コードに戻って修正しますが、TypeScript(型システム)では開発中に早い段階でエラーに気づけるということは嬉しいメリットになります。
-
型システム : プログラマーが作成したプログラムに型を割り当てるために型チェッカーが使用するルールの集まり
-
型チェッカー : コードが型安全であることを検証する特別なプログラム
型推論(Type Inference)について
TypeScriptは、コーディングの生産性に対する影響をなるべく小さく抑えながら型の安全性を提供するために、可能な限り、型推論を行います。
型指定(Type Annotation)について
型推論の結果が正確でない場合は、開発者が、明示的にコード上で、型を指定する(型アノテーションを書く)ことができます。型指定を書くことによって、
2つのメリットがあります:
- コンパイラの理解を助けるだけでなく、より重要なことに、
あなたが書いたコードを次に読まなくてはならない開発者にとってのドキュメントになります(それは、将来のあなたかもしれません!)。 - コンパイラがどのようにコードを理解するか?正しい理解を、コンパイラの型チェックのアルゴリズムに反映することしてくれます
例えば、
let a: number = 1 // a は number
let b: string = 'Hello' // b は string
let c: boolen[] = [true, false] // c は boolenの配列
型が一致しない場合は、エディター上にエラーが表示されます
let a: number = '1'; // エラー: `string` を `number` に代入できません
- 型推論 : ソースコードを解析し、そのコードの流れから、変数や関数などの型を推測してくれる仕組みのこと
ModernなJavaScriptの機能を今すぐに利用できる
TypeScriptは、古いJavaScript(ES5以前)の実行環境でも、ES6以降のバージョンで多くの機能を使えるようにしています
TypeScriptは、JavaScriptを改善する
TypeScriptは単にドキュメント付きのJavaScriptです。
TypeScriptはJavaScriptのlinter(コードの静的解析ツール)で、型情報を持たない他のJavaScriptのlinterよりも優れているだけです。だからこそJavaScriptに関して知っておかなければならないことがあります。
- TypeScript : JavaScriptのスーパーセット(上位互換)であり、JavaScriptにコンパイラやIDEで使う型情報が付いたもの
NullとUndefined
JavaScriptとTypeScriptは、nullとundefinedという2つのボトム型(Bottom Type)があります。これらは異なる意味を持っています。
-
初期化されていない: undefined。
-
現在利用できない: null。
どちらであるかをチェックする
両方とも対応する必要があります。==
で確認する
/// `foo.bar == undefined` のようなコードを書いた時に何が起こるだろうか?
console.log(undefined == undefined); // true
console.log(null == undefined); // true
// 以下のチェックをすれば、falsyな値について十分です。
console.log(0 == undefined); // false
console.log('' == undefined); // false
console.log(false == undefined); // false
== null
を利用して,
undefined
と null
を両方ともチェックすることを推奨します。
function foo(arg: string | null | undefined) {
if (arg != null) {
// `!=` がnullとundefinedを除外しているので、引数argは文字列です
}
}
ルートレベル(Root Level)のUndefinedのチェック
Strictモード(strict mode)
でfooを使うとき、fooが定義されていないと、ReferenceError exception
が発生し、コールスタック全体がアンワインド(unwind)されます。
変数がglobalレベル
で定義されているか
どうかを確認するには、通常、typeof
を使用します:
if (typeof someglobal !== 'undefined') {
// これでsomeglobalは安全に利用できます
console.log(someglobal);
}
Root Level
: ツリーのいちばん上のこと
Unwind
: 「ほどける」「巻き戻る」という意味
Undefined の明示的な利用を制限する
TypeScriptでは値と構造を分離してドキュメントのようにわかりやすくすることができます。
function foo(){
// if 何らかの場合に返す値
return {a:1,b:2};
// else それ以外の場合に返す値
return {a:1,b:undefined};
}
次のように型指定を使用します
function foo():{a:number,b?:number}{
// if 何らかの場合に返す値
return {a:1,b:2};
// else それ以外の場合に返す値
return {a:1};
}
NodeスタイルのCallback
Nodeスタイルのコールバック関数は、エラーがなければerr
にnull
を設定して呼び出されます。
一般的にtruthyチェックを行います
fs.readFile('someFile', 'utf8', (err,data) => {
if (err) {
// 何らかのエラー処理をする
} else {
// エラーなし
}
});
独自のAPIを作成するときは、一貫性のためにnullを使用することは、問題ありません。
できればPromise
を返すようにするべきらしい。そうすれば、err
がnull
かどうかを気にかける必要はなくなります(.then
と.catch
を使います)。
以下、Badな関数の例:
function toInt(str:string) {
return str ? parseInt(str) : undefined;
}
以下、Betterな関数の例:
function toInt(str: string): { valid: boolean, int?: number } {
const int = parseInt(str);
if (isNaN(int)) {
return { valid: false };
}
else {
return { valid: true, int };
}
}
this
this
へのアクセスは、関数がどのように呼び出されたかによって制御されます。
これは一般に「呼び出しコンテキスト」
と呼ばれます。
例:
function foo() {
console.log(this);
}
foo(); // グローバル値をログに出力します(例: ブラウザにおける `window`)
let bar = {
foo
}
bar.foo(); // `bar`がログに出力されます。
bar
に対してfoo
が呼び出されます。なので、thisの使い方に気をつけてください。
クロージャ
JavaScript関数は、外部スコープで定義された変数にアクセスできます。
例:
function outerFunction(arg) {
var variableInOuterFunction = arg;
function bar() {
console.log(variableInOuterFunction); // 外側のスコープにある変数にアクセスします
}
// argにアクセスできることを示すために、ローカル関数を呼び出します。
bar();
}
outerFunction("hello closure"); // "hello closure"とログに出力されます
内側の関数
は外側のスコープの変数
(variableInOuterFunction)にアクセスできます。
外側の関数の変数
は、内側の関数
に閉包
されています。なのでクロージャ(closure)ということは直感的に理解できます。
内側の関数
は、外側の関数
がreturnされた後でも変数にアクセスできます。
変数が内側の関数に束縛(bind)
されており、外側の関数
に依存していないからです。
例:
function outerFunction(arg) {
var variableInOuterFunction = arg;
return function() {
console.log(variableInOuterFunction);
}
}
var innerFunction = outerFunction("hello closure!");
// outerFunctionが返しているものに注意してください
innerFunction(); // "hello closure" と出力されます
クロージャのメリットのひとつ
オブジェクトを簡単に作成することができます。
リヴィーリングモジュール(revealing module pattern)
というコーディングパターンがあります:
function createCounter() {
let val = 0;
return {
increment() { val++ },
getVal() { return val }
}
}
let counter = createCounter();
counter.increment();
console.log(counter.getVal()); // 1
counter.increment();
console.log(counter.getVal()); // 2