仕事ですぐに使えるTypeScript その11
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「非同期処理」。
著作権者:フューチャー株式会社(Future Corporation)様
非同期処理
非同期とは何か
- then()は新しいPromiseオブジェクトを返すので、.thenのメソッドチェーンで繋げることができる。
- thenの中でreturnしたものは、次のthenの中で受け取ることができる。
その他は認識通り。
非同期処理の基本
コンピューターの処理速度
- 計算はCPUで行われる。
- 命令を実行する単位を1サイクルという。
- 1サイクルはおおよそ0.3ナノ秒で計算される。Floatの足し算は1.2ナノ秒。(1サイクル1秒とすると4秒)
- プログラム実行中のデータはPCのメモリ上に置かれている。計算を実行する際はメモリにアクセスして値を取ってくる必要がある。(1サイクル1秒とすると6分)
- SSDからのデータ読み取りは1000ミリ秒。メモリから取得するよりもSSDからデータの読み取りする方は1000倍ほど時間がかかる。(1サイクル1秒とすると4日)
- サンフランシスコをインターネット送受信する場合は100ミリ秒。(1サイクル1秒とすると10年)
- 要するに、ファイルを開くとか、インターネット送受信するのはCPUで計算するよりも、物凄い時間がかかる。まずスケール間を把握することが大事。
同期処理・非同期処理とは
同期処理
- コードがかかれた順序で実行する。どんなに時間がかかったとしても次の実行を待つ。
非同期処理
- 時間がかかる処理を実行しつつ、次の処理を実行する。つまり、同時に複数の処理を進めるということ。
- CPUを待たずに他の処理をすることができるので効率的。
非同期処理で考えなければならないこと
- 同時並行で処理した場合、どちらが先に終わるのかがわからない。
- 人間は柔軟に思考できるが、プログラムはどちらが先に終わっても正常に動作するようコードを書かなければならない。
コールバック関数とは
- ○○が終わったら、××をやってください。の××がコールバック関数。
- 関数の引数に渡された関数をコールバック関数という。
Promise
3つの要素
- 処理(時間がかかる処理)
- 処理が成功した時の値
処理が失敗した時の値
Promiseオブジェクトは受け取っても処理内容が確定していない。
- Promiseオブジェクトには値が確定できたら、呼ばれる処理を登録できる。(thenで)
- Promiseはコールバック関数のシンタックスシュガー(異なる構文だけど同じ意味のもので、楽にかける方をシンタックスシュガーになる)
- forやwhileはシンタックスシュガーの関係にある。
const res = fetch("https://api.jikan.moe/v3/search/anime?q=Kimetsu&limit10"); // > ここのresではまだ処理が完了していない(pending)の状態のPromiseオブジェクトが帰ってくる res.then(res => { console.log(res); }) // > fetchし終わったら、thenで登録したコールバック関数を呼び出す。
- thenメソッドは新たにPromiseオブジェクトを返すのでメソッドチェーンを作成できる。
- thenチェーンの中でエラーが発生したら、以降のthenを全てスキップし、catch節が呼ばれる。
- また、catchの後にthenを登録すると、エラーが発生しても発生しなくても
- thenのなかにthenを書く必要はなし。ネストすると、promiseオブジェクトをreturnしなければならず、ミスが発生しやすい。
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) sleep(2000) .then(() => console.log("2秒たったよ")) .then(() => console.log("2秒たったよ")) .then(() => console.log("2秒たったよ")) .then(() => console.log("2秒たったよ")) .then(() => console.log("2秒たったよ")) .catch(() => console.log("失敗"))
上記の例の場合は、下記のようにコンソールを確認することで、同時にAPIが発行されていることがわかる。
Promise 応用編
function waitOneMinute() { return new Promise((resolve, reject) => { setTimeout(() => { resolve("成功です") }, 2000) }) } waitOneMinute() .then(res => { console.log(res); }) .catch(err => { console.log(err); })
仕事ですぐに使えるTypeScript その10
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「クラス」。
著作権者:フューチャー株式会社(Future Corporation)様
クラス
- javascirptのクラスは下記のように表現することができる。
class Animal { // プロパティ animalType: string; // コンストラクタ constructor() { this.animalType = "大型" } say() { console.log(`${this.animalType} の種類の動物です。`); } } const a = new Animal(); a.say(); // > の種類の動物です。
アクセス制御
- TypeScriptにはpublic, protected, public装飾子がある。
- 特に装飾子を付けない場合は、publicとなる。
- privateは定義があるクラス以外からの呼び出し禁止、protectedは定義があるクラスとその子クラスからのみ呼び出し可能。
- 昔は変数の前に_(アンスコ)を付けて仕組み上はアクセスできるけど使用しないようにする等、トリックを使ってprivateの宣言を再現しようとしていた。
- protectedは用意されているけど、typescriptで階層が深くなる継承をすることはあまりないので使うことはない。
class Cat { private food: string; private place: string; dig() { return `${this.place}を掘って${this.food}を見つける`; } bury(f: string, p: string) { this.food = f; this.place = p; } } const cat = new Cat(); cat.bury("魚", "空き地"); console.log(cat.dig()); // > 空き地を掘って魚を見つける
コンストラクタ
- 認識の通り
static メンバー
- 基本的な概念は認識通り。
- staticを使用するのは、インスタンスを作る特別なファクトリーメソッドを実装するぐらいらしい。
継承/インターフェース宣言
- 継承の記載方法は下記の通り。
class Cat { eat() { console.log("餌の量は100gです"); } walk() { console.log("歩く") } } class smallCat extends Cat{ eat() { console.log("餌の量は50gです"); } } const tama = new smallCat(); tama.eat(); // > 餌の量は50gです tama.walk(); // > 歩く const cat = new Cat(); cat.eat(); // > 餌の量は100gです cat.walk(); // > 歩く
- interfaceの実装方法は下記の通り。
interface Animal { eat(); walk(); sleep(): void; // 返り値がない場合はvoidを付けたりすることも可能。 } class Cat implements Animal { eat() { console.log("餌の量は100gです"); } walk() { console.log("歩く") } sleep() { console.log("眠る") } }
クラスとインターフェースの違い・使い分け
違いは下記の通り。
仕事ですぐに使えるTypeScript その9
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「基本的な型付け」。
著作権者:フューチャー株式会社(Future Corporation)様
基本的な型付け
一番手抜きな型付け: any
- anyを積極的に使用する場合は、すでにJavaScriptとして動作していて実績があるコードをTypeScriptに持ってくる時。 - しかし、anyをする場合はTypeScriptが提供する型チェックの恩恵を受けることはできない。 - TypeScirptではデータが発生する場所で型情報をつければ伝搬されるので、なるべく早く型情報を付けるのが良い。 - 標準ライブラリのfetch関数のレスポンスは外部からのレスポンスになるため、型情報がわからないため、anyとなっている。
未知の型: unknown
- 基本的な振る舞いはunknownはanyと似ているが、異なる点としてはunknown型の変数を使用する時に 型アサーション
が必要になるということ。
型に名前をつける
- type 名前 =
で型に名前をつけることができる。
例
// 複数の型を指定することができる type BirthMonth = number | string; const birthMonthNum: BirthMonth = 11; // > OK const birthMonthStr: BirthMonth = '11月'; // > OK <200b> // 文字列の型も指定することができる。 type redFruits = 'りんご' | 'いちご'; const apple: redFruits = 'りんご'; // > OK const strawberry: redFruits = 'いちご'; // > OK <200b> // 指定した文字以外は代入することができない const banana: redFruits = 'バナナ'; // > ERROR: TSXXXX
関数のレスポンスや引数で使用するオブジェクトの定義
- typeはオブジェクトが保持すべき属性の定義も使用することができる。
type Person = { name: string, favorite: string, old: number } <200b> const p: Person = { name: '佐藤', favorite: 'ハリーポッター', old: 30 } <200b> // 上記のように型定義をしておくと、オブジェクトを生成した時に項目が不足している場合は、エラーが発生する。 const p: Person = { name: '鈴木' } }; // error TS2741: Property 'favorite' is missing in // type '{ name: string; }' but required in type 'Person'.
オブジェクトの属性の就職: オプション、読み込み専用
- オブジェクトの型定義を行う際に、名前の前に
readonly
を付与することで、属性の値が読み込み専用になり、書き込もうとするとエラーにすることができる。 - また、名前の後ろに?(クエスチョンマーク)をつけtることで、そのオブジェクトでは省略可能な属性であることを示すことができる。
type Person = { name: string, readonly personID: number, old?: number }
型ユーティリティー
- Partial
を利用することで、一度定義した型のすべての属性に一括して?(クエスチョンマーク)をつけて任意項目にすることができる。
type Person = { name: string, favorite: string, old: number } <200b> const p: Partial<Person> = { name: 'hogehoge' } // > Partialユーティリティを使用しているので、name, favorite, oldにはクエスチョンマークが付いている状態になっている。 // > すべて任意項目なので、オブジェクトを生成するときは指定しなくてもOKになる。 <200b> const p: Readonly<Person> = { name: 'hoge', favorite: 'fuga', old: 30 } p.name = 'can not change'; // > Cannot assign to 'favorite' because it is a read-only property.
属性名が可変のオブジェクトを扱う
- 基本的に辞書型のようなオブジェクトを使用したい場合は、Mapを使用したりイテレータを使用することが多いですが、外部システムとの連携の兼ね合いから辞書型で返却される場合があるので、そういった場合は下記のように型定義を書くことで辞書のように扱われるオブジェクトの宣言を行うことができる。
- 下記では郵便番号のハイフンを抜くことで
key
をnumber
型で宣言することができる。しかしながら、そもそもJavaScriptのオブジェクトのキーは文字列型(string)のため、キーをObject.keys()
等で抜き出したときは、number
型ではなくstring
型になることに注意する。
const postalCodes: { [key: string]: string } = { '100-0000': '東京都千代田区', '200-0000': '千葉県千葉市', '300-0000': '茨城県つくば市' }
AかつBでなければならない型定義
- 下記のように&(アンパサンド)で型定義を行うものを交差型と呼ばれている。
type Apple = { appleID: string } <200b> type Facebook = { facebookID: string } <200b> const ID: Apple & Facebook = { appleID: 'hoge@icloud.com', facebookID: 'fuga@facebook.com' }
タグ付き合併型: パラメータの値によって必要な属性が変わる柔軟な型定義を行う
直近すぐに業務に必要になりそうなことはなさそうなので、後日読む https://future-architect.github.io/typescript-guide/typing.html#id7
型ガード
- そもそも型ガード
とはなにか、それは型を「数字型 or 文字列型」で定義した時など、複数の型の変数を扱う場合に、この型の場合はこのロジックを適用したい、というもの。
- TypeScriptは型情報を除くとJavaScriptになる。TypeScriptのコンパイラが保持するインターフェースやtypeなどは、実行時のランタイムには存在脚ないので、このオブジェクトがこのインターフェースを保持しているとき、という実行文は記述することができない。
- これを解決するため、TypeScriptでは型ガードという機能がある。
- typeof は変数の型名をstringで返す。
let userNameOrId: string | number; if (typeof userNameOrId === "string") { console.log("string") } else { const id = this.repository.getUserName(userNameOrId); console.log(id); }
型アサーション
- TypeScriptには型アサーションという機能がある。他言語でいうところのキャストである。(asを後置する)
- これはコンパイラのもつ型情報を上書きするもので、実行時に情報を一切参照せずにただ変数の方を変更する。
const page: any = { name: "sample page" } const name: string = page as string;
仕事ですぐに使えるTypeScript その8
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「複合型」。
著作権者:フューチャー株式会社(Future Corporation)様
console.logによる出力
三行サマリ
- console.logの引数に
{}
を使用することで変数名と値がペアになって出力される。 - console.tableでは複雑なオブジェクトを表形式で確認することができる。
- console.dirでは複雑なオブジェクトをツリー形式で確認することができる。
console.log
何かの変数を出力したい時は、{}
で描こうと変数名と値がペアになって出力され、見やすくなる。
const hoge = "fuga"; console.log(hoge); // > fuga console.log({hoge}); // > { hoge: 'fuga' }
console.table/dir()
- 複雑なオブジェクトを
console.table()
は表形式に、console.dir()
はツリー形式で表示することができる。
console.table()
const sample = { hoge: "hogeStr", fuga: "fugaStr", tree1: { hoge: "hoge", tree2: { hoge: "hoge", tree3: { hoge: "hoge" } } } } console.table(sample);
console.dir(sample)
仕事ですぐに使えるTypeScript その7
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「複合型」。
著作権者:フューチャー株式会社(Future Corporation)様
複合型
三行サマリ
- 他のプリミティブ型、もしくは複合型自身を内部で保持し、大きなデータを定義するデータ型をプリミティブ型という。プリミティブ型には配列やオブジェクトが該当する。
タプル型
- Javaでは配列の中身は同じ型になる。しかし、TypeScriptでは1つ目の要素はstring, 二つ目の要素はnumberといった、複数の型を格納できるデータ型を「タプル型」という。
- また、1つは必ず要素があり、2つ以上の要素が格納できるタプルを表現したい場合は、
[string, ...string[]]
と記載することで表現することができる。
配列からのデータの取り出し
- 以前は配列やオブジェクトの要素を変数に取り出す際は一つずつしか取り出すことができなかったが、現在のJavaScriptでは分割代入で複数の要素をまとめて取り出すことができる。
const fruits = [ "apple", "kabos", "banana" ]; const [red, green, yellow] = fruits; console.log(red); // > apple console.log(green); // > kabos console.log(yellow); // > banana const [, ...other] = fruits // > [ 'kabos', 'banana' ]
配列の要素の存在チェック
- 以前はindexOfを利用して判定していたが、現在は
includes()
メソッドが入ったので、これを利用していった方が良い。
配列の加工
- 最近のJavaScriptでは、排t列やオブジェクトを直接加工するのではなく、値が変更されたコピーを別で作成し、最後にリプレイスする方法が取られている。
- スプレッド構文内でspliceを使用することで、配列のコピーも簡単に行うことができる。
配列のソート
- 配列のソートにはsort()を使用する。
- sortをする対象に数値が入っている場合は期待と異なる動作をするため、比較関数を引数に設定し、0より小さい数値(左辺を左側に)、0(等価)、0より大きい数値(左辺を右側に移動)を返すことで要素の並び替えのルールを設定することができる。
- for文で繰り返す際は従来のfor文の実行速度が最も早く、それよりもfor...ofは速度面で劣ると言われている。
読み込み専用の配列
- TypeScriptの
const
は変数の再代入をさせないガードにはなるが、変更不可にすることはできない。どういうことかというと、配列をconstで定義したとしても中の値は変更しうる可能性があるということ。
const fruits: string[] = [ "apple", "kabos", "banana" ]; fruits.push("orange"); console.log(fruits); // > [ 'apple', 'kabos', 'banana', 'orange' ]
constであっても上記のように配列に要素を追加することができるため、中の要素を変更したくない場合はreadonlyを定義することで変更不可にすることができる。
const fruits: readonly string[] = [ "apple", "kabos", "banana" ]; fruits.push("orange"); console.log(fruits); // > error TS2339: Property 'push' does not exist on type 'readonly string[]'.
readonlyを無理やり外したりせずに自然に使うためには、ソースコード全体でreadonlyを使用するように徹底する、もしくは全く使用しないようにする、二者択一になる。利用しているライブラリでもreadonlyを使用していないこともあるので、プロジェクト全体での意思統一が必要になる部分。
TypeScriptと配列
- for..ofは従来のfor文よりもパフォーマンスは劣ると上記で記載をしたが、TypeScriptでfor..ofを使用している場合は、ES5への出力の場合は型情報を見て旧来のfor文がコンパイルされるので、速度上のペナルティがない状態で、最新の構文が使えるメリットがある。
JSON(JavaScript Object Notation)
- JSONをパースするとオブジェクトと配列で階層構造になったデータが出来上がるが、通信用のライブラリではパース済みの状態でレスポンスが返ってくることもある。
- JSONのキーは必ずダブルクォーテーションで囲む必要があり、配列やオブジェクトの末尾にカンマがあるとエラーになる。そのJSONをJSON.parseを行うと、Syntax Errorの例外が発生する。
オブジェクトの加工
const smallAnimal = { name: "犬" }; const attributes = { job: "パート", nearStation: "東京駅" }; // 最近の傾向としてはObject.assignを使用するよりもスプレッド構文でコピーすることが多い。 // 旧 const copy1 = (<any>Object).assign({}, smallAnimal); console.log(copy1); // > { name: '犬' } // 新 const copy2 = {...smallAnimal}; console.log(copy2); // > { name: '犬' } // オブジェクトのマージもObject.assignよりはスプレッド構文を使用する傾向 // 旧 const merge1 = (<any>Object).assign({}, smallAnimal, attributes); console.log(merge1); // > { name: '犬', job: 'パート', nearStation: '東京駅' } // 新 const merge2 = {...smallAnimal, ...attributes}; console.log(merge2); // > { name: '犬', job: 'パート', nearStation: '東京駅' } 参考:https://stackoverflow.com/questions/35959372/property-assign-does-not-exist-on-type-objectconstructor
Mapを使う
// 旧 var map1 = { "北海道": "ホッカイドウ", "青森": "アオモリ" } for (var key in map1) { if (map1.hasOwnProperty(key)) { console.log(key + " : " + map1[key]); } } // > 北海道 : ホッカイドウ // > 青森 : アオモリ / 新 const map2 = new Map<string, string>([ ["北海道", "ホッカイドウ"], ["青森", "アオモリ"] ]) for (const [key, value] of map2) { console.log(`${key} : ${value}`); } // > 北海道 : ホッカイドウ // > 青森 : アオモリ
読み込み専用のオブジェクト
- 配列はreadonlyをつけることで、中の要素も変更することができないよう定義することができたが、オブジェクトでも同様のことを行うことが可能。
- しかし、配列で定義したようにreadonlyキーワードでは対応することができないので、型ユーティリティのReadOnly<>を使用する。
type User = { name: string, age: number } const u: Readonly<User> = { name: "田中", age: 10 } console.log(u); // > u.name = "佐藤"; // > error TS2540: Cannot assign to 'name' because it is a read-only property.
仕事ですぐに使えるTypeScript その6
目的
学習の記録と学びになった部分をアウトプットするため。
期限
対象書籍の読了・内容理解が8月中に完了していること。
対象
今回の対象は「関数」。
著作権者:フューチャー株式会社(Future Corporation)様
関数
三行サマリ
アロー関数
- コールバック関数に無名関数を渡したとき、コールバック関数の中でthisを参照しようとすると、正しく取得できない問題があったが、アロー関数を使うことでその関数が定義された場所のthisの保持まで行うことができる。
- 以前はコールバック関数に無名関数を渡す時はthisが取得できなくなってしまうため、bind()をしようして束縛したりしていた。また、
var self = this;
がバッドノウハウとして有名だった。
// 旧: 無名関数のイベントハンドラではその関数が宣言されたところのthisにアクセスできない var self=this; this.button.addEventListener("click", function() { self.smallAnimal.walkTo("hogehoge"); }); // 旧: bind()で現在のthisに強制束縛 this.button.addEventListener("click", (function() { this.smallAnimal.walkTo("fugafuga"); }).bind(this));
関数の引数と返り値の型定義
- Javaでは引数の数によって同じ関数名でメソッドを定義することも可能ではあるが、TypeScriptではそれは実現することはできない。
function f({name="cat", fav="おにぎり"}={}) { console.log(`${name}: ${fav}`); } const hoge = { name: "dog", height: 180 } f(hoge); > dog: おにぎり
関数を含むオブジェクトの定義方法
const smallAnimal = { getName() { return "小動物" }, _favorite: "お気に入り", get favorite() { return this._favorite; }, set favorite(favorite) { this._favorite = favorite; } } console.log(smallAnimal.getName()); smallAnimal.favorite = 'favorite'; // smallAnima.favorite('favorite') <- ではない。smallAnimal.favorite = 'favorite'; でsetterが呼び出されている。 console.log(smallAnimal.favorite);
thisを操作するコードは書かない
- JavaScriptのthisは様々な種類があり、違いを知って使いこなせるのがかつてのJavaScriptの上級者だったが、こういったコードはなるべく使わないよう済ませた方がいい。
- また、スコープ街に非公開にしたい変数を見えなくするよう関数をその場で作って実行する即時実行関数という技があったが、今時であれば公開したい要素に明示的にexportをつけることでwebpackなどのバンドルツールがそれ以外の変数をファイル単位のスコープで隠してくれるので、基本的に即時事項関数は使用しない方がいい。