Skip to content

Instantly share code, notes, and snippets.

@lanqy
Forked from kenmori/TypeScriptPractice.md
Created May 19, 2024 01:52
Show Gist options
  • Save lanqy/262ce950936418db80847cccbb73cbaa to your computer and use it in GitHub Desktop.
Save lanqy/262ce950936418db80847cccbb73cbaa to your computer and use it in GitHub Desktop.
TypeScript 練習問題集

TypeScript 練習問題集(Practice TypeScript and Playground) latest update(2020/9/21)

TypeScript and Playground練習問題集更新情報

WIP

- 問題を追加(2020/9/21)
- 問題を追加(2020/9/18)
- 問題を追加(2020/8/31)
- 問題を追加(2020/8/16)
- 問題を修正(2020/5/10)
- 問題を追加(2020/5/6)
- 問62、63、64を追加(2020/4/29)
- 問2の問題を修正(2020/4/27)
- 説明・例文を追加(2020/4/26)
  • こちらは @bukotsunikki自身が学習するための問題集です。
  • 以前JavaScript問題集を作りましたがそれのTypeScript版です。
  • わたしについて Twitter URL
  • 随時更新して行きますのでスターつけていただけると励みになります。
  • 最初から答えが見えているのは都度操作させないためです。

ちょっとやってみましょう

問0

こちら

const greeting = (value) => "hello!" + value

関数greeting"hello!world!"など、任意の文字列返す関数です。 引数valueに型注釈してください

ここまでが「問題」です。 以下「答えの一例」になります。

const greeting = (value: string) => "hello!" + value

では早速始めましょう


問1

こちら

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

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

type PartialFoo = Partial<Foo>;

問2

こちら

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

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

type RequireA = Required<Foo>;

問3

こちら

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

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

type Picked = Pick<Foo, "name">

問4

こちら

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

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

type Omited = Omit<Foo, "age">;

// Omited {
//    name?: string | undefined;
// }

問5

こちら

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

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

{ name: string, age: number }

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

問6

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

// Conditional types

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

// ()=> void extends Functionは互換性がある
// "hello!" extends String

問7

下記

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

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

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

const obj = {
  name: "kenji",
  age: 99,
  add: () => 1 * 2
}

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

type result = FunctionPropertyNames<Part>

playground

問8 neverとはどんな型ですか

・絶対にreturnされない関数
・常にthrowされる関数

例えば

function foo(x: string | number): boolean {
  if (typeof x === "string") {
    return true;
  } else if (typeof x === "number") {
    return false;
  }
  return fail("Unexhaustive!"); //ここはreturnされないので neverが返っている。boolean型が返る関数なのでError
}

function fail(message: string): never { throw new Error(message); } // 常にthrowされるのでnever型が返る


// 1. 型推論の結果、取り得る型が無い状態になった時の変数・メンバーに付けられる型

const bool = true
if(bool){
    const a = bool; // boolean
} else {
    const b = bool; // never
}

// 2. return文が無く、かつ(無限ループなどで)関数末尾に到達しない関数/アロー関数に対して推論される戻り値の型

問9

これは

(...args: any[]) => any

どういう意味ですか?

// 関数ならなんでもOK

Type inference in Conditional types

問10

これは

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

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

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

問11

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

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

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

問13

こちら

let createObj = (obj) => {
 let o = {}
 for(const key in obj){
   o[key] = String(obj[key]);
 }
 return o;
}

const anotherFun = createObj;

のcreateObj型を定義してください

let createObj = <T extends unknown>(obj:T) => {
 let o = {} as {[P in keyof T]: string}
 for(const key in obj){
     o[key] = String(obj[key]);
 }
 return o;
}
const anotherFun = createObj;


////or////
let createObj = <T, >(obj:T) => {
 let o = {} as {[P in keyof T]: string}
 for(const key in obj){
   o[key] = String(obj[key]);
 }
 return o;
}

playground

問14 TODO

neverはunion型の中では消えるを問題にする

問15 こちらの

arr(["a", 1]);

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

let arr = <T extends any[]>(...rest: T) => {
   return rest
}

arr(["a", 1]);

問16

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

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

let a = "a" //string

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

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

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

問17

下記

let a;

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

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

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

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

問18

WIP

問19

こちら

let a = 1
const num = a && "hoge";

