Effective TypeScript, 2nd Edition/読書メモ
23 エイリアスの使用に一貫性を持たせる
const { prop } = obj;
someFunc(obj)
とするとpropが破壊される可能性がある。obj自体を別の場所で操作されてしまう可能性があるから。
処理は簡潔にかけるのだが。
22 タイプの絞り込みを理解する
func A(obj: { value: stirng | number }) {
if (typeof obje.value === 'number') {
setTimtoue(() => { obje.value.toFixed() }, 10);
}
}
これはエラーになる可能性がある。なぜなら、valueがsetTimeout実行前に変更されうるから
25 型推論でコンテキストがどのように使われるかを理解する
let lang = 'Japanesel; // string
setLang(lang); // langのunionを求めている場合エラー
langが変更されうるため、型がstringとしてさだまるから?setLang実行時の値から推論されない。
これはconstを使うと改善される
タプル
const a = [1, 10];
function b(value: [number, number]) {}
b(a);
はエラー。aがnumber[]
として推測されるから。as const
でもエラーは回避できない。
なぜなら具体的すぎるから。readonly[1, 10]
として扱われている。
bの型を直すのが良い。
他には
b([10, 20])
と書けば当然OK。inlneな書き方。
Object
同じことが起こる。satisfiesを使うと良い。Object初期化時の具体的な値が保持される。
Callback
function withcallback(c: (a: number, b:number) => void) {};
withcallback((a, b) => { console.log(a + b); })
aとbはnumberとして推論される。callbackを別の場所で定義したらanyとなる。
面倒ならinlineで(使い場所で)定義してしまうといい。
25 進化するタイプを理解する
処理の途中で型が進化することがある。any[]がnumber[]になったり、nullがnumber | nullになったりする。
function a() {
let b = [];
for (let i = 0; i < 10; i++) {
b.push(i);
}
return b;
}
bはany[]からnumber[]になる
function a() {
let b = null;
if (Math.random() > 0.5) {
b = 'hoge';
}
return b;
}
これを避けるには明示的に型注釈を戻り値にいれると良い
26 型の流れを助けるために関数型コンストラクトとライブラリを使う
型を考えるとlodashのようなライブラリの採用はメリットがある。ライブラリを使わない場合は方注釈が必要となる
27 コールバックの代わりに非同期関数を使用して型フローを改善する
async
を使うことで非同期に処理が統一されるため、利用側がシンプルになる。例えばfetchにキャッシュ機構を入れた場合に、asyncだとそのまま返すだけでインタフェースは非同期のまま
ちなみにasync functinoからPromiseを返しても別のPromiseにwrapされることはないので安心
項目28: クラスとカリー化を使用して新しい推論サイトを作成する
type Some = {};
type SomeAPI = {
'/hoge': Some
}
declare function fetchAPI<API, Path extends keyof API>(path: Path): Promise<API[Path]>;
const result = fetchAPI<SomeAPI, '/hoge'>('/hoge')
これは冗長すぎる。カリー化することで簡潔にかける
type Some = {};
type SomeAPI = {
'/hoge': Some
}
declare function fetchAPI<API>(): <Path extends keyof API>(path: Path) => Promise<API[Path]>;
const someFetcher = fetchAPI<SomeAPI>();
const result = someFetcher('/hoge')
項目 29: 常に有効な状態を表す型を好む
個別の状態から、一つの状態を導く出すのは難しい。例えば、ページの状態を表す
- isLoading
- error
の2つがあったとして、それらを見ながら描画する画面を作るのは難しい。また、それぞれのステータスの更新を忘れてしまうことともある。そのため、 1つの状態を持つほうがよい
type ErrorState = {
type: 'error',
message: string;
}
type LoadingState = {
type: 'loading'
}
type SuccessState = {
type: 'success',
content: 'content'
}
type State = ErrorState | LoadingState | SuccessState;
項目30:受け入れるものは自由に、生み出すものは厳格に
Optionalな引数を受け取らざるを得ないケースがあったとしても、戻り地は明確な値にすべき
type SomeType = {
a: string;
b: number
};
type SomeTypeOptions = Partial<SomeType>;
declare function setOption(opt: SomeTypeOptions): SomeType;
単に反復処理をしたい場合はIterableを受け取る
ArrayLike
や generator
を処理することができるため。
declare function sum(values: Iterable<number>): number;
内部の処理では for-of loop
を使えばよい
項目31: ドキュメントでタイプ情報を 繰り返さない
型を見ればわかることはコメントに残さない。
例えば、破壊的変更をしないソート関数を実装する場合、その旨をコメントに残すよりreadonly
の値を受け取った方が良い。もし仮に破壊的な変更を内部でしてしまってもエラーで気づける。
項目2:使用しているTypeScriptオプションを知る
- noImplicitAnyはJSからの移行などを除きONにすべき
- noImplicitAnyとstrictNullChecksを導入するとしたら先にnoImplicitAny
項目3:コード生成は型に依存しないこと を理解する
実行時にTypeScriptの型をチェックし、処理を分岐することはできない。コレを回避するには
- プロパティをチェックする
- タグ付きユニオンを用いる
- クラスを用いる
などの方法がある
TypeScriptはエラーが有ってもコンパイルすることができる
TSではオーバーロードを実際のコードで記述することはできないが、型としては定義できる。
実装は一つしかできない。