Programming TypeScript ch11 Interoperating with JavaScript
- Type Declarations
- Gradually Migrating from JavaScript to Typescript
- Type Lookup for JavaScript
- Using Third-Party JavaScript
- 英語
- 型のない海のなかでTypeScriptの島を作るところから始める
Type Declarations
- .d.tsファイル
- もともと型のないJavaScriptコードに型情報を付与するもの
- 通常のTypeScriptとだいたい同じ。異なるところ:
- 値を含まない
- デフォルト値なども含まない
- 値を含めてはならないが、「値がどこかに存在すること」は宣言できる
declare
- 【補】Cのexternalみたいな
- 利用者から見える型情報のみを記述する
- 関数内部の局所変数の型情報などはexportしない
- 値を含まない
- 例: 以前作った
Maybe
クラス
Maybe.ts
type NotNullOrUndefined = {} class Just<T extends NotNullOrUndefined>{ constructor(private value: T) { } flatMap(f: (value: T) => Nothing): Nothing flatMap<U extends NotNullOrUndefined>(f: (value: T) => Just<U>): Just<U> flatMap<U>(f: (value: T) => Maybe<U>): Maybe<U> { return f(this.value) } getOrElse(_: T): T { return this.value } } class Nothing { flatMap(_: (value: never) => Nothing): Nothing flatMap<U extends NotNullOrUndefined>(_: (value: never) => Just<U>): Nothing flatMap<U>(_: (value: never) => Maybe<U>): Nothing flatMap<U>(_: (value: never) => Nothing|Just<U>|Maybe<U>): Nothing { return this } getOrElse<T extends NotNullOrUndefined>(value: T): T { return value } } type Maybe<T> = T extends NotNullOrUndefined ? Just<T> : Nothing function Maybe<T extends NotNullOrUndefined>(value: T): Just<T> function Maybe(value: null | undefined): Nothing function Maybe<T>(value: T): Maybe<T> function Maybe<T>(value: T): Just<T>|Nothing|Maybe<T> { if (value == null) { return new Nothing } return new Just(value) }
- d.tsファイル生成
tsc -d src/Maybe.ts
declare type NotNullOrUndefined = {}; declare class Just<T extends NotNullOrUndefined> { private value; constructor(value: T); flatMap(f: (value: T) => Nothing): Nothing; flatMap<U extends NotNullOrUndefined>(f: (value: T) => Just<U>): Just<U>; getOrElse(_: T): T; } declare class Nothing { flatMap(_: (value: never) => Nothing): Nothing; flatMap<U extends NotNullOrUndefined>(_: (value: never) => Just<U>): Nothing; flatMap<U>(_: (value: never) => Maybe<U>): Nothing; getOrElse<T extends NotNullOrUndefined>(value: T): T; } declare type Maybe<T> = T extends NotNullOrUndefined ? Just<T> : Nothing; declare function Maybe<T extends NotNullOrUndefined>(value: T): Just<T>; declare function Maybe(value: null | undefined): Nothing; declare function Maybe<T>(value: T): Maybe<T>;
- TSで書かれたライブラリ自身にとってd.tsは無用の長物
- 元のTS実装自体の型情報にアクセスできるので
- 他のライブラリやクライアントコードからアクセスする時にd.tsは有用
- TS/JS両対応のライブラリをバッケージングする際にも有用
- 考えられるパッケージング方法は2つ
- TypeScriptファイルと、コンパイル済JavaScriptファイル両方をパッケージングする
- d.tsファイルと、コンパイル済JavaScriptファイルを別々にパッケージングする
npm install --save-dev @types/xxx
ってやつ
- 後者の利点
- 考えられるパッケージング方法は2つ
- 値を含まない型宣言のことを"ambient"と言って区別することがある
- 型定義ファイルはscript mode
- importしなくていい
Ambient Variable Declarations
process = { // Error: Cannot find name 'myProcess'. Did you mean 'process'? env: { NODE_ENV: 'production' } }
グローバルオブジェクトを拡張したいときに必要:
declare let myProcess: { env: { NODE_ENV: 'production'|'development' } } myProcess = { env: { NODE_ENV: 'production' } }
- tsconfigのlibフィールドは各種d.tsファイルの読み込み設定
- domとかwebworkerとか
Ambient Type Declarations
- 明示的にimportしなくてよくなる
Ambient Module Declarations
- 型のないサードパーティライブラリに型情報を付与する
module-name.ts
export let myExport = 3.14 let myDefaultExport = {a: 'pi'} export default myDefaultExport
types.d.ts
declare module 'module-name' { export type MyType = number export type MyDefaultType = {a: string} export let myExport: MyType let myDefaultExport: MyDefaultType export default myDefaultExport }
index.ts
import ModuleName, {myExport} from './module-name' ModuleName.a // string const b = myExport // number
- モジュールがネストしている場合は
declare module '@most/core'
のようにする - ワイルドカード利用可能
Gradually Migrating from JavaScript to Typescript
- TSは元来JSとの相互運用を念頭に開発された言語
- 痛みは伴うが、JSから漸進的にTSに移行できる
Step 1: Add TSC
tsconfig.json
{ "compilerOptions": { "allowJs": true, ...
- .jsファイルもtscを通すようになる
- 型チェックは行われない
- トランスパイルは行う
Step 2a: Enable Typechecking for JavaScript (Optional)
class A { x = 0 // number method() { this.x = 'foo' } otherMethod() { this.x = ['array', 'of', 'strings'] } }
- tsconfigのcheckJsを有効にする
{ "compilerOptions": { "allowJs": true, "checkJs": true, ...
- エラーを出してくれるようになる
class A { x = 0 // number method() { this.x = 'foo' // Error: Type '"foo"' is not assignable to type 'number'. } otherMethod() { this.x = ['array', 'of', 'strings'] // Error: Type 'string[]' is not assignable to type 'number'. } }
- 既存のコードベースが巨大でエラーが出すぎる場合は、tsconfigのものは無効化し、
@ts-check
アノテーションを用いてファイル単位でチェックする
// @ts-check class A { x = 0 // number method() { this.x = 'foo' // Error: Type '"foo"' is not assignable to type 'number'. } otherMethod() { this.x = ['array', 'of', 'strings'] // Error: Type 'string[]' is not assignable to type 'number'. } }
Step 2b: Add JSDoc Annotations (Optional)
// @ts-check class A { /** * @type {number|string|string[]} x */ x = 0 // number method() { this.x = 'foo' } otherMethod() { this.x = ['array', 'of', 'strings'] } }
- 関数なら
@param
や@returns
が使える- 【補】PHPでいつもやってるアレ
Step 3: Rename Your Files to .ts
index.ts
class A { x: number|string|string[] = 0 method() { this.x = 'foo' } otherMethod() { this.x = ['array', 'of', 'strings'] } }
tsc出力結果
dist/index.js
"use strict"; class A { constructor() { this.x = 0; } method() { this.x = 'foo'; } otherMethod() { this.x = ['array', 'of', 'strings']; } } //# sourceMappingURL=index.js.map
Step 4: Make It strict
- あらかた.jsファイルを.tsファイルに置き換えることができたら
{ "compilerOptions": { "allowJs": false, "checkJs": false, ...
- JSとの相互運用フラグを無効化し、適宜厳しい設定にしていく
- 完全に管理下におけるJSコードベースに型情報を付与していく営みは以上
- 管理下におけない場合 -- 例えばnpmからインストールしたパッケージの場合は?
Type Lookup for JavaScript
- TSからJSをimportするときの規則
ローカルファイル
- importする.jsファイルと同階層の.d.tsファイルを探す
- なければ、tsconfigの
allowJs
とcheckJs
フラグがtrueならば、JSファイルの型推論を行う(JSDoc併用) - これもなければ、全てをanyとして扱う
サードパーティ
- ローカルの型定義(ambient module declarations)を探す。あれば、それを使う
- なければ、package.jsonを参照し、
types
やtypings
フィールドがあれば、指定の型定義を見に行く - なければ、
node_modules/@types/
ディレクトリを走査し、対応する型定義を探すnpm i react
に対応するのはnpm i --save-dev @types/react
- それもなければ、ローカルファイルの1-3を試みる
column: TSC Settings: types and typeRoots
node_modules/@types
以外を走査するようにできる
Using Third-Party JavaScript
- いくつかのパターンがある
JavaScript That Comes with Type Declarations
- 例:
npm i rxjs
ls -l node_modules/rxjs | head -7
total 332 -rw-rw-r-- 1 wand wand 42 Oct 26 1985 AsyncSubject.d.ts -rw-rw-r-- 1 wand wand 261 Oct 26 1985 AsyncSubject.js -rw-rw-r-- 1 wand wand 114 Oct 26 1985 AsyncSubject.js.map -rw-rw-r-- 1 wand wand 45 Oct 26 1985 BehaviorSubject.d.ts -rw-rw-r-- 1 wand wand 267 Oct 26 1985 BehaviorSubject.js -rw-rw-r-- 1 wand wand 120 Oct 26 1985 BehaviorSubject.js.map
JavaScript That Has Type Declarations on DefinitelyTyped
@types
がコミュニティにより保守されている- 不完全だったり不正確だったりすることも。注意
- 例:
npm i @types/lodash --save-dev
ls -l node_modules/@types/lodash/ | head -n 7
total 1624 -rw-rw-r-- 1 wand wand 1141 May 15 19:01 LICENSE -rw-rw-r-- 1 wand wand 838 May 15 19:01 README.md -rw-rw-r-- 1 wand wand 45 May 15 19:01 add.d.ts -rw-rw-r-- 1 wand wand 49 May 15 19:01 after.d.ts -rw-rw-r-- 1 wand wand 45 May 15 19:01 ary.d.ts -rw-rw-r-- 1 wand wand 51 May 15 19:01 assign.d.ts
JavaScript That Doesn't Have Type Declarations on DefinitelyTyped
- そうそうないケース
- 自前のambient module declarationsを作る
- 作ったらコントリビュートしよう
- モジュール開発元に直接コントリビュートする
- DefinitelyTypedリポジトリにコントリビュートする
英語
- leeway
- 余裕、ゆとり
- sleuth
- 探偵
- detour
- 迂回