型推論は何ですか

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

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

基本的にはUnion型の型情報をどれかに絞り込むこと。
コンパイル時のタイプチェッカーに実行時の値に関する型情報を伝えること

絞り込む方法はいくつかあります

  • Type predicates
  • Instanceof Narrowing
  • typeof Narrowing
  • Subtyping

Type Narrowing in TypeScript Widening and Narrowing in Typescript

問21

  • 「return文はあるけどreturnせずに関数が終了する場合がある」 -> 例: string | undefined
  • 「return文がない」 -> void これらの使い分けを教えてください

voidは返す値をコンパイラに「無視してください」と伝える場合に使います。 実際には、runtime時、undefinedとして値は返っています。

undefinedはundefinedという意味のある値を返し、呼び出し元で使う場合

72 なぜ TypeScriptはvoid とundefinedを区別しているか

voidはその真偽を評価させることはできないところがundefinedとの違い functionのcallbackで使う場合promptやalertなど実行するのみで何も返さない型のreturn型にvoidではなくundefinedを指定すると、明示的にundefinedを返さなくてはいけない。voidに指定すると、返す型に「意味がない」、「単に無視して」とTSに伝えることができる

playground

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

TypeScriptは引数の型注釈で型を宣言しなくてはいけない、が、

型推論の時点で期待される型があらかじめわかっている状態(contextual tyipingがある状態)なら書かなくても良い

type Func = (arg: number) => number
const double: Func = function (num) { return num * 2}

numは本来型注釈を書かなくてはいけないが、文脈からdoubleに入る関数はFunc型でなくてはいけないことがわかっている。
これがcontextual typingがある状態

・文脈からわかる型。
・関数の引数や戻り値を文脈から推論させること。
・型推論の時点で期待される型があらかじめ分かっている場合を「contextual typeがある」という

これは以下のときに重要になる
・callback関数を別の変数に割り当てると、contextual typeがなくなる
・型引数を明示しない時、型推論が推論される順番を理解するとき

contextual typingが発生する場面
- 変数の型注釈によって型推論中の式の型があらかじめわかっている場合
- 関数引数のcontextual typing
- 関数の返値のcontextual typ

メモ

型引数の推論について 実行時に型引数を省略すると TやRといった型引数も一緒に推論される この場合引数から推論される

function apply<T, R>(value: T, func: (arg: T) => R): R {
  return func(value);
}

// res は string 型
const res = apply(100, num => String(num ** 2));
  • 呼び出し時仮引数から受け取り側の引数の型が決まる
  • 引数の型が決まってから関数の型が判明する

引数の型より型変数の推論ができていないといけない | 型引数の推論と、引数で関数を渡す場合のその引数の型推論はどのような順番で解決されるか

contextual typingが必要な引数だけ後回しにする

apply(100, num => String(num ** 2))

  1. contextual tyipingが不要な引数を先に型推論。100に対して型推論。numberを得る
  2. 得られた情報からTがnumberに推論される
  3. contextual typingが必要な引数を型推論する。 num => String(num ** 2)に対して型推論。 (num: number) => stringを得る このときcontextual tyingは (arg: T) => R型だがTは判明しているので (arg: number) => R
  4. 再び型引数を推論する型引数 Rstringに推論される

型引数TRの推論結果が決まるタイミングが異なる

Tの型引数の推論結果はその型引数が使われた時点で確定する

型推論結果は処理の順番に依存する

オーバーロードシグネチャ

問23

こちらのコード

type MyObj = {
  name?: string;
}

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

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

問24

こちらの

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

obj is Array<string>の説明をしてください

このように返り値をobj is Array<string>のように宣言している関数は
真偽値が返らなくてはならず、
isStringArray関数の返値がtrueならobjは`Array<string>型`が返ることを指定しています。

問25

こちらの

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

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

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

問26

WIP enumの使い方 1

enum Weekend {
  Friday = 1,
  Saturday,
  Sunday
}

function getDate(Day: string): Weekend {
    if ( Day === 'TGIF') {
        return Weekend.Friday;
    }
    return Weekend.Saturday
 }
let DayType: Weekend = getDate('TGIF');


// string enum
enum Weekend {
  Friday = 'FRIDAY',
  Saturday = 'SATURDAY',
  Sunday ='SUNDAY'
}
const value = someString as Weekend;
if (value === Weekend.Friday || value === Weekend.Sunday){
    console.log('You choose a weekend');
    console.log(value);
}

