嵌りそうな箇所をメモ。
オブジェクトの型は、基本的にObjectのshape(形状)を表すことで宣言できる shapeではくオブジェクトリテラル表記って呼んでもいい。
// 変数aは、keyがbでvalueがstringのプロパティを必ず持ってる
let a: { b: string } = { b: "1" };
let a:{} = {}
let a:object = {}
空のオブジェクトリテラル表記は避けたほうがいい。
オブジェクトしか許容しないとおもいきや、null
とundefined
以外はいけちゃう。
object
とObject
も似たような感じ。細かい違いはあるんだけど、使わない前提でしばらくいくので、気にしないでおこう。
// こういうやつ
let a:{} = 1
let a:object = 1
let a:Object = 1
- 関数の引数に関しては、基本的に型推論は行われない。
- 任意のpropsには
?
をつけることで表現できる
const someFunc = (must: string, option?: string) => {};
someFunc("1");
- デフォルト値がある場合には?はいらないし、型定義もいらない
- 関数の引数としては
?
よりデフォルト値を使ってくほうがよさげ
const someFunc = (must: string, option = 'a') => {};
someFunc("1");
const someFunc = (
must: string,
option = {
param1: "",
param2: "",
}
) => {};
// OK
someFunc("1");
// OK
someFunc("1", { param1: "a", param2: "b" });
// NG param2がない
someFunc("1", { param1: "a"});
// NG param1のvalueの型が違う
someFunc("1", { param1: 1, param2: "b"});
混乱してて、こんな書き方をしてたけど、デフォルト値があるので、optionの型定義はいらない
const someFunc = (
must: string,
// :{ param1: ""...}の部分は不要
option: { param1: ""; param2: "" } = {
param1: "",
param2: "",
}
) => {};
とはいえ、こんな感じに、オブジェクトinオブジェクトの一部は任意みたいなやつは?
パターンにする必要があるんだと思う。
const someFunc = (
// 前述のとおり、デフォルト値をもとに型推論が行われる
// なので、valuesとerrorMessageは必須になる
state = {
values: {},
errorMessages: {},
},
) => {};
// ↓はNG。引数を渡してるので、stateのデフォルト値の設定が行われないので、errorMessageがないってなる
someFunc({ values: {} });
なので、明示的に型を定義する必要がある。
// stateに直接型を書くより、typeとして切り出したほうがみやすいね
type StateProps = {
values: {};
errorMessages?: {};
};
const someFunc = (
state:StateProps = {
values: {},
errorMessages: {},
},
) => {};
// OK
someFunc({ values: {} });
さらにいえば、オブジェクトの定義を{}
とするのは前述の通り避けたほうがいいので、
key:value
をもつっていうふうに書くといいみたい。
type stateProps = {
values: { [name: string]: string };
errorMessages?: { [name: string]: string };
};
callbackなど、関数を渡したり、返す場合、関数を型として定義する必要がある。
type Fn = (name:string) => void
関数の引数は、基本的に型推論されないと書いたけど、例外がある。 その一つが、上記の関数の型を宣言した場合。
type Fn = (name:string) => void
// nameはanyにならない
const someFunc: Fn = (name) => {};
また以下のように、callbackで型が定義してある場合も、アノテーションする必要がない。
const someFunc = (callback: (name:string) => void) => {
callback("tarou");
};
// nameはanyではなく、stringと解釈される
// nameを渡さなくてもエラーならなかったのが意外
someFunc((name) => {
console.log(name);
});
関数の型宣言を完全に書く場合以下の形式になる
type Fn = {
(name: string): void;
};
OOPとかで使うオーバロードの型宣言が上記でできる。 ※ OOPのオーバロードとオーバライドで混乱する。 https://qiita.com/ShirakawaMaru/items/058484a8fb584eb452b2
オーバーロードとは、「引数や戻り値が異なるが名称が同一のメソッドを複数定義する」というオブジェクト指向プログラミングのテクニックである。
使い所がいまいちわからない。
//関数Fnはfromとfrom・toのシグネチャを持つ2つのパターンあることを表現。
type Fn = {
(from: string): string;
(from: string, to: string): string;
};
// といっても実装では、↑の定義を手動で結合する必要がある。
const someFunc: Fn = (name: string, to?: string) => name;
createElement
の例が乗っていて、引数によって返す型が変わる場合に便利なのかしら。
うーん、↓のようにtagの値によって返す型がかわる型宣言書いてみたけ通らないや。 実装はどうやって書くんだろ。
type Fn = {
(tag: "a"): string;
(tag: "b"): number;
(tag: string): string;
};
const someFunc: Fn = (tag: "a" | "b" | string) => {
if (tag === "a") {
return "a";
}
if (tag === "b") {
return 1;
}
return "a";
};
あーany
にすると、コンパイルは通るし、引数によって型を検出してる
const someFunc: Fn = (tag: any):any => {}
��すこしだけ理解できた。引数によって、返す値がかわる例
type ExecAsync = {
// この関数は、Promiseを引数にもらったらPromiseを返す
// ※ async await でreturnすると普通の値もPromiseになるからね
<T>(args: Promise<T>): Promise<T>;
// Promiseが入った配列であれば、PromiseでT型の配列を返す
// Promise<T>[]は、Promise<T[]>になることにしばらく気づかなかった。
<T>(args: Promise<T>[]): Promise<T[]>;
};
// 実装はこう書く
const execAsync: ExecAsync = async (
promises: Promise<any> | Promise<any>[]
): Promise<any | any[]> => {// 省略}
配列と配列をフィルターをする関数filter
。
配列の要素は文字列でも、数値でもオブジェクトにも対応できるようにしたい。
以下は、オーバロードを使って、文字列と数値に対応したとこまで。
type Filter = {
(array: number[], f: (item: number) => boolean): number[];
(array: string[], f: (item: string) => boolean): string[];
};
const filter: Filter = (array: any[], f: (item: any) => boolean): any[] => {
let result = [];
for (let i = 0; i < array.length; i++) {
let item = array[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
毎回、型を追加してくのはしんどい。
ジェネリックを使うところで<T>
の箇所は同一って宣言できる。
type Filter = {
<T>(array: T[], f: (item: T) => boolean): T[];
};
// 関数もアノテートする必要がなくってすっきりした
const filter: Filter = (array, f) => {
let result = [];
for (let i = 0; i < array.length; i++) {
let item = array[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
const arrayNum = [1];
const arrayString = ["1"];
const arrayObject = [{ name: "tarou" }, { age: "age" }];
filter(arrayNum, (item) => true);
filter(arrayString, (item) => true);
filter(arrayObject, (item) => item.name === "tarou");
<T>
を宣言する箇所がちょっとわかりにくい。
type Filter = {
<T>(array: T[], f: (item: T) => boolean): T[];
};
//↓のように使う
//const filter:Filter = ...
type Filter<T> = {
(array: T[], f: (item: T) => boolean): T[];
};
// ↑の形にすると、型を宣言する必要がでてくる
// なんだか、これに衝撃を受けた。この場合関数を格納したfilterは、stringしか扱えなくなる。
// あー、オーバロードと一緒で、anyにするって方法もある?←だめ。Tが全部anyになる。
const filter: Filter<string> = (array, f) => {
let result = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
// 関数宣言の話と、↑のタイプの話�は別軸なのか、いまいちよくわからん
//`type`を使わずに関数に書く例
// typeを使わずというか、これは名前付き関数であって式ではないから、typeは使えない?
function filter<T>(array: T[], f: (item: T) => boolean): T[] {}
// アロー関数式
const filter = <T>(array: T[], f: (item: T) => boolean): T[] => {}
// Reactだと↑で書くとjsxとして認識されちゃうので、カンマをつけるといける
// https://github.com/Microsoft/TypeScript/issues/15713
const filter = <T,>(array: T[], f: (item: T) => boolean): T[] => {};
mapの例
function map<T, U>(array: T[], f: (item: T) => U): U[] {
let result = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
result.push(f(item));
}
return result;
}
ジェネリクスは関数の引数の型だけを使って型推論がされる。 Promiseのように、callbackで渡した関数の実行結果を受け取るような場合は、明示的にアノテートする必要がある。
const promise = new Promise<number>((resolve) => {
resolve(40);
});
promise.then((value) => {
return value * 2;
});
const createState = <T,>(initialState: T) => {
//createStateは、initialStateのshapeを気にしないんであれば、Tにしとけばいいのかな
};
createState({ hoge: "fuga" });
Tにextends加えることで、Tに制限をもたせることができる
type TreeNode = {
value: string;
};
type LeafNode = TreeNode & {
isLeaf: true;
};
type InnerNode = TreeNode & {
children: [TreeNode] | [TreeNode, TreeNode];
};
let a: TreeNode = { value: "a" };
let b: LeafNode = { value: "b", isLeaf: true };
let c: InnerNode = { value: "c", children: [b] };
function mapNode<T extends TreeNode>(
node: T,
f: (value: string) => string
): T {
// T extends TreeNodeがないと、node.valueが存在していない可能性がある
return { ...node, value: f(node.value) };
}
type Shoe = {
purpose: string;
};
class Boot implements Shoe {
purpose = "woodcutting";
}
class Sneaker implements Shoe {
purpose = "wakling";
public price = 1000;
}
// Objectの要素に直接関数書くとびっくりする
// この場合、Shoe.createを実行した結果の返り値は、Shoe
// ファクトリパターン的には、Shoeであることを知っていれば十分
const Shoe = {
create(type: "boot" | "sneaker"): Shoe {
switch (type) {
case "boot":
return new Boot();
case "sneaker":
return new Sneaker();
}
},
};
// 仮に、typeに応じて返す型がかわるってことを明示したい場合
// オーバロードで実現できる
type ShoeCreator = {
create(type: "boot"): Boot;
create(type: "sneaker"): Sneaker;
};
// 返り値をShoe→anyにしてる。Shoeのままだと、Sneakerだけにもってるpropsがないって怒られる
// anyにすることでコンパイルも通って、かつ型もちゃんと拾える
// ここいまいちよくわからん
const Shoe: ShoeCreator = {
create(type: "boot" | "sneaker"): any {
switch (type) {
case "boot":
return new Boot();
case "sneaker":
return new Sneaker();
}
},
};