Open215

TypeScript 練習問題集を解いていくよ〜

svunsvun

問1

Fooが持つプロパティ全てoptionalにしてください

interface Foo {
    bar: string;
    baz: number;
}
svunsvun

答え

?を使うパターン

interface Foo {
    bar?: string;
    baz?: number;
}

Partialを使うパターン

type PartialFoo = Partial<Foo>;
svunsvun

Partialについて

https://typescriptbook.jp/reference/type-reuse/utility-types/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 });
svunsvun

問2

Fooが持つプロパティ全て必須にしてください

type Foo = {
    name?: string;
    age?: number;
}
svunsvun

答え

type RequireA = Required<Foo>;
  • これは すべてのプロパティからオプショナルであることを意味する?を取り除くユーティリティ型
  • つまり、下記と同義
type Foo = {
    name: string;
    age: number;
}
svunsvun

問3

Fooからnameだけを取得したtypeを作ってください

type Foo = {
    name?: string;
    age?: number;
}
svunsvun

ユースケース

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が何型に変更されても問題ない
svunsvun

問4

Fooからageを省略した型を作ってください

type Foo = {
    name?: string;
    age?: number;
}
svunsvun

問5

userに推論される型は何ですか。またその理由を教えてください。

const user = { name: "kenji", age: 98 };
svunsvun

答え

userに推論される型

{ name: string, age: number }

その理由

JavaScriptのオブジェクトはconstであれ(freezeしない限り)書き込みが可能です。
それそれのプロパティ値はあとで書き込めるようにwindeningされ、それぞれのプロパティの型はプリミティブ型になります。
これをそれぞれのプロパティをリテラル型にするには
as constか型注釈をすることです。

svunsvun

補足①

constアサーション(as const)

https://typescriptbook.jp/reference/values-types-variables/const-assertion

オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになります。

  • readonlyはプロパティごとにつけられるのに対して、constアサーションはオブジェクト全体に対する宣言になる
svunsvun

補足②

Widening(型の拡大)について

https://zenn.dev/estra/articles/typescript-widening#subtype

const str = "text"; // text型として扱われる

const 宣言によってそもそも変数に対しての値の再代入ができなくなるわけですから、一般的な string や number といった型で型推論するよりも、より具体的な情報を持つリテラル型として型推論を働かせた方が合理的になるわけです。

  • オブジェクトや配列はミュータブルなので話は別である

このように、let は const よりも 広い (wide) 型 を受け入れるように型推論が働くというわけです。

  • 逆にLiteral Wideningしてるのに、無理にリテラル型の型注釈をするとDenoではas const を付けろって怒られるらしい
svunsvun

問6

T extends U ? X : Y はどのような意味になりますか

T extends U ? X : Y 
svunsvun

答え

// TがUに代入可能ならXを、そうではない場合Yを返す
// T型がU型と互換性あるならXを、そうでない場合Yを返す
// T型がU型のサブタイプならXを、そうでない場合Yを返す
// T型がU型の部分型ならXを、そうでない場合Yを返す

svunsvun

補足

TSにおける型の互換性

  • 「互換性がある」というのは、一つの型が他の型に安全に割り当て可能であることを意味する
  1. 同じ型: 同じ型は互換性があります。例えば、number 型は別の number 型に割り当て可能です。

  2. サブタイプ: サブタイプ(派生型)は、そのスーパータイプ(基底型)に割り当てることができます。例えば、あるクラス Child が別のクラス Parent から派生している場合、Child のインスタンスは Parent 型に割り当て可能です。

  3. インターフェースとオブジェクトリテラル: オブジェクトリテラルまたはインターフェースが、別のインターフェースで要求されるすべてのプロパティを持っている場合、互換性があります。これは「ダック・タイピング」または「構造的サブタイピング」と呼ばれます。

  4. 関数: 引数の数が少ない関数は、引数が多い関数に割り当て可能です(これは「関数の部分適用」として知られています)。また、戻り値の型が互換性があれば、関数同士も互換性があります。

  5. ジェネリック型: ジェネリック型が同じ型パラメータを持っている場合、または一方の型パラメータが他方の型パラメータのサブタイプである場合、互換性があります。

  6. 列挙型(Enums): 同じ列挙型の異なるメンバーは互換性があります。ただし、異なる列挙型同士は互換性がありません。

  7. ユニオンとインターセクション型: ユニオン型は、その型のいずれかのメンバーに割り当てることができます。インターセクション型は、すべての型を含むオブジェクトに割り当てることができます。