// enumを使用するのが最適かつ非常に効率的な場所と適切な使用例があります

// 列挙型は、他のTypeScriptデータ型と同じように、配列の初期化内で使用できます。
// これは簡単な例です。

enum NigerianLanguage {
  Igbo,
  Hause,
  Yoruba
}

//can be used in array initialisation
let citizen = {
  Name: 'Ugwunna',
  Age: 75,
  Language: NigerianLanguage.Igbo
}

列挙型は理想的には、週の7日のように、定数と見なすことができる明確な値がある状況で使用されるべきです。
enum Days {
  Sunday = 1,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}

// 列挙型は、文字列または定数を変数で表す必要がある場所でも使用できます。

// TypeScriptの列挙型は、次の場所では使用しないでください。

// 列挙型メンバーの値を再割り当てまたは変更する予定の場合、enumは型保証されているため、再割り当て時にコンパイルエラーが返されます。
// 動的な値を記録したい場合、enumは有限項目に最も適しており、その背後にある一般的な考え方はユーザー定義の定数システムを作成するのを助けることでした
// 列挙型を変数として使用することはできません。そうするとエラーが返されます

問27

WIP unknown type

unknown type

問28

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

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

問29

こちら

function fa(callback, e){
  return callback(e);
}

const fun = (e) => 1 * e;
const v = fa(fun, 1);

Parameter 'callback' implicitly has an 'any' type.Parameter 'e' implicitly has an 'any' type. に対応してください(callbackに型付けしてください)

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

の型を説明してください

// "tokyo"リテラル型を値としてもつプロパティだけを抜き出した型を定義しています

type YukariNoChi = "tokyo"

const obj = {
    name: "kenji",
    age: 99,
    born: "tokyo",
    live: "tokyo"
}
type Obj = {
    name: string,
    age: number,
    born: YukariNoChi,
    live: YukariNoChi
}

const obj2 = {
    born: "tokyo",
    live: "tokyo"
} as const

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


function fun(onlyYukari: OnlySpecificProperty<Obj>){
    return onlyYukari
}
const result = fun(obj2); // Pick<Obj, "born | live">

playground

問31

stringとnullableな配列の型を作ってください

let arr: (string | null)[] = []

playground

問32

こちらの

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

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

type F = {
    foo: string;
    bar: number;
}

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

問33

type Exclude<T, U> の説明をしてください

ExcludeはTがUに代入可能ならnever、そうでない場合Tを返すconditionalTypeです

use case
type Q = Exclude<string | number, boolean | string | number>
//boolean

type Q = Exclude<string | number | undefined, any>
// never

問34

こちら

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

interface Person {
    id: number
    detail: Detail
}

person({detail: {name: "fafa"}, id: 1 });

はdetailが初期化された時 undefinedが渡って来てもいいように対応してください

export defaut function person({ detail = {}} : Person) { // error
  return <div>{detail.name}</div>
};
// このままだと
// Property 'name' is missing in type '{}' but required in type 'Detail'.
// になります

defaultaParameterを設定し、アサーションします

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

問35

reactでsetStateをする際に

interface State {
 name: string
 age: number
}

this.setState({name: "kenji"}) // Error
this.setState({name: "kenji", age: this.state.age}); // ok

このように特定のState.propertyのみを渡すとエラーになる

全てのpropertyを渡さないでもいいようにしてください。

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

問36

こちらは

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)

関数 aServiceUser or User or AppUsera に渡してそれを返す関数です。 期待型は ServiceUser | User | AppUser になっています。 これを それぞれ ServiceUserserviceIDUseridAppUserappIdを返す関数に直して、 期待型をstringにしてください

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

playground

問37

WIP 問題文。

上の問題の

function a(o: ServiceUser | User | AppUser){
  if("serviceID" in o) return o.serviceID;
  if("appID" in o) return o.appID;
  return o.id;
}

を 独⾃定義 TypeGuardで型定義してください。(それぞれ isServiceisAppUser、任意でisUser関数を作り、ifのコンディション内で実行。返す値がそれぞれのプロパティを持つようにして、型付けされていることを確認してください)

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)

playground

問38

こちら

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

a(o)
a();

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

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

