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版です。
- わたしについて
- 随時更新して行きますのでスターつけていただけると励みになります。
- 最初から答えが見えているのは都度操作させないためです。
ちょっとやってみましょう
問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>
問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;
}
問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を参照するとnumber
と undefined
の可能性があるから。
初期化なし、型注釈なしの変数はコンテキストによって型が変わる(アンチパターン)
問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に伝えることができる
問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))
- contextual tyipingが不要な引数を先に型推論。100に対して型推論。numberを得る
- 得られた情報からTがnumberに推論される
- contextual typingが必要な引数を型推論する。 num => String(num ** 2)に対して型推論。 (num: number) => stringを得る
このときcontextual tyingは
(arg: T) => R
型だがT
は判明しているので (arg: number) => R - 再び型引数を推論する型引数
R
がstring
に推論される
型引数T
とR
の推論結果が決まるタイミングが異なる
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
問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">
問31
stringとnullableな配列の型を作ってください
let arr: (string | null)[] = []
問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)
関数 a
はServiceUser
or User
or AppUser
をa
に渡してそれを返す関数です。
期待型は ServiceUser | User | AppUser
になっています。
これを それぞれ ServiceUser
はserviceID
、User
はid
、AppUser
はappId
を返す関数に直して、 期待型を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
問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で型定義してください。(それぞれ isService
、isAppUser
、任意で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)
問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();
問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;
1
と2
はそれぞれエラーになりますかなりませんか
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;
は代入できるか。それぞれT
とU
の型は何か
// 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
U
をextends
している値T
はnever
を返し、そうでない値型は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
問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
問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>>
問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)
問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;
}
}
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をコンパイラに伝えなくてはならない。さらに返値の方もコンパイラに伝えなくてはならない
問63 下記のような
const values = ['A', 'B']
type Foo = OneOf<values>
const v1: Foo = 'A' // ok
const v2: Foo = 'D' // error
配列の各要素(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"
問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" });
問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;
問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
})
正しく修正してください
// コンパイラは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/
問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;
}
問71
こちらの
const obj = {a: "A", b: "B", c: 1}
obj
からそれぞれの値で且つliteral
なunion type
を作ってください。(期待する結果 -> "A" | "B" | 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>
ではうまくいきません。
正しく修正してください
// 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>
問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
は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
このような
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"
問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")
}
}
問81
こちらの
interface Hoge {
a: string;
(arg: string): void;
}
const hoge = (arg: string) => { console.log(arg); }
const a = {a: "foo"}
const f: Hoge = ??
??
の箇所を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)
問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"となる
問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だけで作られていないので問題の趣旨と違います
問83
keyof Record<K, T>
Record<K, T>[K]
問84
下のような
enum FooKeys {
FOO = 'foo',
BAR = 'bar',
}
enumのvalueであるfoo
とbar
を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")[]
問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"
問87
String Literal "foo"
を型引数として渡すと文字の先頭を大文字にする型("Foo"
型)が返ってくるGetterName<T>
を定義してください
type GetterName<T extends string> = `${capitalize T}`;
type GotNameWithCapitalize = GetterName<'foo'>; // 'Foo'
問88
WIP
https://tech-1natsu.hatenablog.com/entry/2019/02/09/014218
参照