TypeScript の型をお勉強する
ここでお勉強する
ローカルで deno 動かして試す
ジェネリクスの型は、宣言した時と同様 ジェネリクスの変数が入った状態になっている
const call: <T>(arg: T) => void = <T>(arg: T): void => {
console.log(arg);
};
返り値の型指定の箇所にも変数名を利用できる。
すると、実際の関数宣言のところで、その変数名を利用できる。
また、U extends any[]
は、Uが any[]
の部分型である必要があることを示す。
string[]
や タプル型もすべて、any[] の部分型である。
以下の例では、U = number[] と推論される
const bind = <T, U extends any[], R>(
func: (arg: T, ...rest: U) => R,
value: T
): ((...args: U) => R) => {
return (...args: U) => {
console.log(args);
return func(value, ...args);
};
};
const add = (x: number, y: number) => x + y;
const add1 = bind(add, 1);
console.log(add1(5));
Union は与えられた 型 A| B| C| D| E のうち、どれかであればよいことを表す
null check をすると、nullを外したものを TSが推論する。
v?
は null でないならばアクセスする。もし null なら undefined を帰す
const func = (v: string | null): number => {
if (v != null) return v.length;
else return 0;
};
const func2 = (v: string | null): number => {
return v?.length ?? 0;
};
次回ここから
never: 属する値が存在しない型
すべて型の部分型になっている
部分型がよくわかってないけど、以下のようなもの。
{age: number}
は A という interface の部分型となっている。
継承みたいに考えると、Aが子クラスで、{age: number}
が親クラスみたいなもの。
部分型に対して代入は自由にできる。(より部分型のほうが抽象度が高くなり、親クラスに近づいていく。)
interface A {
age: number;
name: string;
}
const a: A = { age: 10, name: "" };
const b: { age: number } = a;
以下のように絶対にこないようなときに、never という型が自動で設定される。
また、例外などを故意に起こすなど、絶対に値を返さない、としたい場合は明示的に never を書く
なので、基本的に never は自分では書かない
interface Some<T> {
type: "Some";
value: T;
}
interface None {
type: "None";
}
type Option<T> = Some<T> | None;
function map<T, U>(obj: Option<T>, f: (obj: T) => U): Option<U> {
switch (obj.type) {
case "Some":
return {
type: "Some",
value: f(obj.value),
};
case "None":
return {
type: "None",
};
default:
return obj; // ここには絶対にこないので never という型になっている
}
}
function func(): never {
throw new Error('Hi');
}
交叉型は T & U である、つまり T でもあり、Uとも解釈できる型を指す。
(イメージ的には foo のみを持つ型って考えてたけど違った。
TもUも両方含む必要がある、というものだった)
interface Hoge {
foo: string;
bar: number;
}
interface Piyo {
foo: string;
baz: number;
}
type HogePiyo = Hoge & Piyo;
const obj: HogePiyo = {
foo: "",
bar: 20,
baz: 30,
};
x?
とすると、undefined との union になる。
また、readonly variable: T
をつけると、const のようになる
インデックスシグネチャ。
key
という部分はなんでもいい tmp
とかでもいい。
Mapを極力使ったほうがいいっぽい
interface MyObj {
[key: string] : number;
}
関数も interface に書ける
複数の型を書くと、オーバーロードのように表現できる
interface Func {
(age: number, name: string): void;
}
const f: Func = (age: number, name: string) => {
console.log(age, name);
};
ParentクラスとChildクラスがあったときに、
ChildインスタンスをParentに入れるのがアップキャスト。
ParentインスタンスをChildに入れるのでダウンキャスト。
as はダウンキャストもアップキャストもできる。
基本ダウンキャストにだけつかおう(アップキャストにasはいらないので)
function rand(): string | number {
if (Math.random() < 0.5) return "hello";
else return 200;
}
const value = rand();
const str = value as number;
console.log(str);
次はここから
タプル型を推論する(...をつけると、展開してそれらの型だと推論する)
const removeFirst = <T, Rest extends readonly unknown[]>(
arr: [T, ...Rest]
): Rest => {
const [, ...rest] = arr;
return rest;
};
const arr = removeFirst([1, 2, 3, "foo"]);
x.a = 20 でエラーが出る。
as const
すると、変数をその値に制限する。(以下ならx.a = "10" という 10 リテラル型になる)
readonly にあると考えるといい
const x = {
a: 10,
b: 30,
} as const;
x.a = 20;
unknown は、すべての型を部分型(子)として持つ。
そのため、const x: unknown = 40
など、やんちゃができる
any と違うのは、ほとんどすべての操作を制限できる ところである。
const s = x + 5
は、x が unknown 、つまりどのような演算ができるかわからない! と推論されてエラーができる。
any が強制的になんでもあり! であうならば
unknown はほぼすべてだめ! とみなせる
typeof は変数の型を取り出す。
const x = 1;
type T = typeof x;
keyof T
は、Tの key要素のUnionを返す。
そのため、x には、 "foo", "bar" が入る
interface A {
foo: string;
bar: number;
}
let x: keyof A;
x = "foo";
Lookup Types 型 T[K]
型T の 要素Kを 用いて、Kの型 T[K]
をプロパティのように取り出す
以下の場合 T = A, K = "foo".
"foo" 型 というのがポイント
そして、A["foo"] は string となる
interface A {
foo: string;
bar: number;
}
const x: A["foo"] = "hello";
以下は、lookup types の応用である。
まず、 K extends keyof T
は Tのプロパティ名のUnionである。
以下の例であれば K = foo | bar
となる。
そして、T[K]
は、オブジェクトT で取り出されたプロパティ名の型自体を指す。
以下の例であれば Obj["foo"] は Obj interface の foo 属性と考えられるので、
Obj=T[K="foo"] = string
となる。
よって、以下の pick は、オブジェクトから「プロパティ名」を指定して そのプロパティの型を保証して値を返す関数である。
function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
interface Obj {
foo: string;
bar: number;
}
const obj: Obj = {
foo: "nyann",
bar: 123,
};
const str = pick(obj, "foo");
いよいよもってわからんくなってきた