a(o)
a();

playground

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

なぜコンパイルエラーになるのですか?説明してください

// NotHummanはAnimal型かBird型の可能性があるので、それを区別してからではないと一方にしかないproperyへのアクセスはできません。
// 型を確定後に参照する必要があります
type NotHumman = Animal | Bird

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

問40

こちら

declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(action: (done: DoneFn) => void,timeout?: number): void;

コールバックに渡す引数の数が違うのでオーバーライドしてあります。修正してください

declare function beforeAll(action: (done: DoneFn) => 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?

修正してください。

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

// TypeScript は関数呼び出し時に最初にマッチしたオーバーライドを選ぶので、any だと最初に必ずマッチしてしまう

問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

こちら

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

// 1
y = x;

// 2
x = y;

12はそれぞれエラーになりますかなりませんか

y = x; // ok!
x = y; // error

//返る型が同じ場合、引数の数は関係ない。代入元が代入先の引数を持っているかどうか。

// example
type F = (name: string, n: number) => void;
let f: F = (value: string) => {
  //実装は使わないでもokだが
  console.log("here");
};
f("how", 2); //渡す際に満たさないといけない,

問44

こちら

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

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

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

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

x = y; // OK
y = x; // エラー。xの戻り値には location プロパティがない

// 代入元の返り型は代入先のプロパティを含んでいないといけない

問46

こちら

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

は代入できるか。それぞれTUの型は何か

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

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

なぜですか

// objの型はStrFuncかNumFuncの型であり、それぞれの引数の型が違うためどちらの関数が呼び出されてもいいようにどちらの引数にも対応できる型を渡す必要があります


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

こちらは

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

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

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

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

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

// オプショナルを使わない場合はプロパティは存在はして居ないといけません。

let obj: MyObj = {
  name: "kenji",
  age: undefined
};
// なら可能
存在もして居ない場合は`?`を付与すること。

問49

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

//https://docs.solab.jp/typescript/ambient/declaration/

TypeScript では console.log などを呼び出してもコンパイルエラーにはなりません。 
これは、TypeScript コンパイラがデフォルトで lib.d.ts という宣言ソースファイルを利用しており、
lib.d.ts には次のようなアンビエント宣言が記述されているためです。

// lib.d.ts
declare var console: Console;

問50

こちらは

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

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

// オブジェクトリテラル型はFooが知っているプロパティのみ代入可能です。ageは知りません。
// これを回避するためにはオブジェクト型にすることです。

interface Foo {
  name: string;
}
const other = { name: "kenji", age: 90 };
// otherで推論が下記のように変わる
const other: {
  name: string;
  age: number;
}
let obj: Foo = other;

// or

//何が入ってくるかわからない場合
interface Foo {
  name: string;
  [other: string]: any; //here
}

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

問51

こちらは

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

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

let foo:{ [index: string]: { message: string }} = {}
foo["a"] = { message: "some message"};

問52

こちらは

const tupleStrNum = ["X", 2];

型推論で(string|number)[]になります。

[string, number] とするにはどうしたらいいですか

const tupleStrNum = ["x", 2] as [string, number];
//const tupleStrNum: [string, number] = ["X", 2];

問53

こちらを

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

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

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

const b: RecursivePartial<SomeObject> = {}
b.thirdKey = {}

/// 一部のプロパティをのぞいて再帰的にオプショナルにする型
type PartialExcept<T, K extends keyof T> = RecursivePartial<T> & Pick<T, K>;

//ok
const a: PartialExcept<SomeObject, "thirdKey"> = {
    firstKey:   "",
    secondKey:  "",
    thirdKey:   { id: { name: {}} }
}
// ok
const b: PartialExcept<SomeObject, "thirdKey"> = {
    thirdKey:   { id: { name: {}} }
}
// error
const c: PartialExcept<SomeObject, "thirdKey"> = {}

問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

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

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

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

問56

こちらの

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

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

//type T3 = "name" | "age"

問57

TODO

enum StatusEnum { RootAdmin = "RootAdmin", Admin = "Admin" }
type T2 =  Partial<Record<StatusEnum, number | null>>
const t2:T2 = { RootAdmin: 0 }

問58

こちらの

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

を参照に、

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

type F<T> = T extends (a: infer P) => any ? P : T;
type User = { name: string, age: number}
const f = (a:User) =>  a

const a:F<User> = f({name: "kenji", age: 9}); // User
const b:F<string> = "hello" //string

playground

問59

下記のような

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

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

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

// 別解
type Value<T> = { [K in keyof T]: T[K] }[keyof T]
type ValueResult = Value<User> // string | number

playground

問60

こちらの型

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

の値の型がnumberのものだけを抽出した型を作ってください。 期待する結果 {age: number, id: number}

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

playground

問61 下のようなコードがあります

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 // Operator '+' cannot be applied to types 'string | number' and 'number'.

上の場合unionTypeなため+で加算しようとするところでエラーになります。こちらを渡ってきた型を推論するようにしてください

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)