svunsvun

問7

メソッド名だけ取り出した型を作ってください

interface Part {
  name: string,
  age: number,
  add(): number
}
svunsvun

答え

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"

svunsvun

解説

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;

STEP5: マッピングの結果から、never ではないキーだけを抽出

[keyof T]
  1. keyof T は、元の型 T のすべてのキーを取得
  name
  age
  add()
  1. マップされた型で生成された新しい型をこのキーでインデックスし、それぞれのキーに対応する型(K または never)を取得
never | never| add //  never は無視される
svunsvun

問9

neverとはどんな型ですか

type MethodNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
svunsvun

補足

値を持たない

function throwError(): never {
  throw new Error();
}

絶対にreturnされない

function forever(): never {
  while (true) {} // 無限ループ
}

常にthrowされる

function fail(message: string): never { throw new Error(message); }
svunsvun

voidは部品です。neverはうそつきです。

void型はundefinedが代入できますが、neverは値を持てません。
何も返さない関数はvoidを返します。しかし、returnを返すことのない関数(または常にスローする)はneverを返します。voidは(strictNullCheckingなしで)代入することができるものですが、neverはnever以外のものに代入することはできません。

const ok: void = undefined;
const ng: never = undefined;
svunsvun

問10

これは、なにをする型か説明してください(とくにinfer)

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
svunsvun

答え

  • Tが関数ならその関数の戻り値をRにキャプチャしてそれを返す
svunsvun

補足

  • (...args: any[]) は 任意の型の引数を任意の数受け取る という意味
  • infer R はTypeScript の型推論を使用している。ここでは、R は関数の戻り値の型を表し、その具体的な型は関数が実際に呼び出されるまで未知
  • => infer Rは 「この関数が何を返すかはわからないが、返すものを型 R として扱う」

実際の使われ方(ReturnType)

  • 指定した関数の返り値の型を推論
type Func = () => number;
type Result = ReturnType<Func>;  // Result は number 型になります

svunsvun

問11

非同期の中身を取る型を書いてください

svunsvun

答え

type ResolvedType<T> =
T extends Promise<infer R> ? R :
T extends Observable<infer R> ? R :
T;
svunsvun

補足

三項演算子の構造としては下記のようになっている

type ResolvedType<T> =
  Tがプロミス型のとき ? プロミス型を返す : そうでなければAの内容;
// Aの内容
  (TがObservable型のとき ? Observable型を返す :
  Tを返す)

一つずつ見ていくと

  • もし T が Promise に割り当て可能であれば(つまり、T が Promise の形式であれば)、条件は「真」として評価され、R(Promise の解決値の型)が結果として返される。Observable型も同様。どちらでもないならそのままTが返される。
T extends Promise<infer R> ? R :
svunsvun

問12

Nullableな型を作ってください

svunsvun

答え

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 }
svunsvun

補足

  • Tで受け取ったkeyのPを一つずつをその型 or nullの状態にする
type PropNullable<T> = {[P in keyof T]: T[P] | null};
  • PropNullable<User>でnameとageとmoneyにnullを付け足す
svunsvun

問 15

どんな要素の配列が渡されてもいいような型を作ってください。

svunsvun

補足

制約

  • 「T は any[](任意の型の要素を持つ配列)のサブタイプである必要がある」という制約を意味
  • T は配列型でなければならないことが保証
  • number[]、string[]、boolean[] など...

rest parameterについて

