mizchi / TypeScript Meetup 2
- mizchi / 竹馬光太郎
- フロントエンドと Node.js
- あとはググって
- Modern JS ≒ TypeScript の時代になった
- なので型を書け
- TypeScript を導入しない言い訳を全部潰す
- そのために痛みがない導入・運用を提示する
- TS の型アノテーションとはなにか?
- 導入編
- 発展編
- アンチパターン
- メモリ確保量を決めるもの、ではない
- TS の型はインターフェースしか知らない
- 実行時の挙動を決めるもの、ではない
- TS の型宣言はランタイムに関与しない
// TS は ArrayBuffer であることを知っているが
// VM で確保されるメモリに興味がない
const buf: ArrayBuffer = new ArrayBuffer(8);
- 人間のためのインターフェース宣言
- 実行可能な Lint
- それらによくコード品質の向上・レビューコストの削減
主機能
- 型の静的検査
- エディタへの情報提供 - Language Server Protocol
おまけ機能 (babel で代替可能)
- 型アノテーションの除去
- ES2015 => ES5
「Java の静的型付けが大変で、反動で動的なのが流行ったけど、 推論あればそうでもなかった。むしろドキュメントとして有用。 でも動的が流行ったあとだから、一旦は漸進的型付けで行く」
$ npm install typescript webpack webpack-cli ts-loader --save-dev
最小 tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"esModuleInterop": true
}
}
module.exports = {
resolve: {
extensions: [".ts"]
},
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: "ts-loader",
options: {
transpileOnly: true // 型チェックしない!!!
}
}
]
}
]
}
};
src/index.ts
// 型はわざと間違ってる。アノテーションが取り除かれることを確認する
const text: number = "World";
console.log(`Hello, ${text}`);
$ npx webpack # build only
# => dist/main.js
$ npx tsc -p . --noEmit # type check only
エラーを確認
src/index.ts:1:7 - error TS2322: Type '"World"' is not assignable to type 'number'.
1 const text: number = "World";
- 「コードを修正せずに」自分の
.js
を.ts
にする - 「CI で型違反を検査せずに」ふるまいを手動/ユニットテストで確認
- 初手はコンパイラとしての機能を保証する
- どうせ最初は型チェックを通せない
- 後々効く: コンパイルと型チェックの分離で高速化
- どうせ後でも IDE で型違反を見る
- どうせ後でも CI で型チェックする
(awesome-typescript-loader やめとけ)
$ npm install --save-dev @types/<pkg-you-want>
自分がほしいやつを一通り叩いてみる (一部のライブラリは TS 型定義を同梱)
動作確認
import { range } from "lodash";
range(3);
たぶん全部の型定義ファイルは揃わないので一旦潰す
src/decls.d.ts
declare module "xxx"; // xxx がない or 合わせるのがきつい
// ちょっと頑張る場合
declare module "yyy" {
export function foo(input: string): number;
}
tsc -p . --noEmit
で落ちた場所を修正していく- 読み下しながら自明な範囲で
number
やstring
を付与する - わからなかったら
any
や@ts-ignore
で潰す - ロジックを変更しない!!!!
function genMagicId(): number | string {
// @ts-ignore
return super_magical_func();
}
- 本当に守りたいのは関数の入出力
- 暗黒面で心折れるぐらいなら any で全部潰す
const foo: Foo = (foobar as any) as Foo;
- 推論過程が導けない場合に仕方なく書くもの
- 多用厳禁
- 雑に修正したら CI でテストを流す
.circleci/config.yml
steps:
...
- run: npx tsc -p . --noEmit
circleci ない人は husky などで頑張って
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"esModuleInterop": true,
// 段階的に有効化
"strict": false, // "use strict" 有効化
"strictNullChecks": false, // null|undefined 厳格化
"noImplicitAny": false // 推論不可能なときにアノテーション必須に
}
}
↓ ほど難易度高い
- 型とロジックを同時に修正しない
- 推論などで発見すると嬉しくて修正したくなる
- => 確認の工数がかかる
- => マージされない
- => 治安悪い状態が続く
- 推論などで発見すると嬉しくて修正したくなる
- 別 Issue にしましょう
- 型付けるとコスパ良い順
- ORM 周り (node.js)
- API レスポンスの返り値
- モデル層(redux/vuex)
- View の入力(React/Vue Props)
- View ステート層(React state / Vue data)
- 本当に守りたいのは(たぶん)データベースと API
- スキーマ定義から型定義生成ツールがあると良い
- grpc => .d.ts
- graphql => .d.ts
- jsonschema => .d.ts
- 自作
const id: string = number_or_string as string;
- 本当に型の契約が守れてるかが自己責任
- 新規にコードを書く際は Type Refinements (if /switch による union type 絞り込み) を優先
const initialState = {...};
export type State = typeof initialState;
- 型とインスタンスの従属関係が逆
- 本当に管理したいのは潜在的に State のとりうる状態
export class StateManager<A, B, C, D, E, F> {...}
- 人間が管理できる型引数はたぶん 2 つぐらいまで
- ライブラリ作者は 3 つ、アプリケーション内なら 2 つまでが感覚的なセーフライン
export type Action = {
type: string;
payload: any;
};
- 間違っちゃいないんだけど union type でもっと詳細に書ける
import module = require("module");
- ESModules 導入以前の独自モジュールシステム。新規で書く場合には不要