playground

問62 こちらは

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

playground

numberかstringが渡ってくることを想定して作られた関数です。 2つは同じ型の引数を取るため、型をTとして使おうとしています。 ただ、コンパイルエラーになっています。正しく修正してください

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
}
 throw Error("not Support")
}

関数の中のgeneric parameterの型は絞り込むことはできない。なのでaをテストした時、bをコンパイラに伝えなくてはならない。さらに返値の方もコンパイラに伝えなくてはならない

playground

問63 下記のような

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

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

playground

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

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');
type Foo = OneOf<typeof values>;

リテラルなstring、("A"|"B")[]にする必要がある。 1 の方は一度関数を通してそれを得て、 OneOfの方で要素の型を取得している 2 の方はReadOnlyしか受け付けないOneOf。constでReadonlyにしてから渡すとその要素を受け取れる どちらも "A" | "B" が返る

// 1
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"

or

type OneOf<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ElementType> ? ElementType : never;
const values = ["A", "B"] as const // readonly ["A", "B"]
type Foo = OneOf<typeof values>; // "A" | "B"

playground

問64 こちら

const a: Record<string, string> = {
    doorToDoor: "delivery at door",
    airDelivery: "flying in",
    specialDelivery: "special delivery",
    inStore: "in-store pickup",
};
const aa = a["name"] // stringが入ってしまう

a に割り当てられたオブジェクトのプロパティ以外を参照できないようにしてください

プロパティ名のUnionTypeを作る必要があります

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

問65

こちらの

type A = { name: string }
type B = { age: number }

AとBのkeyであるnameとageを合わせたtype、 name | ageとなるUnion型を作ってください

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

問66

こちらの

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

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

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

type FooType = MyUnionType['foo']
// FooType = "a" | "b" | "c"


//or

type PickField<T, K extends string> = T extends Record<K, any> ? T[K] : never;

type FooType2 = PickField<MyUnionType, 'foo'>
// type FooType2 = "a" | "b" | "c"

問67 こちらの

interface Foo {
    foo: number;
    common: string;
}

interface Bar {
    bar: number;
    common: string;
}

function foo(arg){
  return arg.foo
}

const result = foo({foo: 9});

関数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;
}

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

playground

問68 こちらは

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;

現状mapに割り当てられている実際のproperty以外のプロパティを受け入れてしまっています 実際に渡したもpropertyをもつ型のみを受け入れるようにしてください(map.weoiroweiroewをエラーにししてください)

また type MyKeys = keyof map;を期待する結果であるone | two | threeにしてください

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;

playground

問69

こちらは型エラーがでます

playground

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

正しく修正してください

// コンパイラはmeがオブジェクトリテラルなことを知っていてそのkeyもまた具体的に "name" | "age" | "id"ということを知っています。keyはstringでかなり広範囲なものを差し、unionなそれらにアサインできません。この場合、keyが何かをより具体的に明示するする必要があります

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 Pertion]) // ok
})


// see https://www.reddit.com/r/typescript/comments/edzgtw/help_no_index_signature_with_a_parameter_of_type/

// other interfaceに追加する方法 https://fettblog.eu/typescript-better-object-keys/

playground

問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: "[email protected]", password: "000", name: "222"})

過剰なプロパティチェックをしてくれているのでエラーがでますが、 こちら

const userProfile = {id: "1", email: "[email protected]", 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: "[email protected]", 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;
}

playground

see

問71

こちらの

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

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

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

playground

問72

このようなオブジェクトがあります

const userByIdResult = {
  data: {
    userById: {
       id: 123,
       username: 'joseph'
    }
  }
}