https://typescriptbook.jp/reference/functions/rest-parameters

  • ...rest: T はレストパラメータを表し、任意の数の引数を配列として受け取る。
  • この関数は、単一の配列ではなく、複数の引数を配列の要素として受け取ることができる。
svunsvun

問 16

wideningとはなんですか説明してください。

svunsvun

答え

// 型推論によってリテラル型を変数に代入した際にプリミティブ型に拡張されること
// 例えば、

let a = "a" //string

// constでは再代入はできないので`a`型というリテラル型になるが、letは再代入可能なので推論は拡張されプリミティブ型になる

let a: "a" = "a" //"a"

// このように型注釈をつけることでa型というリテラル型になる(型注釈はwideningより優先される)
svunsvun

問 17

aがunion型になるのはなぜか

let a;

if (Math.random() < 0.5) {
    a = 123;
}

console.log(a); // a は number | undefined 型
svunsvun

答え

a宣言時に初期化されていない & 型注釈されていないことでif文がtrueになるまでundefined、その後aを参照するとnumber と undefinedの可能性があるから。 初期化なし、型注釈なしの変数はコンテキストによって型が変わる(アンチパターン)

svunsvun

補足

https://typescriptbook.jp/reference/values-types-variables/undefined-vs-null

  • TypeScript では、初期化されていない変数は undefined 型として扱われる

undefinedは言語仕様上、プログラマーが明示的に使わなくても、自然に発生してくるものです。たとえば、変数を宣言したときに初期値がなければJavaScriptはその変数にundefinedを代入します。

  • a には number 型の値が割り当てられる可能性がある
  • つまり、number or undefined なのではと型推論してくれる
svunsvun

問 19

型推論は何ですか

let a = 1
const num = a && "hoge";
svunsvun
  • 出題者は
  • 0 | "hoge"

// a && b is equivalent to a ? a : b
// aはnumberで、それがfalseになる場合は0。なので 0 | "hoge"
// 仮にaの初期値が""(空文字)の場合、stringなので、falseになる場合は、"" | "hoge"になることを確認してください

svunsvun

問 20

type narrowing(型の絞り込み)とはなんですか

svunsvun

答え

  • 変数や式の型をより具体的な型に制約するプロセス
svunsvun

補足

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);
}

svunsvun

問21

下記の使い分けを教えてください

  • 「return文はあるけどreturnせずに関数が終了する場合がある」 -> 例: string | undefined
  • 「return文がない」 -> void
svunsvun

答え

  • 関数が値を返すかどうか

「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型の関数
}
svunsvun

問 22

contextual typeがある状態というのはどういう状態のことですか

svunsvun

答え

TypeScriptは引数の型注釈で型を宣言しなくてはいけない、が、
型推論の時点で期待される型があらかじめわかっている状態(contextual tyipingがある状態)なら書かなくても良い

type Func = (arg: number) => number;

const double: Func = function(num) {
  // 引数 num の型は number 型と推論されている
  return num * 2;
};
svunsvun

補足

  • 引数でnumber型を貰って、返り値でnumber型を返すというFunc型を宣言している
type Func = (arg: number) => number;
  • それを型として宣言しているので
  • 再度、明示する必要が無い
const double: Func =
svunsvun

問 23

!の意味、危険性について説明をしてください。

type MyObj = {
  name?: string;
}

function foo(obj: MyObj): string {
  return obj.name!.slice(0, 5);
}
svunsvun

問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");
  }
}
svunsvun

問25

'num' is declared but never used.をdisableしてください

(num: number) => { // 'num' is declared but never used.
  return "return";
}
svunsvun

答え

_を使う

(_num: number) => { // ok, _ pre
  return "return";
}
svunsvun

別解

コメントで無効化

// tslint:disable-next-line: no-unused-variable
(num: number) => { // ok, _ pre
  return "return";
}
svunsvun

問28

こちらのエラーをnumberとstringに対応できるように修正してください。

