TypeScript
https://legacy.gitbook.com/book/basarat/typescript
tsconfig.json
paths
import のパス解決は大まかにわけて 2 通りある。
- 相対パス指定(
import Hoge from "./hoge"
): 相対パスで解決 - それ以外(
import Hoge from "hoge"
):node_modules/hoge/index.d.ts
で解決
このうち、相対パス指定以外の場合はパスの解決をtsconfig.json
で上書きできる。
これは、以下の場合などに有効。
@types/***
が存在しないライブラリを利用するときに、自分で用意した型定義を読みたいとき- 相対指定だとパスが長くなる(例:
"../../hoge/fuga"
)のでシンプル(例:@component/fuga
)に書きたいとき
パスの指定は、tsconfig.json
のbaseUrl
とpaths
を設定するとできる。
{
"baseUrl": "src",
"paths": {
"@app/*": [
"app/*"
] /* import hoge from "@app/hoge" と書くと、"src/app/hoge" を見に行くようになる */,
"@config/*": ["app/_config/*"],
"@environment/*": ["environments/*"],
"@shared/*": ["app/_shared/*"],
"@helpers/*": ["helpers/*"]
}
}
文法
Type Guard
戻り値を[引数の名前] is [型名]
とすると、型チェック済みをコンパイラに知らせることができる。
interface User {
name: string;
age: number;
}
// 戻り値が`true`の場合は`some`を`User`型とコンパイラが認識する
const isUser = (some: any): some is User => true;
const hoge = 1;
if (isUser(hoge)) {
// コンパイルエラーにならない
console.log(hoge.name);
}
ジェネリックと組み合わせると汎用的な安全キャストが実装できる。
import Joi from "joi";
const safeCast = <T>(value: any, schema: Joi.ObjectSchema): value is T => {
const error = schema.validate(value, { abortEarly: false }).error;
if (error) {
console.warn(error);
return false;
}
return true;
};
// 使い方
const some = 1;
if (safeCast<Hoge>(some, schema)) {
// ここでsomeはHoge型
console.log(some);
}
Conditional Type
関数の引数
3.0 から使える。
type ArgumentsType<T> = T extends (...args: infer U) => any ? U : never;
Predefined conditional types: 定義済みConditional Types
Exclude<T, U>
: T割り当て可能なタイプから除外するU。Extract<T, U>
: にT割り当て可能なタイプから抽出するU。NonNullable<T>
: 除外nullしてundefinedからT。ReturnType<T>
: 関数型の戻り型を取得してください。InstanceType<T>
: コンストラクタ関数型のインスタンス型を取得します。
Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html https://qiita.com/nahokomatsui/items/856c0cb07a380305521a
型
readonly
を付けるとあとで値が変わらないことが保証されるので、定数型に推論される。
const readonly foo = "Add" // fooの型は"Add"
const bar = "Add" // barの型はstring
Mapped Type
Mapped Type は既存の型のメンバに一括で型変換をしたいときに使う。
既存の型にある特定のプロパティを除いた型を作る
Pick
とExclude
を組み合わせるとできる。
// 既存の型から特定のプロパティを除く型
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// a, b, cの3つをもつ型
type Hoge = {
a: number,
b: number,
c: number
}
// b, cの2つをもつ型になる
const x: Omit<Hoge, 'a'> = {
b: 1,
c: 2
}
Ref: Exclude property from type
既存の型にある特定のプロパティの型を変換する
Pick
とExclude
と&
を組み合わせるとできる。
変換したいプロパティを一度除外してから、好きな型を&
で合成する。
// 既存の型から特定のプロパティを除く型
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Hoge = {a: number, b: number, c: number}
const hoge = {a: 1, b: 1, c: 1}
// Fugaは、Hogeのaを任意のstring型に変換した型
type Fuga = Omit<Hoge, 'a'> & {a?: string}
const fuga1 = {a: 'test', b: 1, c: 1}
const fuga2 = {b: 1, c: 1}
Ref: Exclude property from type
既存の型のプロパティをすべて任意にした型を作る
type Partial<T> = {
[P in keyof T]?: T[P];
}
// a, b, cの3つをもつ型
type Hoge = {
a: number,
b: number,
c: number
}
// a | undefined, b | undefined, c | undefined をもつ型になる
const x: Partial<Hoge> = { b: 1 }
Ref: https://www.typescriptlang.org/docs/handbook/advanced-types.html
tips
Reactコンポーネント関数のプロパティにコンポーネント関数をもたせる
MyComponent
、MyComponent.SubComponent
のようにコンポーネントをグループ化したい場合に便利。
interface Props {}
interface MyComponent extends React.FunctionComponent<Props> {
SubComponent: React.FunctionComponent<Props>
}
const MyComponent = (props => {
return <div>This is my component.</div>
}) as MyComponent
export default MyComponent
再代入不可の型を定義する
interface
のメンバ定義でreadonly
を先頭につける。
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
配列の場合はReadonlyArray<T>
を使う。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
Ref: Interfaces - Readonly properties | TypeScript
const
vs readonly
次のように使い分ける。
const
- 変数に使うreadonly
- プロパティに使う
インタフェースで関数型を表す
関数の引数と戻り値の型のみを書いたinterface
を定義する。
interface Func {
(foo: string, bar: number): boolean
}
関数にプロパティをもたせる
interface FuncWithProps {
(): boolean,
foo: string
}
let f = (() => true) as FuncWithProps
f.foo = 'Hello World'
異なる型を含む配列を返すときは戻り値の型注釈を必ずつけること
型注釈をつけない場合はたとえ型が自明でも複合型の配列型扱いになってしまう。
const mixArray = () => [1, 'a']
// a, bはともに number | string
const [a, b] = mixArray()
この問題は明示的に型注釈を書くことで回避できる。
const mixArray = (): [number, string] => [1, 'a']
// aはnumber, bはstring
const [a, b] = mixArray()
kindプロパティを使って型を判定する
// is.ts
type Cat = { kind: 'cat' }
type Dog = { kind: 'dog' }
type Mouse = { kind: 'mouse' }
const is = (kind: string) => (obj: any) => obj != null $$ obj.kind === kind
const cat = is('cat')
const dog = is('dog')
const mouse = is('mouse')
使う側:
import * as is from './is'
const cat = { kind: 'cat' }
console.log(is.cat(cat))
Ref: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
文字列定数を文字列リテラル型として使う
typeof [変数名]
でできる。
const HOGE = 'aaa'
const FUGA = 'bbb'
type HogeOrFuga = typeof HOGE | typeof FUGA
// これは以下と同じ
// type HogeOrFuga = 'aaa' | 'bbb'
読み取り専用
const readOnlyArray: ReadOnlyArray<number> = [1, 2, 3];
readOnlyArray.push(1); // コンパイルエラー
type vs interface
type がおすすめされていた。 Interface vs Type alias in TypeScript 2.7
任意の型パラメータ
= {}
を書けばいい。
const func = <T, U = {}>(val1: T, val2: U)
// 普通に使う場合
func<number, string>(123, 'hoge')
// 第二パラメータはstringに推論される
func<number>(123, 'hoge')
ただし、型を省略して呼び出すと返却型が{}
になってしまうことがある。例えばこのような場合。
const func = <T, U = {}>(val1: T, val2: U): U
// 問題なし
const a: string = func<number, string>(1, 'hoge')
// 問題あり:第二引数に'hoge'を入れているため戻り値をstringと推論してほしいが、デフォルト値`{}`が勝ってしまっている
const b: {} = func<number>(1, 'hoge')
そういう時はExclude<T, U>
を使い、{}
を除外する。
const func = <T, U = {}>(val1: T, val2: U): Exclude<U, {}>
ref: Typescript optional generic type
既存のプロパティを一括でnullableにする
Partical<T>
を使う。
interface Hoge {
a: number,
b: string,
c: boolean
}
const hoge: Partical<Hoge> = {b: 'aaa'}
アロー関数でオーバーロード
関数の型注釈を、すべてのオーバーロード関数をもつオブジェクトとして定義するとできる。
const handleEvent: {
(event: HogeEvent): void;
(event: FugaEvent): void;
(event: PiyoEvent): void;
} = (event: HogeEvent | FugaEvent | PiyoEvent) => {
console.log(event);
};
ref: Can I use TypeScript overloads when using fat arrow syntax for class methods?
型定義ファイル
d.ts
ファイルを好きな場所に置くと、特にimport
などしなくても自動で型定義を読み込んでくれる。
https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
グローバル変数
declare var foo: number;
declare let foo: number;
declare const foo: number;
グローバル関数
declare function greet(greeting: string): void;
プロパティをもつオブジェクト
declare namespace myLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}
関数
export function myFunc<T>(a: number): T;
使う側
let result = myLib.makeGreeting("hello, world");
console.log("The computed greeting is:" + result);
let count = myLib.numberOfGreetings;
オーバーロード関数
宣言
declare function getWidget(n: number): Widget;
declare function getWidget(s: string): Widget[];
コード
let x: Widget = getWidget(43);
let arr: Widget[] = getWidget("all of them");