- 型のない海のなかで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
ってやつ
- 後者の利点
- パッケージサイズが小さくて済む
- importがambiguousにならない
-
アプリケーションのコンパイル時間短縮
- アプリケーションをコンパイルするたびにライブラリのTSファイルを再コンパイルしなくて済む
- 値を含まない型宣言のことを”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
[https://github.com/DefinitelyTyped/DefinitelyTyped:embed:cite]
-
@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
- 迂回