function eachItem(val: number, i: number) {
    return val.toExponential(3);
}
const arr = [4, "fafa", 6];
arr.map(eachItem);
svunsvun

答え

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' ]

svunsvun

問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);
svunsvun

答え

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);
svunsvun

問30

型を説明してください

type YukarinoChi = "tokyo";
type OnlySpecificProperty<T> = Pick<T, {[K in keyof T]: T[K] extends YukariNoChi ? K : never}[keyof T]>;
svunsvun

答え

プロパティの型が"tokyo"(YukariNoChi型)に限定されたプロパティのみを含む新しい型を作成する

svunsvun

備考

  1. keyof Tで型Tのすべてのプロパティキーを取得する
  2. {[K in keyof T]: T[K] extends YukariNoChi ? K : never}で型Tの各キーKに対してYukariNoChi型か否かを判別する
svunsvun

問32

定義元のFを直接編集せずに代入できるように型付けしてください

type F = {
    foo: string;
    bar: number;
}
const E:F = { foo: "fafa", bar: "fafa"} //Error
svunsvun

答え

const E:Record<keyof F, string> = { foo: "fafa", bar: "fafa"}
svunsvun

問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 });
svunsvun

答え

export defaut function person({ detail = {} as Detail} : Person) {
  return <div>{detail.name}</div>
};
svunsvun

備考

{ 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;
}
svunsvun

35問

reactでsetStateをする際に全てのpropertyを渡さないでもいいようにしてください。

interface State {
 name: string
 age: number
}
svunsvun

答え

オプショナルプロパティを使う

interface State {
  name?: string;
  age?: number;
}
svunsvun

問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)
svunsvun

答え

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
svunsvun

備考

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
svunsvun

問37

問36を独⾃定義 TypeGuardで型定義してください

svunsvun
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)
svunsvun

問38

defaultValueとany型に対応してください

const o = { name: "hoge" }
function a(o){
    return o
}

a(o)
a();
svunsvun

答え

const getDefaultProps = () => {
    return {name: "hoge"}
}
const defaultProps = getDefaultProps();
const o = {name: "hoge"}
function a(o = defaultProps){
    return o
}

a(o)
a();
svunsvun

問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
}
svunsvun

答え

Birdにrunが存在しないから

対処法

const b = (b: NotHumman) => {
    if ("run" in b) { // animalであることが確定する
        b.run
    } else {
        b.fly
    }
}
svunsvun

問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?
svunsvun

答え

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, :)
svunsvun

42問

修正してください

interface Example {
  diff(one: string): number;
  diff(one: string, two: string): number;
  diff(one: string, two: string, three: boolean): number;
}
svunsvun

答え

返る型が同じ場合、可能な限りオプショナルを使いましょう。

interface Example {
  diff(one: string, two?: string, three?: boolean): number;
}
svunsvun

43問

1と2はそれぞれエラーになりますかなりませんか

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

// 1
y = x;

// 2
x = y;
svunsvun

答え

1

  • エラーになりません

2

  • エラーになります
svunsvun

備考

  • TypeScriptでは、関数の割り当てにおいて「余分な」引数がある関数を、「少ない」引数を取る関数の型に割り当てることが可能
svunsvun

問44

1, 2はそれぞれエラーになるかならないか

let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });

// 1
x = y;
// 2
y = x;
svunsvun

答え

1

  • エラーになりません

2

  • エラーになります
svunsvun

問46

代入できるか。それぞれTとUの型は何か

let identity = function<T>(x: T): T {
  // ...
};
let reverse = function<U>(y: U): U {
  // ...
};
identity = reverse;
svunsvun

答え

// OK。anyになります。
(x: any)=>any は (y: any)=>any と互換性がある

svunsvun

脱線

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型

svunsvun

問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'.
svunsvun

答え

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...
svunsvun

問48

Errorになります。なぜですか。また正しく修正してください

interface MyObj {
  name: string;
  age: number | undefined;
}

