カテゴリー
個人的Typescriptの型入門③ 〜 Typescriptの型のディープな世界
※ 当ページには【広告/PR】を含む場合があります。
2024/09/10
目次
- 1. Typescriptによる「型プログラミング」
- 1-1. keyof演算子
- 1-2. symbol型をプロパティに含むオブジェクトへのkeyof
- 1-3. keyof型の注意点
- 1-4. インデックスシグネチャとkeyof型
- 1-5. Lookup Types
- 1-6. keyofとLookup Typesを組み合わせた関数
- 1-7. Mapped Types
- 1-8. Mapped Typesの利用法
- 1-9. Mapped Typesとプロパティ修飾子
- 1-10. Mapped Typesで修飾子を取り除く
- 1-11. Mapped Typesの応用
- 1-12. Mapped Typesと引数の位置
- 1-13. Conditional Types
- 1-14. Mapped Typesの限界とConditional Types
- 1-15. Mapped TypesとConditional Typesを組み合わせて型操作する
- 1-16. Conditional Typesの遅延評価
- 1-17. Conditional Typesにおける型抽出(infer)
- 1-18. inferの重ね掛け
- 1-19. Conditional Typesとinferによる文字列マッチング
- 2. まとめ
Typescriptによる「型プログラミング」
keyof演算子
T
keyof T
interface MyObj {
foo: string;
bar: number;
}
//keyは'foo' | 'bar'型
let key: keyof MyObj;
key = 'foo';
key = 'bar';
//✘ Type '"baz"' is not assignable to type '"foo" | "bar"'.
key = 'baz';
MyObj
keyof MyObj
'foo' | 'bar'
keyof MyObj
symbol型をプロパティに含むオブジェクトへのkeyof
keyof
//新しいシンボルを作成
const symb = Symbol();
const obj = {
foo: 'str',
[symb]: 'symb',
};
//'foo' | typeof symb 型
type ObjType = keyof (typeof obj);
typeof
keyof
ObjType
'foo' | typeof symb
keyof
typeof <シンボル名>
symb
'foo' | symbol
keyof型の注意点
const obj = {
foo: 'str',
0: 'num',
};
//0 | 'foo'型
type ObjType = keyof (typeof obj);
インデックスシグネチャとkeyof型
keyof
interface MyObj {
[foo: string]: number;
}
//MyObjKeyは、string | number型
type MyObjKey = keyof MyObj;
MyObj
keyof MyObj
string | number
keyof MyObj
interface MyObj {
[foo: number]: string;
}
//MyObjKeyは、number型
type MyObjKey = keyof MyObj;
Lookup Types
T
K
T[K]
K
T[K]
interface MyObj {
foo: string;
bar: number;
}
//strはstring型
const str: MyObj['foo'] = '123';
MyObj['foo']
T[K]
MyObj
'foo'
MyObj['foo']
MyObj['bar']
MyObj['baz']
K
keyof T
interface MyObj {
foo: string;
bar: number;
}
//strはstring | numbder型
const str: MyObj[keyof MyObj] = 123;
MyObj[keyof MyObj]
MyObj['foo' | 'bar']
string | number
keyofとLookup Typesを組み合わせた関数
T[K]
K
keyof T
function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const obj = {
foo: 'string',
bar: 123,
};
const str: string = pick(obj, 'foo');
const num: number = pick(obj, 'bar');
//✘ Argument of type '"baz"' is not assignable to parameter of type '"foo" | "bar"'.
pick(obj, 'baz');
pick
pick(obj, 'foo')
obj.foo
pick
T
K
K
K extends keyof T
K extends keyof T
K
keyof T
T[K]
{ foo: string; bar: number; }
'foo'
({ foo: string; bar: number; })['foo']
Mapped Types
{[P in K]: T}
P
K
T
K
{[P in K]: T}
「K型の値として可能な各文字列Pに対して、型Tを持つプロパティPが存在するようなオブジェクト型」
{[P in 'foo' | 'bar']: number}
'foo' | 'bar'
'foo'
'bar'
{[P in 'foo' | 'bar']: number}
{ foo: number; bar: number; }
type Obj1 = {[P in 'foo' | 'bar']: number};
interface Obj2 {
foo: number;
bar: number;
}
const obj1: Obj1 = {foo: 3, bar: 5};
const obj2: Obj2 = obj1;
const obj3: Obj1 = obj2;
Mapped Typesの利用法
{[P in K]: T}
T
P
type PropNullable<T> = {[P in keyof T]: T[P] | null};
interface Foo {
foo: string;
bar: number;
}
const obj: PropNullable<Foo> = {
foo: 'foobar',
bar: null,
};
T
PropNullable<T>
T[P] | null
PropNullable<Foo>
{foo: string | null; bar: number | null; }
Mapped Typesとプロパティ修飾子
[P in K]
type Partial<T> = {[P in keyof T]?: T[P]};
Partial<T>
Readonly<T>
type Readonly<T> = {readonly [P in keyof T]: T[P]};
Mapped Typesで修飾子を取り除く
「-」
type MyRequired<T> = {[P in keyof T]-?: T[P]};
「?」を取り除いた
Required<T>
type MyRequired<T> = {[P in keyof T]-?: T[P]};
interface Foo {
foo: string;
bar?: number;
}
///ReqFooは{ foo: string; bar: number; }型
type ReqFoo = MyRequired<Foo>;
ReqFoo
Mapped Typesの応用
function propStringify<T>(obj: T): {[P in keyof T]: string} {
const result = {} as {[P in keyof T]: string};
// const result = {} as any;でも可
for (const key in obj) {
result[key] = String(obj[key]);
}
return result;
}
const foo = {foo: 'foo', bar: 123};
//aは{foo: string; bar: string; }型
const a = propStringify(foo);
{[P in keyof T]: string}
Mapped Typesと引数の位置
function pickFirst<T>(obj: {[P in keyof T]: Array<T[P]>}): {[P in keyof T]: T[P] | undefined} {
const result: any = {};
for (const key in obj) {
result[key] = obj[key][0];
}
return result;
}
const obj = {
foo: [0, 1, 2],
bar: ['foo', 'bar'],
baz: [],
};
const picked = pickFirst(obj);
picked.foo; // number | undefined型
picked.bar; // string | undefined型
picked.baz; // undefined型
{ foo: number[]; bar: string[]; baz: never[]; }
{[P in keyof T]: Array<T[P]>}
{ foo: number; bar: string; baz: never; }
{ foo: number | undefined; bar: string | undefined; baz: undefined; }
never | undefined
undefined
pickFirst
Conditional Types
T extends U ? X : Y
T
U
X
Y
type A<T> = T extends string ? string : number;
//Bはstring型
type B = A<string>;
//Cはnumber型
type C = A<boolean>;
//Dはstring型
type D = A<'hello'>;
//Eはnumber型
type E = A<123>;
「T extends U」
type UserA = {
name: string;
age: number;
};
type UserB = {
name: string;
email: string;
};
type UserC = {
name: string;
age: number;
email: string;
};
type U<T> = T extends UserA ? number : string;
//Aはnumber型
type A = U<UserA>;
//Bはstring型
type B = U<UserB>;
//Cはnumber型
type C = U<UserC>;
Mapped Typesの限界とConditional Types
Readonly<T>
type MyReadonly<T> = {readonly [P in keyof T]: T[P]};
interface Obj{
foo: string;
bar: {
hoge: number;
};
}
//Aは以下の型:
//{
// readonly foo: string;
// readonly bar: {
// hoge: number;
// };
//}
type A = MyReadonly<Obj>;
Obj
Readonly<Obj>
{readonly foo: string; readonly bar: { hoge: number; };}
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
interface Obj{
foo: string;
bar: {
hoge: number;
};
}
//Aは以下の型:
//{
// readonly foo: string;
// readonly bar: DeepReadonly<{
// hoge: number;
// }>;
//}
type A = DeepReadonly<Obj>;
const obj: A = {
foo: 'foo',
bar: {
hoge: 3,
},
};
//✘ Cannot assign to 'hoge' because it is a constant or a read-only property.
obj.bar.hoge = 3;
DeepReadonly<T>
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
function readonlyify<T>(obj: T): DeepReadonly<T> {
return obj as DeepReadonly<T>;
}
const obj = {
foo: 'foo',
bar: {
hoge: 3,
piyo: {
moge: 8
}
},
};
const a = readonlyify(obj);
//✘ Cannot assign to 'hoge' because it is a read-only property.
a.bar.hoge = 3;
//✘ Cannot assign to 'moge' because it is a read-only property.
a.bar.piyo.moge = 8;
Mapped TypesとConditional Typesを組み合わせて型操作する
DeepReadonly<T>
type DeepReadonly<T> =
T extends any[] ? DeepReadonlyArray<T[number]> :
T extends object ? DeepReadonlyObject<T> :
T;
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {};
type DeepReadonlyObject<T> = {
readonly [P in NonFunctionPropertyNames<T>]: DeepReadonly<T[P]>;
};
type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
DeepReadonly<T>
T
DeepReadonlyArray<T>
DeepReadonlyObject<T>
T
DeepReadonlyArray<T>
T
DeepReadonly<T>
ReadonlyArray<T>
ReadonlyArray<T>
T
T[number]
DeepReadonlyObject<T>
NonFunctionPropertyNames<T>
T
DeepReadonlyObject<T>
T
Conditional Typesの遅延評価
DeepReadonly<T>
type List<T> = {
value: T;
next: List<T>;
} | undefined;
type A = DeepReadonly<List<number>>;
const a: A = {
value: 1,
next: {
value: 2,
next: undefined
}
};
//Cannot assign to 'value' because it is a read-only property.
a.value = 2;
//Cannot assign to 'next' because it is a read-only property.
a.next!.next = {value: 3};
List<T>
DeepReadonly<T>
Conditional Typesにおける型抽出(infer)
type UserA = {
name: string;
role: 'admin' | 'user'; //この型を抽出したい
};
type UserB = {
name: string;
age:number
};
type A<T> = T extends { role: infer U } ? U : null;
type B = A<UserA>; //"admin" | "user"
type C = A<UserB>; // null
T extends { key: infer U } ? U : V
T
ReturnType<T>
MyReturnType<T>
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
//Bはnumber型
type B = MyReturnType<number>;
//Cはvoid型
type C = MyReturnType<(arg: number[])=>void>;
T
(...args: any[])=>R
R
ReturnType<T>
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
inferの重ね掛け
type Foo<T> = T extends {
foo: infer U;
bar: infer U;
hoge: (arg: infer V)=> void;
piyo: (arg: infer V)=> void;
} ? [U, V] : never;
interface Obj {
foo: string;
bar: number;
hoge: (arg: string)=>void;
piyo: (arg: number)=>void;
}
//tは[string | number, never]型
declare let t: Foo<Obj>;
Conditional Typesとinferによる文字列マッチング
type PartialHello<S extends string> = S extends `Hello, ${infer P}!` ? P : unknown;
//"world"型
type T1 = PartialHello<"Hello, world!">;
//unknown型
type T2 = PartialHello<"Hell, world!">;
"Hello, world!"
まとめ
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー
- 1. Typescriptによる「型プログラミング」
- 1-1. keyof演算子
- 1-2. symbol型をプロパティに含むオブジェクトへのkeyof
- 1-3. keyof型の注意点
- 1-4. インデックスシグネチャとkeyof型
- 1-5. Lookup Types
- 1-6. keyofとLookup Typesを組み合わせた関数
- 1-7. Mapped Types
- 1-8. Mapped Typesの利用法
- 1-9. Mapped Typesとプロパティ修飾子
- 1-10. Mapped Typesで修飾子を取り除く
- 1-11. Mapped Typesの応用
- 1-12. Mapped Typesと引数の位置
- 1-13. Conditional Types
- 1-14. Mapped Typesの限界とConditional Types
- 1-15. Mapped TypesとConditional Typesを組み合わせて型操作する
- 1-16. Conditional Typesの遅延評価
- 1-17. Conditional Typesにおける型抽出(infer)
- 1-18. inferの重ね掛け
- 1-19. Conditional Typesとinferによる文字列マッチング
- 2. まとめ