TypeScript 初学者が手っ取り早く使うための知識
TypeScript の初心者である私がコーディングするうえで躓いた点をメモする記事です。今後もちょいちょいと TypeScript を触りますので、疑問が生まれたら随時この記事を更新しようと思います。
本職プログラマーでなければ複数の言語には手を出しづらいですよね。個人的には Ruby のお作法に慣れているのですが、TypeScript を使わざるを得ない場面に遭遇したのでトライアンドエラーでやってみました。
慣れてない初学者だと「そもそも検索キーワードがわからない!!」なんてことも起こりがちです。例えばスップレッド構文とか、初見だとどうやって調べればいいんだって感じ。そんな苦労を経つつ、調べるのに苦労した点などを中心にまとめられればと思います。
この記事で記載している表現は必ずしも正確な説明をしているものではありません。
あくまで目的は、ささっとTypeScript を使い始めたいスタートダッシュ重視の目線です。自身がほどほどにコーディングをするうえで困らない程度の及第点の説明をしています。
長期的に利用する場合は、もう少し丁寧に基礎知識を学習すべきでしょう。プロフェッショナルを目指す方はオフィシャルのリファレンスを参照してください。
型について
便利。使えば使うほど慣れてきて、型なしで JavaScript を書くのが嫌になっていく感覚さえあります。
慣れていないうちは、いちいち赤字で警告が出たり邪魔くさくて any
にしたり @ts-ignore
したりしがちですが、できるだけ使わないでいいように頑張ることで将来のメンテナンスがしやすくなりますね。
型をつけるべき理由はググると無数に記事があるのでそれらを軽く参照しつつ、あとは実践あるのみでしょう。慣れです。
分割代入と型の付け方
今さら分割代入知らないなんて言えないのでTypeScriptでこっそり勉強する - Qiita
https://qiita.com/ringtail003/items/b4de9278cd507feb2478 より引用
右辺の配列リテラルは TypeScript ではタプル型とみなされます。
const tuple: [number, string, boolean] = [1, 'abc', true];
let a = tuple[0]; // number
let b = tuple[1]; // string
let c = tuple[2]; // boolean
スプレッド構文を使うと、残りの要素をまとめて拾う事ができます。
const [a, b, ...c] = [1, 2, 3, 4, 5];
a; // 1
b; // 2
c; // [3, 4, 5]
興味のない要素は、変数名を省略する事で無視する事ができます。
const [a,,,, b] = [1, 2, 3, 4, 5];
a; // 1
b; // 5
分割代入の左辺には型アノテーションを指定する事ができます。
let { a, b }: { a: number, b: string } = { a: 1, b: 'abc' };
Promise
JavaScript (TypeScript) でコーディングする上では絶対に避けて通れないだろう技術。Javascript は非同期処理なので〜〜とか、コールバック地獄になるから〜〜とか、このような雰囲気はわかりつつもいざ自分で実装すると訳わからん状態だったのですが、以下の考え方をするとシンプルに腹落ちしました。
Promise 時代があった(await/async
が登場する以前)
外部サーバーへの通信など、応答を待ちたい処理(同期処理)に対しては、戻り値をそのまま返すのではなく Promise オブジェクトに包んで返すようにする。 Promise
オブジェクトを返すことで、以下のように .then
関数を用いて処理の順番を保証できるようになる。
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.catch(failureCallback);
普通に return
しない代わりに、 resolve()
を使うことで Promise に包んで返すことができる。
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
then
により JavaScript 最初期の function
のネスト地獄に比べてはだいぶ読みやすくなっているものの、ちょっと違和感もある。
async/await
の登場
then を更に使いやすくしたのが async/await
の構文。登場は ECMAScript 2017 以降。
このようにシンプルな構文になる。
async function foo() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
} catch(error) {
failureCallback(error);
}
}f
で、Before/After の形は見てわかるようになったけど、この薄っぺらい理解のままいざ自身の知識としてコードを書こうとすると
async
どこに書けばいいんだっけ。え、ここにもいるの?await
いるの?いらないの?
みたいなことになりがち。いろいろ試行錯誤していたところ、個人的には以下の理解であまり戸惑わないくらいには理解できるようになりました。
- 呼び出し先の関数が
Promise
オブジェクトを返す場合は、await
をつけることでPromise
オブジェクトを解いてくれるし、同期処理を待ってくれる。 await
を使う部分がある場合、その使っているブロックの直近の関数名部分にはasync
をつける。async
が付与された関数はPromise
オブジェクトを返す。- async 内での
return
は、async
を使わない場合にPromise
オブジェクトを返すと際のresulve
に相当する。
const func = async () {
// この場合は result には Promise を解いた戻り値が代入される
const result1 = await doSomething();
// この場合は result には Promise オブジェクトそのものが代入される
const result2 = doSomething();
}
プロミスの使用 - JavaScript | MDN ― https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises
Promise.all
Promise
オブジェクトを配列にまとめて、 Promise.all
に渡すことで全ての Promise オブジェクトを同時に実行することができます。
ただし、単純に Promise.all
してしますと渡したオブジェクト数によっては一気にやりすぎ問題が生じるため、同時実行数を制限する実装が必要です。
Promise オブジェクトの配列を適当な数で小分けして順次 Promise.all
に渡すなどいくつかの実装方法が考えられますが、変に実装してバグっても嫌ですし、素直にNPMのライブラリを利用するのがいいと思います。
NPM パッケージ | Weekly Downlods (2022年2月現在) |
---|---|
p-limit - npm ― https://www.npmjs.com/package/p-limit | 70M |
async-limiter - npm ― https://www.npmjs.com/package/async-limiter | 11M |
TypeScript での Promise オブジェクトの型指定
TypeScript の型指定になれるまでは、都度カンペを見ないと混乱します。 <...>
って何だっけ、などなど・・・。どんな型にでも成り代わることができるジェネリクス(Generics)なわけですが、細かいことを理解するにはなかなか膨大なので都度調べることとして、とりあえず Promise においてはこう書くと覚えれば困らなそうです。
// 文字列の配列を Promise オブジェクトに包んで返す
const func = async (): Promise<string[]> => {
const urls: any = [];
return urls;
}
forEachでは async/await が使えない
forEach()
のループ中では async/await が使えない。 break
でループを止めることもできない。
素直に for...of
で処理しておけばよさそう。
似たような話で、 Array.filter
での条件指定時に Promise オブジェクトを返す処理は使えない。外部通信の結果によて true/false を判定する関数を作って使おうとしたら駄目でした。
参考記事 https://dev.classmethod.jp/articles/foreach-async-await/
まとめ
書けば書くほど愛着が湧いてくる TypeScript。もう Pure JavaScript には戻れない。