let obj: MyObj = {
  name: "kenji"
};
svunsvun

答え

ageを定義していないから

修正

interface MyObj {
  name: string;
  age?: number | undefined;
}
svunsvun

問49

TypeScriptでconsole.logを呼び出してもコンパイルエラーにならないのはなぜですか?

svunsvun

答え

declareを使用しているから

declare var console: Console;
svunsvun

問50

なぜコンパイルエラーなのですか? { name: "kenji", age: 90 };が代入できるように修正してください

interface Foo {
  name: string;
}
let obj: Foo = { name: "kenji", age: 90 };
svunsvun

答え

ageプロパティが存在しないから

interface Foo {
  name: string;
+   [other: string]: any; //here
}
svunsvun

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; // これはエラーになる。

svunsvun

問51

fooにanyを注釈しています。インデックスにstring、 値に代入しようとしている型を指定してください

let foo:any = {}
foo["a"] = { message: "some message"};
svunsvun

答え

  • インデックス型の応用
let foo:{ [index: string]: { message: string }} = {}
foo["a"] = { message: "some message"};
svunsvun

問53

再帰的に各プロパティをオプショナルにした型を定義してください

interface SomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   { id: { name: string} }
}
svunsvun

答え

type RecursivePartial<T> = {
    [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
};

type PartialSomeObject = RecursivePartial<SomeObject>;
svunsvun

一部のプロパティをのぞいて再帰的にオプショナルにする型

type PartialExcept<T, K extends keyof T> = RecursivePartial<T> & Pick<T, K>;
svunsvun

問54

プロパティnameの値型がstring | null、ageの値型がnumber | nullの型Userを定義してください

svunsvun

答え

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};
svunsvun

問55

Uをextendsしている値Tはneverを返し、そうでない値型はTを返すDiffを定義してください

svunsvun

答え

type Diff<T, U> = T extends U ? never : T;

const t1:Diff<"a" | "b", "b" | "c"> = "a"; // const t1: "a"
svunsvun

問56

T3の型をおしえてください

const t3 = {name: "kenji", age: 99} as const
type T3 = keyof typeof t3
svunsvun

問58

もし関数型である引数を渡したらその引数が返ってくる型、関数型ではないなら関数が返ってくるF<User>を定義してください。

type User = { name: string, age: number}
const f = (a:User) => a
const a:F<User> = f({name: "kenji", age: 9});
svunsvun

答え

type F<T> = T extends (a: infer P) => any ? P : T;
svunsvun

備考

  • T extends (a: infer P) => anyは、Tが関数型かどうかをチェックします。もしそうなら、その関数が取る最初の引数の型をPとして推論します。
  • もしTが関数型であれば、F<T>はその関数の最初の引数の型Pになります。
  • もしTが関数型でなければ、F<T>はT自身の型になります。
svunsvun

問59

下記のようなUser型がある。こちらのvalueのUnion型を取得する型を定義してください。 string | number

type User = { name: string, age: number }
svunsvun

答え

type Value<T> = T[keyof T]

使い方

type User = { name: string, age: number }
type Value<T> = T[keyof T]
type ValueType = Value<User> // string | number
svunsvun

問60

type User = { name: string, age: number, id: number } からnumberのものだけを抽出した型を作ってください。

svunsvun

答え

type User = { name: string, age: number, id: number }

type Value<T> = { [K in keyof T]: T[K] extends number ? K : never }[keyof T]

type NumberType = Pick<User, Value<User>>
svunsvun

問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
svunsvun

答え

const isNarrowScreen = () => false
export function wideNarrow<T>(wide: T,narrow:T){
    return isNarrowScreen() ? narrow : wide;
}

const a = wideNarrow(0, 8)
const extendedAreaHeight = 26;
const b = a + extendedAreaHeight
console.log(b)
svunsvun

補足

  • +演算子が数値の加算と文字列の連結の両方に使用されるため、演算子の意図が不明確になる
svunsvun

問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;
 }
}
svunsvun