const userByUsernameResult = {
  data: {
    userByUsername: {
       id: 123,
       username: 'joseph'
    }
  }
}

userByIduserByUsername は同じ型を返します。

ただこちら

type GraphQLResponse<QueryKey, ResponseType> = {
  data: {
    [QueryKey]: ResponseType
  }
}

interface User {
    username: string
    id: string
}

type UserByIdResponse = GraphQLResponse<'userById', User>
type UserByUsernameResponse = GraphQLResponse<'userByUsername', User>

ではうまくいきません。

playground

正しく修正してください

// 1
// 'QueryKey' is not assignable to type 'string | number | symbol'
// QueryKeyはstring、nnumber、symbleになりうるので型を絞り込む。この場合string

// 2 QueryKeyがanyとして推論されているので修正する(computed valuには識別する名前が必要です) see: https://stackoverflow.com/questions/44110641/typescript-a-computed-property-name-in-a-type-literal-must-directly-refer-to-a-b

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>

playground

問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

こちらの

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

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

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"

// or ...  I think This is not best answer.

問75

こちら

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

のfooの値のみが入った1 | 3 | 5)[]の型を返す関数を書いてください

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)[]

問76

こちらの

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

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

type B = typeof value["data"]["name"]

// or

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

type Data = {
	data : {
		name: {id: number}
	}
}

const getNameValue = (value: Data) => {
	return value.data.name
}

type B = ReturnType<typeof getNameValue>

問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

playground

keystringにannotateされているため、activityLog[key]がいろいろな型を返す可能性があるためTypeScriptはanyを返しています

keyactivityLogに存在するプロパティのみ渡すことを保証し、その後のルックアップで適切に推論されるように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]
}

playground

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

playground

問79

このような

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

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

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

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

playground

問80

こちら

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

declare const someObj: SomeObject;

if (someObj.c) { // error
}

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

// typeというtagを与えます
type SomeObject =
  | {
      type: "a"
      a: string;
    }
  | {
      type: "b"
      b: number;
    }
  | {
      type: "c"
      c: boolean;
    };

declare const someObj: SomeObject;

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

// or

const check = (someObj:SomeObject) => {
switch(someObj.type){
    case "a":
    someObj.a
    return
    case "b":
    someObj.b
    return
    case "c":
    someObj.c
    return
    default:
        new Error("not provided type")
    }
}

playground

問81

こちらの

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

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

const a = {a: "foo"}

const f: Hoge = ??

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

playground

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)

playground

see

問82

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

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


// 例えば Tが{id: string, age?: number}の場合

// -?はオプショナルなキーが渡ってきてもは必須になる。
// type TObj = {id: string, age?: number}
// type RecordT<TObj> = {[K in keyof TObj]-?: TObj[K]}
// type RequireTObj = RecordT<TObj>
// type RequireTObj = {
//     id: string;
//     age: number;
// }
// この処理をしないと最後に
// type RequireTObj = string | number | undefinedのようなオプショナルな場合の値型であるundefinedがついてくる

//  {} extends Pick<T, K>のイメージ。それぞれ
// {} extends {id: string}
// {} extends {age? number}

type Id = Pick<{id: string}, "id">
type Age = Pick<{age?: number}, "age">
const id: Id = {} // error
const age: Age = {} // ok
type Result = RequireKeys<TObj> // "id"

// オプショナルなものは{}にassignableなので neverが返り、そうでないidはKが返る
type BB = RequireKeys<{id: string, age?: number}>
// type BB = {
//     id: "id";
//     age: never;
// }

// ageがnever, idがKの値型になる

// 最後に
// [keyof T]で値型にアクセス
// 値型がnever以外を抽出
// "id"となる

playground

問83

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

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


// 問題82とconditionalが逆

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

// こちらでもうまくいくが
// const aAndB = {a: "a", b: "b"}
// type AandB<Intersection> = { [K in keyof Intersection]: Intersection[K]}
// type Result = AandB<typeof aAndB>
// この場合 typeだけで作られていないので問題の趣旨と違います

playground

問83

keyof Record<K, T>
Record<K, T>[K]

問84

下のような

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

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

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

playground

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

playground

問87

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

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

playground

問88

WIP

playgrond

https://tech-1natsu.hatenablog.com/entry/2019/02/09/014218

参照

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment