TypeScript 練習問題集を解いていくよ〜
問1
Fooが持つプロパティ全てoptionalにしてください
interface Foo {
bar: string;
baz: number;
}
答え
?を使うパターン
interface Foo {
bar?: string;
baz?: number;
}
Partialを使うパターン
type PartialFoo = Partial<Foo>;
Partialについて
PartialをOptions Objectパターンに応用し、省略可能でありながら見やすい関数を実装する
type User = {
surname: string;
middleName?: string;
givenName: string;
age: number;
address?: string;
nationality: string;
createdAt: string;
updatedAt: string;
};
function findUsers(
surname?: string,
middleName?: string,
givenName?: string,
age?: number,
address?: string,
nationality?: string
) {
// ...
}
- この場合年齢のみをしたい時に
findUsers(undefined, undefined, undefined, 22); // 年齢以降がundefinedなら省略可
- こうなる
これをPartial<T>を使って見た目をよくできる
STEP 1 : Partialを使用する
type FindUsersArgs = Partial<User>;
function findUsers({
surname,
middleName,
givenName,
age,
address,
nationality,
}: FindUsersArgs) {
// ...
}
STEP 2 : デフォルト引数を使用する
function findUsers({
surname,
middleName,
givenName,
age,
address,
nationality,
}: FindUsersArgs = {}) {
// ...
}
- こうすることで下記のようにかける
findUsers({ age: 22 });
問2
Fooが持つプロパティ全て必須にしてください
type Foo = {
name?: string;
age?: number;
}
答え
type RequireA = Required<Foo>;
- これは すべてのプロパティからオプショナルであることを意味する?を取り除くユーティリティ型
- つまり、下記と同義
type Foo = {
name: string;
age: number;
}
問3
Fooからnameだけを取得したtypeを作ってください
type Foo = {
name?: string;
age?: number;
}
答え
type Picked = Pick<Foo, "name">
Pick<T, Keys>は、型TからKeysに指定したキーだけを含むオブジェクトの型を返すユーティリティ型です。
ユースケース
type Book = {
id: number;
title: string;
author: string;
createdAt: Date;
updatedAt: Date;
};
- 上記からINPUT値の型を定義する
- 下記以外の情報は後付けする想定
type BookInputData = {
title: string;
author: string;
};
- ここでauthorプロパティがstringではなくPersonになる必要があったとき
- Pickを使用しない場合二箇所の変更が生じる
type Book = {
id: number;
title: string;
author: Person; // ここ
createdAt: Date;
updatedAt: Date;
};
type BookInputData = {
title: string;
author: Person; // ここ
};
- Pickを使用すると
type BookInputData = Pick<Book, "title" | "author">;
- authorが何型に変更されても問題ない
問4
Fooからageを省略した型を作ってください
type Foo = {
name?: string;
age?: number;
}
問5
userに推論される型は何ですか。またその理由を教えてください。
const user = { name: "kenji", age: 98 };
答え
userに推論される型
{ name: string, age: number }
その理由
JavaScriptのオブジェクトはconstであれ(freezeしない限り)書き込みが可能です。
それそれのプロパティ値はあとで書き込めるようにwindeningされ、それぞれのプロパティの型はプリミティブ型になります。
これをそれぞれのプロパティをリテラル型にするには
as constか型注釈をすることです。
補足①
constアサーション(as const)
オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになります。
- readonlyはプロパティごとにつけられるのに対して、constアサーションはオブジェクト全体に対する宣言になる
補足②
Widening(型の拡大)について
const str = "text"; // text型として扱われる
const 宣言によってそもそも変数に対しての値の再代入ができなくなるわけですから、一般的な string や number といった型で型推論するよりも、より具体的な情報を持つリテラル型として型推論を働かせた方が合理的になるわけです。
- オブジェクトや配列はミュータブルなので話は別である
このように、let は const よりも 広い (wide) 型 を受け入れるように型推論が働くというわけです。
- 逆にLiteral Wideningしてるのに、無理にリテラル型の型注釈をするとDenoではas const を付けろって怒られるらしい
問6
T extends U ? X : Y はどのような意味になりますか
T extends U ? X : Y
答え
// TがUに代入可能ならXを、そうではない場合Yを返す
// T型がU型と互換性あるならXを、そうでない場合Yを返す
// T型がU型のサブタイプならXを、そうでない場合Yを返す
// T型がU型の部分型ならXを、そうでない場合Yを返す
補足
TSにおける型の互換性
- 「互換性がある」というのは、一つの型が他の型に安全に割り当て可能であることを意味する
-
同じ型: 同じ型は互換性があります。例えば、number 型は別の number 型に割り当て可能です。
-
サブタイプ: サブタイプ(派生型)は、そのスーパータイプ(基底型)に割り当てることができます。例えば、あるクラス Child が別のクラス Parent から派生している場合、Child のインスタンスは Parent 型に割り当て可能です。
-
インターフェースとオブジェクトリテラル: オブジェクトリテラルまたはインターフェースが、別のインターフェースで要求されるすべてのプロパティを持っている場合、互換性があります。これは「ダック・タイピング」または「構造的サブタイピング」と呼ばれます。
-
関数: 引数の数が少ない関数は、引数が多い関数に割り当て可能です(これは「関数の部分適用」として知られています)。また、戻り値の型が互換性があれば、関数同士も互換性があります。
-
ジェネリック型: ジェネリック型が同じ型パラメータを持っている場合、または一方の型パラメータが他方の型パラメータのサブタイプである場合、互換性があります。
-
列挙型(Enums): 同じ列挙型の異なるメンバーは互換性があります。ただし、異なる列挙型同士は互換性がありません。
-
ユニオンとインターセクション型: ユニオン型は、その型のいずれかのメンバーに割り当てることができます。インターセクション型は、すべての型を含むオブジェクトに割り当てることができます。
問7
メソッド名だけ取り出した型を作ってください
interface Part {
name: string,
age: number,
add(): number
}
答え
interface Part {
name: string;
age: number;
add(): number;
}
type MethodNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type PartMethodNames = MethodNames<Part>; // "add"
解説
STEP1: interfaceのPartをMethodNamesに渡す
MethodNames<Part>
STEP2 : Partの中身をTとして受け取る
type MethodNames<T> = {
STEP3 : Tの中身をKとして1ずつ確認
[K in keyof T]
STEP4 : プロパティの型がFunction のサブタイプであるかどうかをチェック
- Function のサブタイプならそのキー K をそのまま残し
- Function のサブタイプじゃないならnever 型になる
T[K] extends Function ? K : never;
never
ではないキーだけを抽出
STEP5: マッピングの結果から、[keyof T]
- keyof T は、元の型 T のすべてのキーを取得
name
age
add()
- マップされた型で生成された新しい型をこのキーでインデックスし、それぞれのキーに対応する型(K または never)を取得
never | never| add // never は無視される
インデックスアクセス型について
type Foo = { a: number; b: string; c: boolean };
type T = Foo[keyof Foo]; // type T = string | number | boolean
問9
neverとはどんな型ですか
type MethodNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
答え
- 「値を持たない」を意味するTypeScriptの特別な型
- 絶対にreturnされない関数
- 常にthrowされる関数
補足
値を持たない
function throwError(): never {
throw new Error();
}
絶対にreturnされない
function forever(): never {
while (true) {} // 無限ループ
}
常にthrowされる
function fail(message: string): never { throw new Error(message); }
voidは部品です。neverはうそつきです。
void型はundefinedが代入できますが、neverは値を持てません。
何も返さない関数はvoidを返します。しかし、returnを返すことのない関数(または常にスローする)はneverを返します。voidは(strictNullCheckingなしで)代入することができるものですが、neverはnever以外のものに代入することはできません。
const ok: void = undefined;
const ng: never = undefined;
問10
これは、なにをする型か説明してください(とくにinfer)
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
答え
- Tが関数ならその関数の戻り値をRにキャプチャしてそれを返す
補足
- (...args: any[]) は 任意の型の引数を任意の数受け取る という意味
- infer R はTypeScript の型推論を使用している。ここでは、R は関数の戻り値の型を表し、その具体的な型は関数が実際に呼び出されるまで未知
- => infer Rは 「この関数が何を返すかはわからないが、返すものを型 R として扱う」
実際の使われ方(ReturnType)
- 指定した関数の返り値の型を推論
type Func = () => number;
type Result = ReturnType<Func>; // Result は number 型になります
問11
非同期の中身を取る型を書いてください
答え
type ResolvedType<T> =
T extends Promise<infer R> ? R :
T extends Observable<infer R> ? R :
T;
補足
三項演算子の構造としては下記のようになっている
type ResolvedType<T> =
Tがプロミス型のとき ? プロミス型を返す : そうでなければAの内容;
// Aの内容
(TがObservable型のとき ? Observable型を返す :
Tを返す)
一つずつ見ていくと
- もし T が Promise に割り当て可能であれば(つまり、T が Promise の形式であれば)、条件は「真」として評価され、R(Promise の解決値の型)が結果として返される。Observable型も同様。どちらでもないならそのままTが返される。
T extends Promise<infer R> ? R :
問12
Nullableな型を作ってください
答え
type PropNullable<T> = {[P in keyof T]: T[P] | null};
interface User { name: string, age: number, money: null }
const obj:PropNullable<User> = { name: "kenji", age: 99, money: null }
補足
- Tで受け取ったkeyのPを一つずつをその型 or nullの状態にする
type PropNullable<T> = {[P in keyof T]: T[P] | null};
- PropNullable<User>でnameとageとmoneyにnullを付け足す
問 15
どんな要素の配列が渡されてもいいような型を作ってください。
問 16
wideningとはなんですか説明してください。
問 17
aがunion型になるのはなぜか
let a;
if (Math.random() < 0.5) {
a = 123;
}
console.log(a); // a は number | undefined 型
答え
a宣言時に初期化されていない & 型注釈されていないことでif文がtrueになるまでundefined、その後aを参照するとnumber と undefinedの可能性があるから。 初期化なし、型注釈なしの変数はコンテキストによって型が変わる(アンチパターン)
補足
- TypeScript では、初期化されていない変数は undefined 型として扱われる
undefinedは言語仕様上、プログラマーが明示的に使わなくても、自然に発生してくるものです。たとえば、変数を宣言したときに初期値がなければJavaScriptはその変数にundefinedを代入します。
- a には number 型の値が割り当てられる可能性がある
- つまり、number or undefined なのではと型推論してくれる
問 19
型推論は何ですか
let a = 1
const num = a && "hoge";
問 20
type narrowing(型の絞り込み)とはなんですか
答え
- 変数や式の型をより具体的な型に制約するプロセス
補足
typeofで絞り込み
if (typeof value === "number") {
// valueはここではnumber型として絞り込まれる
console.log(value.toFixed(2));
}
nullish絞り込み(Nullish Coalescing)
- ?? でnull または undefined の場合に型を絞り込む
function func(value: number | null | undefined) {
// value が null または undefined でない場合に絞り込まれる
const result = value ?? 0;
// result は引数に与えられた数値または 0
console.log(result.toFixed(2));
}
カスタム型ガード関数で絞り込み
function isString(value: any): value is string {
return typeof value === "string";
}
let input: string | number = "Hello";
if (isString(input)) {
// inputはここではstring型として絞り込まれる
console.log(input.length);
}
問21
下記の使い分けを教えてください
- 「return文はあるけどreturnせずに関数が終了する場合がある」 -> 例: string | undefined
- 「return文がない」 -> void
答え
- 関数が値を返すかどうか
「return文はあるけどreturnせずに関数が終了する場合がある」
function getStringOrUndefined(condition: boolean): string | undefined {
if (condition) {
return "Hello";
}
// ここではreturn文がないため、undefinedが暗黙的に返される
}
「return文がない」
function logMessage(message: string): void {
console.log(message);
// return文がなくても、void型の関数
}
問 22
contextual typeがある状態というのはどういう状態のことですか
答え
TypeScriptは引数の型注釈で型を宣言しなくてはいけない、が、
型推論の時点で期待される型があらかじめわかっている状態(contextual tyipingがある状態)なら書かなくても良い
type Func = (arg: number) => number;
const double: Func = function(num) {
// 引数 num の型は number 型と推論されている
return num * 2;
};
補足
- 引数でnumber型を貰って、返り値でnumber型を返すというFunc型を宣言している
type Func = (arg: number) => number;
- それを型として宣言しているので
- 再度、明示する必要が無い
const double: Func =
問 23
!の意味、危険性について説明をしてください。
type MyObj = {
name?: string;
}
function foo(obj: MyObj): string {
return obj.name!.slice(0, 5);
}
答え
! の意味
変数やプロパティの初期化が確実に行われていることをコンパイラに伝えるには、明確な割り当てアサーションを使います。変数宣言の変数名やプロパティ名のあとに!を書きます。
危険性
型の安全性を保証する責任をコンパイラからプログラマに移す
問24
obj is Array<string>の説明をしてください
function isStringArray(obj: unknown): obj is Array<string> {
return Array.isArray(obj) && obj.every(value => typeof value === "string");
}
function foo(obj: unknown) {
if (isStringArray(obj)) {
obj.push("abcde");
}
}
答え
型ガード関数
- isStringArrayがtrueのとき、objをArray<string>型とする
ただし、ユーザー定義型ガードでバグ(実際の型との乖離)があっても、TypeScriptはそれに気付けず、型安全を破壊する可能性があるので、その点は注意が必要です。
問25
'num' is declared but never used.をdisableしてください
(num: number) => { // 'num' is declared but never used.
return "return";
}
問28
こちらのエラーをnumberとstringに対応できるように修正してください。
function eachItem(val: number, i: number) {
return val.toExponential(3);
}
const arr = [4, "fafa", 6];
arr.map(eachItem);
答え
function eachItem(val: number | string, i: number) {
// valがnumber型の場合のみtoExponentialを呼び出す
if (typeof val === "number") {
return val.toExponential(3);
}
// valがstring型の場合はそのまま返す
return val;
}
const arr = [4, "fafa", 6];
const result = arr.map(eachItem);
console.log(result); // 結果: [ '4e+0', 'fafa', '6e+0' ]
問29
Parameter 'callback' implicitly has an 'any' type. と Parameter 'e' implicitly has an 'any' type. に対応してください(callbackに型付けしてください)
function fa(callback, e){
return callback(e);
}
const fun = (e) => 1 * e;
const v = fa(fun, 1);
答え
interface Fun {(e: number): number;}
function fa(callback:Fun, e: number){
return callback(e);
}
const fun:Fun = (e) => 1 * e;
const v = fa(fun, 1);
問30
型を説明してください
type YukarinoChi = "tokyo";
type OnlySpecificProperty<T> = Pick<T, {[K in keyof T]: T[K] extends YukariNoChi ? K : never}[keyof T]>;
問32
定義元のFを直接編集せずに代入できるように型付けしてください
type F = {
foo: string;
bar: number;
}
const E:F = { foo: "fafa", bar: "fafa"} //Error
問33
type Exclude<T, U> の説明をしてください
問34
detailが初期化された時 undefinedが渡って来てもいいように対応してください
export defaut function person({ detail } : Person) {
return <div>{detail.name}</div>
};
interface Person {
id: number
detail: Detail
}
person({detail: {name: "fafa"}, id: 1 });
答え
export defaut function person({ detail = {} as Detail} : Person) {
return <div>{detail.name}</div>
};
備考
{ detail = {} as Detail} について
- detailが未定義(undefined)の場合に空のオブジェクトをDetail型として扱う
- Detail型がnameプロパティを必須とする場合、空のオブジェクト{}をDetail型としてキャストすることは型安全ではない
- なぜなら、{}にはnameプロパティが存在しないため、実行時にdetail.nameへのアクセスがundefinedを返す可能性があるから
解決策
- デフォルト値を設定しておく
export default function person({ detail = { name: '' } } : Person) {
return <div>{detail.name}</div>
};
interface Person {
id: number;
detail?: Detail;
}
35問
reactでsetStateをする際に全てのpropertyを渡さないでもいいようにしてください。
interface State {
name: string
age: number
}
答え
オプショナルプロパティを使う
interface State {
name?: string;
age?: number;
}
問36
関数 a はServiceUser or User or AppUserをa に渡してそれを返す関数です。 期待型は ServiceUser | User | AppUser になっています。 これを それぞれ ServiceUser はserviceID、Userはid、AppUserはappIdを返す関数に直して、 期待型をstringにしてください
interface User {
id: string
}
interface AppUser {
appName: "appName"
appID: string
}
interface ServiceUser {
serviceName: 'serviceName'
serviceID: string
}
const user = {id: "1"}
const appUser = { appName: "appName", appID: "appId"} as const;
const serviceUser = { serviceName: "serviceName", serviceID: "serviceID"} as const
function a(o: ServiceUser | User | AppUser){
return o
}
const result = a(user)
答え
function a(o: ServiceUser | User | AppUser){
if("serviceID" in o) return o.serviceID;
if("appID" in o) return o.appID;
return o.id;
}
const result = a(serviceUser) // string
備考
in演算子を使用する
type Person = {
name: string;
old: number;
};
const person: Person = {
name: 'yamada',
old: 22,
};
console.log('name' in person); // true
console.log('xxx' in person); // false
問37
問36を独⾃定義 TypeGuardで型定義してください
const isService = (o: any): o is ServiceUser => {
return o.serviceID === "serviceID";
}
const isAppUser = (o: any): o is AppUser => {
return o.AppUser === "appUser";
}
type O = ServiceUser | User | AppUser;
function a(o: any){
if(isService(o)) return o.serviceID;
if(isAppUser(o)) return o.appID;
return o.id; // User
}
const result = a(serviceUser)
備考
- 問24の考え方で 「o is ServiceUser」のところは理解できる
https://zenn.dev/link/comments/e25923b283daf3
問38
defaultValueとany型に対応してください
const o = { name: "hoge" }
function a(o){
return o
}
a(o)
a();
答え
const getDefaultProps = () => {
return {name: "hoge"}
}
const defaultProps = getDefaultProps();
const o = {name: "hoge"}
function a(o = defaultProps){
return o
}
a(o)
a();
問39
なぜコンパイルエラーになるのですか?説明してください
type Animal = { name: string, run: boolean }
type Bird = { name: string, fly: boolean }
const animal = { name: "tigger", run: true }
const bird = { name: "condol", fly: true }
type NotHumman = Animal | Bird
const a = (b:NotHumman) => {
b.run
}
答え
Birdにrunが存在しないから
対処法
const b = (b: NotHumman) => {
if ("run" in b) { // animalであることが確定する
b.run
} else {
b.fly
}
}
問40
コールバックに渡す引数の数が違うのでオーバーライドしてあります。修正してください
declare function beforeAll(action: () => void, timeout?: number): void;
問41
修正してください。
declare function fn(x: any): any;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: any, wat?
答え
TypeScript は関数呼び出し時に最初にマッチしたオーバーライドを選ぶので、any だと最初に必ずマッチしてしまう
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
42問
修正してください
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
答え
返る型が同じ場合、可能な限りオプショナルを使いましょう。
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}
43問
1と2はそれぞれエラーになりますかなりませんか
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
// 1
y = x;
// 2
x = y;
問44
1, 2はそれぞれエラーになるかならないか
let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });
// 1
x = y;
// 2
y = x;
答え
1
- エラーになりません
2
- エラーになります
問46
代入できるか。それぞれTとUの型は何か
let identity = function<T>(x: T): T {
// ...
};
let reverse = function<U>(y: U): U {
// ...
};
identity = reverse;
答え
// OK。anyになります。
(x: any)=>any は (y: any)=>any と互換性がある
脱線
anyとジェネリクスの違い
let reverseAny = function(y: any): any {
// yは任意の型であるため、型に関する情報が失われています。
// ここにyに対する操作を書くことができますが、型安全ではありません。
return y;
};
let result1 = reverseAny("Hello"); // result1はany型
let result2 = reverseAny(123); // result2もany型
let reverseGeneric = function<U>(y: U): U {
// yはU型です。この型はこの関数が呼び出される際に決定されます。
// yに対する操作は型Uに適合する必要があります。
return y;
};
let result1 = reverseGeneric("Hello"); // result1はstring型
let result2 = reverseGeneric(123); // result2はnumber型
問47
こちらは呼び出すことができません。なぜですか?
type StrFunc = (arg: string) => string;
type NumFunc = (arg: number) => string;
declare const obj: StrFunc | NumFunc;
obj("fa"); // Argument of type 'string' is not assignable to parameter of type 'never'.
答え
NumFuncがnumber型だから
type StrFunc = (arg: string) => string;
type NumFunc = (arg: number) => string;
type StrOrNumFunc = <T>(arg: T) => string
declare const obj: StrOrNumFunc
obj("fa");
// or
(obj as StrFunc)("fa"); // unnn...
問48
Errorになります。なぜですか。また正しく修正してください
interface MyObj {
name: string;
age: number | undefined;
}
let obj: MyObj = {
name: "kenji"
};
答え
ageを定義していないから
修正
interface MyObj {
name: string;
age?: number | undefined;
}
問49
TypeScriptでconsole.logを呼び出してもコンパイルエラーにならないのはなぜですか?
答え
declareを使用しているから
declare var console: Console;
問50
{ name: "kenji", age: 90 };
が代入できるように修正してください
なぜコンパイルエラーなのですか? interface Foo {
name: string;
}
let obj: Foo = { name: "kenji", age: 90 };
答え
ageプロパティが存在しないから
interface Foo {
name: string;
+ [other: string]: any; //here
}
オブジェクトのフィールド名をあえて指定せず、プロパティのみを指定したい場合があります。そのときに使えるのがこのインデックス型(index signature)です。
インデックス型のフィールド名の型はstring、number、symbolのみが指定できます。
noUncheckedIndexedAccessが無効の場合
interface Example {
prop: string;
}
const obj: Example = { prop: "Hello" };
// この場合、propValの型はstringです。
const propVal = obj.prop; // 通常はstring型
// 存在しないプロパティにアクセスしても、コンパイラはエラーを報告しません。
const nonExistent = obj.nonExistent; // これはエラーにならない。
noUncheckedIndexedAccessが有効の場合
interface Example {
prop: string;
}
const obj: Example = { prop: "Hello" };
// この場合、propValの型はstring | undefinedです。
const propVal = obj.prop; // string | undefined型
// 存在しないプロパティにアクセスすると、コンパイラはエラーを報告します。
const nonExistent = obj.nonExistent; // これはエラーになる。
問51
fooにanyを注釈しています。インデックスにstring、 値に代入しようとしている型を指定してください
let foo:any = {}
foo["a"] = { message: "some message"};
答え
- インデックス型の応用
let foo:{ [index: string]: { message: string }} = {}
foo["a"] = { message: "some message"};
問52
(string|number)[]
になります。[string, number]
とするにはどうしたらいいですか
下記は型推論でconst tupleStrNum = ["X", 2];
問53
再帰的に各プロパティをオプショナルにした型を定義してください
interface SomeObject {
firstKey: string;
secondKey: string;
thirdKey: { id: { name: string} }
}
問54
プロパティnameの値型がstring | null、ageの値型がnumber | nullの型Userを定義してください
答え
const d = {name: "kenji", age: 99}
type E = {name: string, age: number}
type User<T> = {[K in keyof T]: T[K] | null }
const e:User<E> = { name: null, age: null};
問55
Uをextendsしている値Tはneverを返し、そうでない値型はTを返すDiffを定義してください
答え
type Diff<T, U> = T extends U ? never : T;
const t1:Diff<"a" | "b", "b" | "c"> = "a"; // const t1: "a"
問56
T3の型をおしえてください
const t3 = {name: "kenji", age: 99} as const
type T3 = keyof typeof t3
答え
type T3 = "name" | "age"
問58
もし関数型である引数を渡したらその引数が返ってくる型、関数型ではないなら関数が返ってくるF<User>を定義してください。
type User = { name: string, age: number}
const f = (a:User) => a
const a:F<User> = f({name: "kenji", age: 9});
問59
下記のようなUser型がある。こちらのvalueのUnion型を取得する型を定義してください。 string | number
type User = { name: string, age: number }
答え
type Value<T> = T[keyof T]
使い方
type User = { name: string, age: number }
type Value<T> = T[keyof T]
type ValueType = Value<User> // string | number
問60
type User = { name: string, age: number, id: number }
からnumberのものだけを抽出した型を作ってください。
問61
unionTypeなため+で加算しようとするところでエラーになります。こちらを渡ってきた型を推論するようにしてください
const isNarrowScreen = () => false
export function wideNarrow(wide: number | string | undefined,
narrow:number|string|undefined){
return isNarrowScreen() ? narrow : wide;
}
const a = wideNarrow(0, 8)
const extendedAreaHeight = 26;
const b = a + extendedAreaHeight
問62
numberかstringが渡ってくることを想定して作られた関数です。 2つは同じ型の引数を取るため、型をTとして使おうとしています。 ただ、コンパイルエラーになっています。正しく修正してください
function add<T extends (number | string)>(a: T, b: T): T {
if (typeof a === 'string') {
return a + b;
} else if (typeof a === 'number') {
return a + b;
}
}
答え
function add<T extends number | string>(a: T, b: T): T {
if (typeof a === 'string' && typeof b === 'string') {
return (a + b) as T;
} else if (typeof a === 'number' && typeof b === 'number') {
return (a + b) as T;
} else {
throw new Error('Type mismatch: a and b must be of the same type');
}
}
補足
- aもbも型を絞り込む必要がある
- (a + b) as Tで返り値もT型であることを伝えてると尚、良い
問63
配列の各要素(string)のどれかを割り当てることができるUnion型が返るOneOfを定義してください
const values = ['A', 'B']
type Foo = OneOf<values>
const v1: Foo = 'A' // ok
const v2: Foo = 'D' // error
答え
function stringLiterals<T extends string>(...args: T[]): T[] { return args; }
type OneOf<T extends unknown[]> = T extends (infer R)[] ? R : never;
const values = stringLiterals('A', 'B'); // ("A" | "B")[]
type Foo = OneOf<typeof values>; // "A" | "B"
題意
この問題は、TypeScriptの型システムを使って特定の配列の要素からユニオン型を作成する方法に関するものです。具体的には、与えられた文字列の配列(この例ではvalues)の各要素から、その要素のいずれか一つを表す型(Foo)を生成するOneOfという型エイリアスまたは型ユーティリティを定義することを意味しています。
ただし、TypeScriptの現在のバージョンでは、配列の値から直接型を生成することはできません。つまり、リテラル型の配列(例:['A', 'B'])から直接ユニオン型(例:'A' | 'B')を生成することはできないのです。代わりに、リテラル型のユニオンを手動で定義するか、またはconstアサーションを使用してリテラル型のタプルとして配列を宣言する必要があります。
解説
-
function stringLiterals<T extends string>(...args: T[]): T[] { return args; }
- Tがstringのサブタイプ(この場合は文字列リテラル)であることを指定
- 可変長引数(...args)を取り、それらを配列として返す
-
type OneOf<T extends unknown[]> = T extends (infer R)[] ? R : never;
- T extends unknown[]は、Tが任意の配列型であることを意味します。
- T extends (infer R)[] ? R : neverは条件型を使用
- infer Rは配列Tの要素の型を推論し、その型をユニオン型として返す
- 配列でない場合はnever(存在しない型)を返す
問64
- AとBのkeyであるnameとageを合わせたtype、 name | ageとなるUnion型を作ってください
type A = { name: string }
type B = { age: number }
問65
type Foos = 'a' | 'b' | 'c'
このようになるようにしてください
こちらの型を type MyUnionType =
| { foo: 'a', bar: 1 }
| { foo: 'b', bar: 2 }
| { foo: 'c', bar: 3 }
問66
a に割り当てられたオブジェクトのプロパティ以外を参照できないようにしてください
const a: Record<string, string> = {
doorToDoor: "delivery at door",
airDelivery: "flying in",
specialDelivery: "special delivery",
inStore: "in-store pickup",
};
const aa = a["name"] // stringが入ってしまう
答え
const source = {
doorToDoor: "delivery at door",
airDelivery: "flying in",
specialDelivery: "special delivery",
inStore: "in-store pickup",
};
const a: Record<keyof typeof source, string> = source
const aa = a["name"] // error
解説
- 下記と同義
const a: Record<"doorToDoor" | "airDelivery" | "specialDelivery" | "inStore", string> = source;
問67
関数fooは現状 interface Foo型を受け取り、 現状Fooが持つfooを返すようになっています。(argはanyです)この関数をfooAndBarと名前をへんこうして、Foo型が渡された場合はarg.fooを、Bar型の場合はarg.barを返すように 実装して、型付けしてください
interface Foo {
foo: number;
common: string;
}
interface Bar {
bar: number;
common: string;
}
// interface Foo型を受け取り
// argはanyです
function foo(arg){
// Fooが持つfooを返すようになっています。
return arg.foo
}
const result = foo({foo: 9});
答え
interface Foo {
foo: number;
common: string;
}
interface Bar {
bar: number;
common: string;
}
function isFoo(arg: any): arg is Foo {
return arg.foo !== undefined;
}
function fooAndBar(arg: Bar | Foo){
if(isFoo(arg)){
return arg.foo
} else {
return arg.bar
}
}
const result = fooAndBar({ foo: 9, common: "fa" });
問68
現状mapに割り当てられている実際のproperty以外のプロパティを受け入れてしまっています 実際に渡したもpropertyをもつ型のみを受け入れるようにしてください(map.weoiroweiroewをエラーにししてください)
また type MyKeys = keyof map;を期待する結果であるone | two | threeにしてください
interface NumberMap {
[key: string]: number;
}
const map: NumberMap = {
one: 1,
two: 2,
three: 3,
}
// no error, but incorrect, this key does *not* exist
const lol = map.weoiroweiroew;
// Also cannot do this
// 'map' refers to a value, but is being used as a type here.
type MyKeys = keyof map;
答え
interface NumberMap {
[key: string]: number;
}
function careteMap<T extends NumberMap>(v: T){
return v
}
const map = careteMap({
one: 1,
two: 2,
three: 3,
})
const lol = map.weoiroweiroew;
type MyKeys = keyof typeof map;
問69
こちらは型エラーがでます正しく修正してください
type Person = {
name: string, age: number, id: number,
}
const me = {name: "a", age: 11, id: 999};
Object.keys(me).forEach(key => {
console.log(me[key]) // error
})
答え
type Person = {
name: string, age: number, id: number,
}
const me = {name: "a", age: 11, id:999};
Object.keys(me).forEach(key => {
console.log(me[key as keyof Person]) // ok
})
key as keyof Person を使う理由
- Object.keys は string[] を返すが、TypeScriptではオブジェクトのキーは特定の型を持つ。
- key as keyof Person により、key が Person 型のキーのいずれかであることをTypeScriptに伝え、型の不一致を解消する。
詳細
-
key as keyof Person
で型アサーションが使われている理由は、Object.keysは実際の型情報を持たないため - TypeScriptでは、Object.keys メソッドは常に string[] を返す。
- つまり、Object.keys(me) の結果は string[] 型として扱われる。
- これはJavaScriptの動的な性質に由来しており、オブジェクトのキーは基本的に文字列(またはシンボル)であるため。
- しかし、TypeScriptの型システムでは、オブジェクトの各キーはそのオブジェクトの特定の型に対応する。
- 例えば、Person 型のオブジェクトでは、各キーは 'name' | 'age' | 'id' のいずれかであり、それぞれ string, number, number 型の値に対応する。
問70
type User = {
id: string
email: string
password: string
}
type UserProfile = User & { name: string, dob: string }
function insert(user: User) {
console.log(user)
}
insert({id: "1", email: "fa@gmail.com", password: "000", name: "222"})
- 過剰なプロパティチェックをしてくれているのでエラーがでますが、
const userProfile = {id: "1", email: "fa@gmail.com", password: "000", name: "222", job: "engineer"}
insert(userProfile)
- のようにリテラルじゃないケースの場合TypeScriptは過剰なプロパティチェックをやめてしまいます
- insertがUser型のみしか受け入れたくないように修正してください
答え
type User = {
id: string
email: string
password: string
}
type UserProfile = User & { name: string, job: string }
function insert<T extends User>(user: Exact<User, T>) {
console.log(user)
}
type Exact<TExpected, TActual extends TExpected> = TExpected extends TActual ? TExpected: never;
const userProfile = {id: "1", email: "fa@gmail.com", password: "000", name: "222", job: "engineer"}
insert(userProfile) // 期待するerror
// other: 関数の中で過剰なオブジェクトを受け入れて、type guardで型チェック。正統だったら実行するなども
function _insert(user: User) {
console.log(user)
}
function insertUser(user: unknown) {
if(isUser(user)) {
//user arg is now verified to be of shape User, safe to insert into db
_insert(user);
}
}
//do your check
function isUser(user: unknown): user is User {
if(typeof user !== "object" || user === null)
return false;
const neededKeys: Array<keyof User> = ["email", "id", "password"];
const actualKeys = Object.keys(user);
return new Set([...neededKeys, ...actualKeys]).size === neededKeys.length;
}
備考
Exact 型ユーティリティ
type Exact<TExpected, TActual extends TExpected> = TExpected extends TActual ? TExpected: never;
- 正確に一致する場合に限り、TExpected を返す型。それ以外の場合は never を返す。
問71
obj からそれぞれの値で且つliteralなunion typeを作ってください。(期待する結果 -> "A" | "B" | 1)
const obj = {a: "A", b: "B", c: 1}
答え
const a = {a: "A", b: "B", c: 1} as const
type LiteralsUnion = typeof a[keyof typeof a]
問72
const userByIdResult = {
data: {
userById: {
id: 123,
username: 'joseph'
}
}
}
const userByUsernameResult = {
data: {
userByUsername: {
id: 123,
username: 'joseph'
}
}
}
-
userById と userByUsername は同じ型を返します。
-
ただこちら
type GraphQLResponse<QueryKey, ResponseType> = {
data: {
[QueryKey]: ResponseType
}
}
interface User {
username: string
id: string
}
type UserByIdResponse = GraphQLResponse<'userById', User>
type UserByUsernameResponse = GraphQLResponse<'userByUsername', User>
- ではうまくいきません。
- 正しく修正してください
答え
type GraphQLResponse<QueryKey extends string, ResponseType> = {
data: {
[K in QueryKey]: ResponseType
}
}
interface User {
username: string
id: string
}
type UserByIdResponse = GraphQLResponse<'userById', User>
type UserByUsernameResponse = GraphQLResponse<'userByUsername', User>
補足
- QueryKey は文字列型に限定されるべき→
QueryKey extends string
- マップされた型(Mapped Types)の構文で,
- QueryKey に含まれる各キーに対して型を適用する→
[K in QueryKey]
問73
- このようにすると全てを許してしまいます
type TransitionStyles = {
entering: Object
entered: Object
exiting: Object
exited: Object
[key: string]: Object
}
const b:TransitionStyles = {
entered: {},
exiting: {},
exited: {},
entering: {},
eee : "fa"
}
- 特定のkeyだけにしてください
答え
type TransitionStyles<T extends string> = {
[K in T]: Object
}
type TransitionStylesKeys = "entered" | "exiting" | "exited" | "entering"
const b:TransitionStyles<TransitionStylesKeys> = {
entered: {},
exiting: {},
exited: {},
entering: {},
fafa: "fa"
}
問74
cの値を型とするtypeを書いてください
const a = {a: "A", b: "B", c: 1}
答え
const a = {a: "A", b: "B", c: 1}
type ExtractCValue<T extends Record<string, string | number>, V extends T[keyof T]> = V extends number ? V : never
const c: ExtractCValue<typeof a, 1> = "aaa"
問75
- fooの値のみが入った1 | 3 | 5)[]の型を返す関数を書いてください
const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ]
問76
こちらのname型を抽出してください expect {id: number}
const value = {
data: {
name: {id: 1}
}
}
問77
- こちら
type ActivityLog = {
lastEvent: Date
events: {
id: string
timestamp: Date
type: "Read" | "Write"
}[]
}
let activityLog: ActivityLog = { lastEvent: new Date(), events: [{ id: "1", timestamp: new Date(), type: "Read"}]}
function get(activityLog: ActivityLog, key: string){
return activityLog[key] // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ActivityLog'.
No index signature with a parameter of type 'string' was found on type 'ActivityLog'.(7053)
}
let lastEvent = get(activityLog, "lastEvent") // any
-
はkeyをstringにannotateされているため、activityLog[key]がいろいろな型を返す可能性があるためTypeScriptはanyを返しています
-
keyがactivityLogに存在するプロパティのみ渡すことを保証し、その後のルックアップで適切に推論されるようにgetを型付けしてください
答え
type ActivityLog = {
lastEvent: Date
events: {
id: string
timestamp: Date
type: "Read" | "Write"
}[]
}
let activityLog: ActivityLog = { lastEvent: new Date(), events: [{ id: "1", timestamp: new Date(), type: "Read"}]}
function get<T, K extends keyof T>(activityLog: T, key: K){
return activityLog[key]
}
let lastEvent = get(activityLog, "lastEvent")
// or
type Get = <T, K extends keyof T>(activityLog: T, key: K) => T[K]
let get:Get = (activityLog, key) => {
return activityLog[key]
}
問78
type ActivityLog = {
lastEvent: Date
events: {
id: string
timestamp: Date
type: "Read" | "Write"
}[]
}
let activityLog: ActivityLog = { lastEvent: new Date(), events: [{ id: "1", timestamp: new Date(), type: "Read"}]}
type Get = <T, K extends keyof T>(activityLog: T, key: K) => T[K]
const get: Get = (activityLog, key) => {
return activityLog[key]
}
- のgetがよりネストされた値の型を返す (get(activityLog, "events", 0, "id")を実行したら stringを返す) ようにgetをオーバーライドで定義して、 内部の実装を変更して それぞれのkeyを参照に型を返すようにしてください
答え
type ActivityLog = {
lastEvent: Date
events: {
id: string
timestamp: Date
type: "Read" | "Write"
}[]
}
let activityLog: ActivityLog = { lastEvent: new Date(), events: [{ id: "1", timestamp: new Date(), type: "Read"}]}
type Get = {
<O extends object, K1 extends keyof O>(o: O, key: K1): O[K1]
<O extends object, K1 extends keyof O, K2 extends keyof O[K1]>(o: O, key: K1, key2: K2): O[K1][K2]
<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>(o: O, key: K1, key2: K2, key3: K3): O[K1][K2][K3]
}
let get:Get = (o: any, ...keys: string[]) => {
let result = {...o}
keys.forEach(k => {
result = result[k]
return result
})
}
let lastEvent = get(activityLog, "events", 0, "id")
問79
このような型がある。こちらを型パラメータにそれぞれのkeyを渡した時値型が返ってくる型を作ってください
type Type = {
"a": string,
"b": number,
"c": (ind: string) => void
}
答え
type Type = {
"a": string,
"b": number,
"c": (ind: string) => void
}
type Key<K extends keyof Type> = Type[K]
const a:Key<"a"> = "a"
問80
下記は参照すると、エラーになります。TypeScriptが正しく推論できるようにしてください
type SomeObject =
| {
a: string;
}
| {
b: number;
}
| {
c: boolean;
};
declare const someObj: SomeObject;
if (someObj.c) { // error
}
答え
type SomeObject =
| {
type: "a"
a: string;
}
| {
type: "b"
b: number;
}
| {
type: "c"
c: boolean;
};
declare const someObj: SomeObject;
if (someObj.type === "c") {
someObj.c
}
こうしたらもっと確実
function isTypeC(obj: SomeObject): obj is { type: "c"; c: boolean } {
return obj.type === "c";
}
if (isTypeC(someObj)) {
console.log(someObj.c);
}
問81
??の箇所をHoge型になるように代入してください
interface Hoge {
a: string;
(arg: string): void;
}
const hoge = (arg: string) => { console.log(arg); }
const a = {a: "foo"}
const f: Hoge = ??
答え
interface Hoge {
a: string;
(arg: string): void;
}
const hoge = (arg: string) => { console.log(arg); }
const a = { a: "foo" }
const f:Hoge = Object.assign(hoge, a)
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// Expected output: Object { a: 1, b: 4, c: 5 }
問82
必須プロパティをUnion型で返す型を作ってください
答え
type RequireKeys<T> = {[K in keyof T]-?: {} extends Pick<T, K> ? never : K}[keyof T]
問83
オプショナルなキーだけをUnion型で返す型を作ってください
答え
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
問84
type Union = {a: "a"} | {b: "b"}
を使って {a: "a", b: "b"}
になるようなtype InterSection を作ってください
答え
type GetKeys<U> = U extends Record<infer K, any> ? K : never
type UnionToIntersection<U extends object> = {
[K in GetKeys<U>]: U extends Record<K, infer T> ? T : never
}
type Union = { a: "a" } | { b: "b" }
type Transformed = UnionToIntersection<Union>
問85
enumのvalueであるfooとbarをkeyにする型を作ってください。(期待する型。type A = {foo: string, bar: string})
enum FooKeys {
FOO = 'foo',
BAR = 'bar',
}
答え
type A = Record<FooKeys, string>
問85
このように
const obj = {
foo: "foo",
bar: "bar"
}
const keysArray = Object.keys(obj)
console.log(keysArray) // string[]
Object.keys()を使うと返型がstring[]になってしまいます playground
推論されるようにしてください
答え
// Assertion
const keysArray = Object.keys(obj) as (keyof typeof obj)[]
// or
// うまくいくkeysという関数を作ってしまう
export const keys = Object.keys as <T>(o: T) => (Extract<keyof T, string>)[];
const obj = {
foo: "foo",
bar: "bar"
}
export const keys = Object.keys as <T>(o: T) => (Extract<keyof T, string>)[];
const keysArray2 = keys(obj) // ("foo" | "bar")[]
問86
TypeScript(4.1)未満ではこちら
const date = (year: number, month: number, date: nubmer) => {
return `${year}-${month}-${date}`;
};
const result = date(2020, 10, 10) // string
のように2020-10-10にもかかわらずstringとして推論されてしまっていた Template Literal Typesを使ってresultの型が 2020-10-10になるようにしてください。
答え
const date = <Y extends number, M extends number, D extends number>(year: Y, month: M, date: D) => {
return `${year}-${month}-${date}` as `${Y}-${M}-${D}`;
};
const result = date(2020, 10, 10) // "2020-10-10"