答え

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');
  }
}

svunsvun

補足

  • aもbも型を絞り込む必要がある
  • (a + b) as Tで返り値もT型であることを伝えてると尚、良い
svunsvun

問63

配列の各要素(string)のどれかを割り当てることができるUnion型が返るOneOfを定義してください

const values = ['A', 'B']
type Foo = OneOf<values>

const v1: Foo = 'A' // ok
const v2: Foo = 'D' // error
svunsvun

答え

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"
svunsvun

題意

この問題は、TypeScriptの型システムを使って特定の配列の要素からユニオン型を作成する方法に関するものです。具体的には、与えられた文字列の配列(この例ではvalues)の各要素から、その要素のいずれか一つを表す型(Foo)を生成するOneOfという型エイリアスまたは型ユーティリティを定義することを意味しています。
ただし、TypeScriptの現在のバージョンでは、配列の値から直接型を生成することはできません。つまり、リテラル型の配列(例:['A', 'B'])から直接ユニオン型(例:'A' | 'B')を生成することはできないのです。代わりに、リテラル型のユニオンを手動で定義するか、またはconstアサーションを使用してリテラル型のタプルとして配列を宣言する必要があります。

svunsvun

解説

  • 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(存在しない型)を返す
svunsvun

問64

  • AとBのkeyであるnameとageを合わせたtype、 name | ageとなるUnion型を作ってください
type A = { name: string }
type B = { age: number }
svunsvun

答え

type A = { name: string }
type B = { age: number }
type T1 = keyof (A & B)
svunsvun

問65

こちらの型を type Foos = 'a' | 'b' | 'c' このようになるようにしてください

type MyUnionType =
  | { foo: 'a', bar: 1 }
  | { foo: 'b', bar: 2 }
  | { foo: 'c', bar: 3 }
svunsvun

答え

type MyUnionType =
  | { foo: 'a', bar: 1 }
  | { foo: 'b', bar: 2 }
  | { foo: 'c', bar: 3 }

type FooType = MyUnionType['foo']
svunsvun

問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が入ってしまう
svunsvun

答え

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
svunsvun

解説

  • 下記と同義
const a: Record<"doorToDoor" | "airDelivery" | "specialDelivery" | "inStore", string> = source;
svunsvun

問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});
svunsvun

答え

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" });
svunsvun

問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;
svunsvun

答え

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;
svunsvun

問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
})
svunsvun

答え

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
})
svunsvun

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 型の値に対応する。
svunsvun

問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型のみしか受け入れたくないように修正してください
svunsvun

答え

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;
}
svunsvun

備考

Exact 型ユーティリティ

type Exact<TExpected, TActual extends TExpected> = TExpected extends TActual ? TExpected: never;
  • 正確に一致する場合に限り、TExpected を返す型。それ以外の場合は never を返す。
svunsvun

問71

obj からそれぞれの値で且つliteralなunion typeを作ってください。(期待する結果 -> "A" | "B" | 1)

const obj = {a: "A", b: "B", c: 1}
svunsvun

答え

const a = {a: "A", b: "B", c: 1} as const
type LiteralsUnion = typeof a[keyof typeof a]
svunsvun

問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>
  • ではうまくいきません。
  • 正しく修正してください
svunsvun

答え

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>
svunsvun

補足

  • QueryKey は文字列型に限定されるべき→ QueryKey extends string
  • マップされた型(Mapped Types)の構文で,
  • QueryKey に含まれる各キーに対して型を適用する→ [K in QueryKey]
svunsvun

問73

  • このようにすると全てを許してしまいます
type TransitionStyles = {
  entering: Object
  entered: Object
  exiting: Object
  exited: Object
  [key: string]: Object
}

const b:TransitionStyles = {
	entered: {},
	exiting: {},
	exited: {},
	entering: {},
	eee : "fa"
}
  • 特定のkeyだけにしてください
svunsvun

答え

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"
}
svunsvun

問74

cの値を型とするtypeを書いてください

const a = {a: "A", b: "B", c: 1}
svunsvun

答え

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"
svunsvun

問75

  • fooの値のみが入った1 | 3 | 5)[]の型を返す関数を書いてください
const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ]
svunsvun

答え

const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ]

function getFooValue(arr: typeof objArray){
	return objArray.map(({foo}) => foo)
}
const result = getFooValue(objArray) // (1 | 3 | 5)[]
svunsvun

問76

こちらのname型を抽出してください expect {id: number}

const value = {
	data: {
		name: {id: 1}
	}
}
svunsvun

問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
svunsvun
  • はkeyをstringにannotateされているため、activityLog[key]がいろいろな型を返す可能性があるためTypeScriptはanyを返しています

  • keyがactivityLogに存在するプロパティのみ渡すことを保証し、その後のルックアップで適切に推論されるようにgetを型付けしてください

svunsvun

答え

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]
}
svunsvun

問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を参照に型を返すようにしてください
svunsvun

答え

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")
svunsvun

問79

このような型がある。こちらを型パラメータにそれぞれのkeyを渡した時値型が返ってくる型を作ってください

type Type = {
    "a": string,
    "b": number,
    "c": (ind: string) => void
}
svunsvun

答え

type Type = {
    "a": string,
    "b": number,
    "c": (ind: string) => void
}

type Key<K extends keyof Type> = Type[K]
const a:Key<"a"> = "a"
svunsvun

問80

下記は参照すると、エラーになります。TypeScriptが正しく推論できるようにしてください

type SomeObject =
  | {
      a: string;
    }
  | {
      b: number;
    }
  | {
      c: boolean;
    };

declare const someObj: SomeObject;

if (someObj.c) { // error
}
svunsvun

答え

type SomeObject =
  | {
      type: "a"
      a: string;
    }
  | {
      type: "b"
      b: number;
    }
  | {
      type: "c"
      c: boolean;
    };

declare const someObj: SomeObject;

if (someObj.type === "c") {
    someObj.c
}
svunsvun

こうしたらもっと確実

function isTypeC(obj: SomeObject): obj is { type: "c"; c: boolean } {
    return obj.type === "c";
}

if (isTypeC(someObj)) {
    console.log(someObj.c);
}
svunsvun

問81

??の箇所をHoge型になるように代入してください

interface Hoge {
    a: string;
    (arg: string): void;
}

const hoge = (arg: string) => { console.log(arg); }

const a = {a: "foo"}

const f: Hoge = ??
svunsvun

答え

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)
svunsvun

問82

必須プロパティをUnion型で返す型を作ってください

svunsvun

答え

type RequireKeys<T> = {[K in keyof T]-?: {} extends Pick<T, K> ? never : K}[keyof T]
svunsvun

問83

オプショナルなキーだけをUnion型で返す型を作ってください

svunsvun

答え

type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
svunsvun

問84

type Union = {a: "a"} | {b: "b"} を使って {a: "a", b: "b"} になるようなtype InterSection を作ってください

svunsvun

答え

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>
svunsvun

問85

enumのvalueであるfooとbarをkeyにする型を作ってください。(期待する型。type A = {foo: string, bar: string})

enum FooKeys {
  FOO = 'foo',
  BAR = 'bar',
}
svunsvun

問85

このように

const obj = {
  foo: "foo",
  bar: "bar"
}

const keysArray = Object.keys(obj)
console.log(keysArray) // string[]
Object.keys()を使うと返型がstring[]になってしまいます playground

推論されるようにしてください

svunsvun

答え

// 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")[]
svunsvun

問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になるようにしてください。

svunsvun

答え

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"
svunsvun

問87

String Literal "foo" を型引数として渡すと文字の先頭を大文字にする型("Foo"型)が返ってくるGetterName<T>を定義してください

svunsvun

答え

type GetterName<T extends string> = `${capitalize T}`;
type GotNameWithCapitalize = GetterName<'foo'>;  // 'Foo'