『TypeScript 練習問題集』を解く
『TypeScript 練習問題集』を解いていき、調べたことや疑問点をメモしていきます。
問1
Fooが持つプロパティ全てoptionalにしてください。
interface Foo {
bar: string;
baz: number;
}
解答
Partial
を使ってオプションプロパティにする。
type PartialedFoo = Partial<Foo>;
参考
問2
Fooが持つプロパティ全て必須にしてください。
type Foo = {
name?: string;
age?: number;
}
解答
Required
を使用。
type RequiredFoo = Required<Foo>
参考
問3
Fooからnameだけを取得したtypeを作ってください。
type Foo = {
name?: string;
age?: number;
}
解答
Pickを使用
type PickedFoo = Pick<Foo, "name">
参考
問4
Fooからageを省略した型を作ってください。
type Foo = {
name?: string;
age?: number;
}
解答
Omitを使用。
Fooからnameをpickするのとageをomitするのは同じ結果。
type OmittedFoo = Omit<Foo, "age">
参考
問5
userに推論される型は何ですか。またその理由を教えてください。
const user = { name: "kenji", age: 98 };
解答
{ name: string, age: number }型
答え
- JavaScriptのオブジェクトはconstであれ(freezeしない限り)書き込みが可能です。
- それそれのプロパティ値はあとで書き込めるようにwindeningされ、それぞれのプロパティの型はプリミティブ型になります。
- これをそれぞれのプロパティをリテラル型にするにはas constか型注釈をすることです。
// as const
const user = { name: "kenji", age: 98} as const
// 型注釈
const user: {name: "kenji", age: 98} = { name: "kenji", age: 98}
参考
オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになります。
問6
T extends U ? X : Y
はどのような意味になりますか
解答
TはUがtrueならX型、falseならY型をextends(継承)する
"Uがtrueなら"ってどういう状況??
答え
// TがUに代入可能ならXを、そうではない場合Yを返す
// T型がU型と互換性あるならXを、そうでない場合Yを返す
// T型がU型のサブタイプならXを、そうでない場合Yを返す
// T型がU型の部分型ならXを、そうでない場合Yを返す
// ()=> void extends Functionは互換性がある
// "hello!" extends String
参考
-
T extends U ? X : Y
はConditional Types(条件付き型、型の条件分岐)
-
extendsは型を限定するために使用する
-
例
type User = { id: number, name: string | null, age: number | null, }
-
type PickProps<T, K extends keyof T>
のジェネリクスTにはUser、Kにはageを入れるとすると、PickProps<User, "age" extends keyof User>
となり、PickProps<User, "age" extends "id" | "name" | "age">
と同義になる。 -
つまり、KにはTに含まれるプロパティしか指定できないということ。
問7
メソッド名だけ取り出した型を作ってください
interface Part {
name: string,
age: number,
add(): number
}
解答
Pickでaddメソッド名だけ取り出したうえで、keyofでプロパティ名だけにする
type PartMethodName = keyof Pick<Part, "add">
これでPartMethodName
にカーソルを当てるとtype PartMethodName = "add"
が表示される
参考
答え
interface Part {
name: string,
age: number,
add(): number
}
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
type result = FunctionPropertyNames<Part>
result
にカーソルを当てるとtype result = "add"
が表示される。
私の解答と結果は同じだが、答えは汎用的。
ただ、何をやっているのか理解できない、、
答えの検討
type FunctionPropertyNames<T> = {
type result = FunctionPropertyNames<Part>
FunctionPropertyNamesはジェネリクス化されていて<T>
で型を受け取る。今回はPart型を受け取り、Part型に含まれるメソッドの型だけ返している。返り値がresult型に入る。
[K in keyof T]
Mapping Typesという機能を利用して、in keyof T
でTのプロパティname, age, add()
を取得し、各プロパティのキーK
に対して新しい型[K in keyof T]
を作成している。
参考
インデックス型
例)objのKeyがstring型、valueがnumber型の場合
Kの部分が型変数。任意の名前にできるがKにするのが一般的
let obj: {
[K: string]: number;
};
keyofはオブジェクトのキーをユニオン型に変更する
TのプロパティK(具体的にはPart[name]
, Part[age]
, Part[add]
)がFunction型ならKを、そうでないならneverを返す
T[K] extends Function ? K : never
}[keyof T]
keyof T
でTの全プロパティname, age, add
の型がユニオン型で得られる。
つまり、この場合はnever | never | add
となる。
おそらくnever
は無視されて?addのみが抽出される。
もしこれがなかったら、resultにはTの全プロパティが入る。
参考
keyof型演算子と組み合わせると、オブジェクトの全プロパティの型がユニオン型で得られます。
まだ理解しきれてないのはインデックスの理解不足なのでは?
シンプルにしてみる。
interface Part {
name: string,
age: number,
add(): number
}
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K]
}
type result = FunctionPropertyNames<Part>
この場合のresultはPartと一致する。
type result = {
name: string;
age: number;
add: () => number;
}
単に [K in keyof T]
がキーでT[K]
がバリューなのでは??
ということは
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}
[K in keyof T]
がキーでT[K] extends Function ? K : never
がバリュー
具体的には、[K in keyof T]
でTのキーname, age, add
を取り出していて、
そのそれぞれに対して、T[K]
つまりstring
, number
, Function
について三項演算子で判定して、結果をK
またはnever
で返している。
参考
- Mapped TypesとConditional Typesの組み合わせの例
type Freeze<T> = Readonly<{
[P in keyof T]: T[P] extends object ? Freeze<T[P]> : T[P];
}>;
再挑戦
解答
type GetFunctionName<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}
type NameOfFunction = GetFunctionName<Part>;
type NameOfFunction = {
name: never;
age: never;
add: "add";
}
ここまではできたが、この状態からバリューだけ返すようにする方法が思いつかなかった。
答え
GetFunctionNameに[keyof T]
をつける。
type GetFunctionName<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T]
問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型が返る
もうちょっと簡単な例
- 終了しないので絶対にreturnされない関数
function forever(): never {
while (true) {
// 処理
}
}
- 常にthrowされる関数
function throwError(): never {
throw new Error();
}
問9
これは
(...args: any[]) => any
どういう意味ですか?
問10
これは
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
なにをする型か説明してください(とくにinfer)
調べた
-
infer
は推測する
という意味 -
(...args: any[])
で捕捉した部分をinfer R
でR
に置き換える
Conditional type
T extends U ? X : Y
の条件(Uのとこ)にinfer S
と書くと、Sに補足された型を X の部分で再利用可能になります。
最終的にこの ReturnType<T> という型は「Tが関数であればその戻り値型」を表すことになります。
補足:ReturnType型の定義
- ReturnTypeは関数の戻り値の型を返してくれる便利なユーティリティタイプ
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
-
<T extends (...args: any) => any>
:関数の型-
(...args: any) => any
:引数はなんでもよく、何かしらの型を返す = 関数を意味する
-
-
T extends (...args: any) => infer R
- ここが条件文
-
infer
は条件文内にジェネリクスとして書ける - ここでは戻り値の型が
R
として設定されている -
<T extends (...args: any) => any>
に当てはまるなら、つまり関数なら必ず真になりR、つまり関数の戻り値の型を返す
参考
-
JavaScriptエンジニアのためのハンズオンで学ぶTypeScript徹底入門 2024年最新版
-
- Conditional Typesで使用されるinferキーワードについて
-
問11
非同期の中身を取る型を書いてください
問12
Nullableな型を作ってください
解答
作ってくださいと言われても、何を作ればいいのかよくわからない。。
Nullable型の例としては、const name: string | null = null;
のようにユニオン型でnullを指定することでnullの代入を許容する型のことだと思う。
答え
- 上記を汎用的にした型を作ればよい
-
{[P in keyof T]: T[P] | null};
でstring | null
のようにすべての型を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型を定義してください
解答
- createObjは、任意の型のバリューを持つオブジェクトを受け取り、バリューをStringに変換して返す関数
const createObj = (obj: { [key: string]: any }): { [key: string]: string } => {
let o: { [key: string]: string } = {};
for (const key in obj) {
o[key] = String(obj[key]);
}
return o;
};
答え
- unknown型は安全なAny型
- unknown型の値を他の具体的な型の変数に代入できない
- any型, unknown型には代入できる
-
T extends unknown
とすると、Tはどんな型でも受け取れる
- unknown型の値を他の具体的な型の変数に代入できない
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;
別解
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;
}
参考
問15
こちらの
arr(["a", 1]);
どんな要素の配列が渡されてもいいような型を作ってください。
解答
- 問12みたいに汎用的な型を作る問題
let arr = <T extends any[]>(...args: T) {
return args
}
arr(["a", 1]);
問16
wideningとはなんですか説明してください。
調べた
-
Widening (型の拡大)は変数の型を拡大して受け入れる値の範囲を広くすること
-
let は再代入可能なので const よりも広い (wide) 型を受け入れるように型推論が働く
// 例
let letSample = "sample" // string型
const constSample = "sample" // "sample"という文字列リテラル型
参考
答え
// 型推論によってリテラル型を変数に代入した際にプリミティブ型に拡張されること
// 例えば、
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型になるのはなぜか
問19
こちら
let a = 1
const num = a && "hoge";
型推論は何ですか
解答
-
let a = 1
でa
はnumber
型 -
const num = a && "hoge"
でnumber | "hoge"
型?
答え
0 | "hoge"
-
a && b
is equivalent toa ? a : b
- aはnumberで、それがfalseになる場合は0。なので
0 | "hoge"
- 仮にaの初期値が""(空文字)の場合、stringなので、falseになる場合は、
"" | "hoge"
になることを確認してください
検証
確かにTypeScript Playgroundでもnumにカーソルを当てると以下のように表示される
const num: 0 | "hoge"
が、理屈が理解できていない。。なぜaが0になる?
調べた
-
a && b
is equivalent toa ? a : b
- 本当にそう?
-
a
がfalse,b
がtrueのとき-
a && b
はfalse,a ? a : b
はb
つまりtrueになり、一致しないのでは
-
-
a && b
-
a
がtrueならa && b
の結果はb
に依存するため、b
を返す -
a
がfalseならa && b
もfalseになるため、a
(=false)を返す - ということは
a && b
はa ? b : a
では?- これが正しいとしてもなぜ
a
の型推論が0になるのかはわからない。。
- これが正しいとしてもなぜ
-
参考
追記
-
a && b is equivalent to a ? a : b
ではなくa || b is equivalent to a ? a : b
なのでは?と教えていただいて納得 - そうなると今回の問題には
a ? a : b
は関係ないな、、
問20
type narrowing(型の絞り込み)とはなんですか
問21
- 以下の使い分けを教えてください
- 「return文はあるけどreturnせずに関数が終了する場合がある」 -> 例: string | undefined
- 「return文がない」 -> void
解答
戻り値があるかどうかで使い分ける。
- 「return文はあるけどreturnせずに関数が終了する場合がある」
- つまり、戻り値がある場合がある
function exampleFunction(): string | undefined {
if (Math.random() > 0.5) {
return "success";
}
// ここでreturnせずに関数が終了する可能性がある
}
- 「return文がない」
- 必ず戻り値はない
function exampleFunction(): void {
console.log("戻り値なし!");
// return文がない
}
問22
contextual typeがある状態というのはどういう状態のことですか
問23
こちらのコード
type MyObj = {
name?: string;
}
function foo(obj: MyObj): string {
return obj.name!.slice(0, 5);
}
の !の意味、危険性について説明をしてください。
調べた
-
!
は『 非Nullアサーション (non-null assertion operator)』
値がnullやundefinedでないことを宣言し、コンパイラーに値を非Nullとして解釈させます。
-
MyObj
のname
はオプションプロパティ-
foo
の引数として与えられたobj
がname
プロパティを持たない可能性がある - にもかかわらず、
obj.name!
としてobj.name
がnullでもundefinedでもないとしてしまっている
-
問24
obj is Array<string>
の説明をしてください
function isStringArray(obj: unknown): obj is Array<string> {
return Array.isArray(obj) && obj.every(value => typeof value === "string");
}
function foo(obj: unknown) {
if (isStringArray(obj)) {
obj.push("abcde");
}
}
解答
isStringArray
関数の戻り値であるオブジェクトをstring型のArrayに限定している
答え
このように返り値をobj is Array<string>
のように宣言している関数は
真偽値が返らなくてはならず、
isStringArray
関数の返値がtrueならobjはArray<string>型
が返ることを指定しています。
振り返り
-
Array<string>
を返すわけではない -
関数名からも真偽値を返す関数であることは明らかだった。。
-
Array.prototype.every()は何?
every() は Array インスタンスのメソッドは、列内のすべての要素が指定された関数で実装されたテストに合格するかどうかをテストします。これは論理値を返します。
-
Array.isArray(obj) && obj.every(value => typeof value === "string");
-
obj
が配列かつ、obj
の要素が全てstring型なら
-
検証
let objS = ["apple", "orange"];
console.log(objS); // (2) ['apple', 'orange']
foo(objS);
console.log(objS); // (3) ['apple', 'orange', 'abcde']
let objS2 = [1, 2];
console.log(objS2); // (2) [1, 2]
foo(objS2);
console.log(objS2); // (2) [1, 2]
参考
obj is Array<string>
部分はType predicate
という名前があった
ユーザー定義の型ガード関数を作るためにはType predicateを使用します。Type predicateの宣言は戻り値がboolean型の関数に対して適用でき、戻り値の型の部分を次のように書き替えます。
function isDuck(animal: Animal): animal is Duck {
return animal instanceof Duck;
}
関数isDuck()がtrueを返す時のifのブロックの中ではanimalはDuck型として解釈される
問25
'num' is declared but never used.
をdisableしてください
(num: number) => { // 'num' is declared but never used.
return "return";
}
問28
こちらのエラーをnumberとstringに対応できるように修正してください。
function eachItem(val: number, i: number) {
return val.toExponential(3);
}
const arr = [4, "fafa", 6];
arr.map(eachItem);
解答
「numberとstringに対応できるように修正し、エラーが出ないようにしてください」という題意と解釈した。
function eachItem(val: number | string, i: number ) {
if (typeof val === "number") {
return val.toExponential(3);
}
return "";
}
const arr = [4, "fafa", 6];
arr.map(eachItem);
疑問点
-
i: number
は使ってない?何に必要? -
val
がnumber型でないときのreturn
を勝手に指定していいのか- ""を返すよりは
return val
の方が良い?
- ""を返すよりは
参考
toExponential() メソッドは Number オブジェクトを指数表記で表した文字列を返します。
- 例
const test = 123;
console.log(test.toExponential(3)); // 1.230e+2
答え
答えが載ってない、、
問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);
別解
typeを使っても良さそう
type FunType = (e: number) => number;
function fa(callback: FunType, e: number){
return callback(e);
}
const fun: FunType = (e) => 1 * e;
const v = fa(fun, 1);
参考
答え
interface Fun {(e: number): number;}
function fa(callback:Fun, e: number){
return callback(e);
}
const fun:Fun = (e) => 1 * e;
const v = fa(fun, 1);
問30
こちら
type YukarinoChi = "tokyo";
type OnlySpecificProperty<T> = Pick<T, {
[K in keyof T]: T[K] extends YukarinoChi ? K : never
}[keyof T]
>;
の型を説明してください
解答
- 問7のメソッド名だけを取り出すTypeと同じ部分がある
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
- 以下の部分は
YukarinoChi
型("tokyo"リテラル)ならK
(プロパティ名)を返している
{
[K in keyof T]: T[K] extends YukarinoChi ? K : never
}[keyof T]
-
Pick<T, {...}>
で、オブジェクトTからYukarinoChi
型("tokyo"リテラル)のプロパティだけを抽出している -
[key of T]
でTのプロパティ名を返す -
上記をまとめると、
OnlySpecificProperty
はYukarinoChi
型("tokyo"リテラル)のキーだけを返す型
答え
// "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">
検討
-
fun(obj)
としたときにYukariNoChi
型以外のプロパティも返ってきてる、、
type YukariNoChi = "tokyo"
const obj = {
name: "kenji",
age: 99,
born: "tokyo" as YukariNoChi,
live: "tokyo" as YukariNoChi,
}
type Obj = {
name: string,
age: number,
born: YukariNoChi,
live: YukariNoChi
}
const obj2 = {
born: "tokyo" as YukariNoChi,
live: "tokyo" as YukariNoChi,
}
type OnlySpecificProperty<T> = Pick<T, {[K in keyof T]: T[K] extends YukariNoChi ? K : never}[keyof T]>;
function fun(onlyYukari: OnlySpecificProperty<Obj>){
return onlyYukari
}
console.log(fun(obj)); // {name: 'kenji', age: 99, born: 'tokyo', live: 'tokyo'}
console.log(fun(obj2));// {born: 'tokyo', live: 'tokyo'}
問31
stringとnullableな配列の型を作ってください
問32
こちらの
type F = {
foo: string;
bar: number;
}
const E:F = { foo: "fafa", bar: "fafa"} //Error
定義元のFを直接編集せずに代入できるように型付けしてください
解答
一応エラーは出なくなったけどコレジャナイ感がしますね
type F = {
foo: string;
bar: number;
}
const E:F = { foo: "fafa", bar: "fafa" as unknown as number}
答え
type F = {
foo: string;
bar: number;
}
const E:Record<keyof F, string> = { foo: "fafa", bar: "fafa"}
答えの検討
Record<Keys, Type>はプロパティのキーがKeysであり、プロパティの値がTypeであるオブジェクトの型を作るユーティリティ型です。
-
Record<keyof F, string>
は、プロパティーのキーがfoo
またはbar
で、プロパティの値がstringである型を指す
問33
type Exclude<T, U>
の説明をしてください
解答
- ユニオン型
T
からU
で指定した型を取り除いて返す
具体例
type Sample = Exclude<string | number | boolean, boolean>
// type Sample = string | number
参考
答え
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
答えの検討
- 答え、正しくなさそう
// use case
type Q = Exclude<string | number, boolean | string | number>
// type Q = never
type Q2 = Exclude<string | number | undefined, any>
// type Q2 = never
type Sample = Exclude<string | number | boolean, any>
// type Sample = never
問34
こちら
export default function person({ detail } : Person) {
return <div>{detail.name}</div>
};
interface Person {
id: number
detail: Detail
}
person({detail: {name: "fafa"}, id: 1 });
はdetailが初期化された時 undefinedが渡って来てもいいように対応してください
解答
-
detail = {}
- detailがundefinedの時デフォルト値を設定する
-
detail?: Detail
-
detail
プロパティを持たなくても良い
-
export default function person({ detail = {} } : Person) {
return <div>{detail.name}</div>
};
interface Person {
id: number
detail?: Detail
}
person({detail: {name: "fafa"}, id: 1 });
person({detail: undefined, id: 2 });
person({id: 3 });
答え
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を渡さないでもいいようにしてください。
問36
- 関数 a は
ServiceUser
orUser
orAppUser
を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)
解答
- 最初
if (typeof o === "ServiceUser")
のように書こうとしていたが、typeof
はプリミティブ型にしか使えなかった - 「期待型」って戻り値のことかな?
function a(o: ServiceUser | User | AppUser): string{
if ("serviceID" in o) return o.serviceID
if ("id" in o) return o.id
return o.appID // AppUser
}
console.log(a(user)) // 1
console.log(a(appUser)) // appId
console.log(a(serviceUser)) // serviceID
答え
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
- 問36に出てきた以下の関数を独⾃定義 TypeGuardで型定義してください
- それぞれ isService、isAppUser、任意でisUser関数を作り、ifのコンディション内で実行
- 返す値がそれぞれのプロパティを持つようにして、型付けされていることを確認
function a(o: ServiceUser | User | AppUser){
if("serviceID" in o) return o.serviceID;
if("appID" in o) return o.appID;
return o.id;
}
解答
function isService(o: any): o is ServiceUser {
return o.serviceID === "serviceID"
}
function isAppUser(o: any): o is AppUser {
return o.appID === "appId"
}
function isUser(o: any): o is User {
return o.id === "1"
}
function a(o: ServiceUser | User | AppUser){
if (isService(o)) return o.serviceID;
if (isAppUser(o)) return o.appID;
return o.id;
}
答え
- 同上
問38
こちら
const o = { name: "hoge" }
function a(o){
return o
}
a(o)
a();
の defaultValueとany型に対応してください
解答
const o = { name: "hoge" }
function a(obj: any = o){
return obj
}
console.log(a({name: "foo"})); // {name: 'foo'}
console.log(a()); // {name: 'hoge'}
参考
答え
- 何をやっているのか理解できない
-
o = defaultProps
でデフォルト値を入れている -
const o = {name: "hoge"}
必要?? - どこでany型に対応している?
- 何をすれば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
のUnion型であり、run
プロパティを持たないBird型である可能性があるため、b.run
のように呼び出すことはできない - 呼び出したいならチェックが必要
const a = (b:NotHumman) => {
if ("run" in b) 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;
参考
Don't write separate overloads that differ only on callback arity:
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;
Do write a single overload using the maximum arity:
/* OK */
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;
Why: It's always legal for a callback to disregard a parameter, so there's no need for the shorter overload. Providing a shorter callback first allows incorrectly-typed functions to be passed in because they match the first overload.
問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?
修正してください。
解答
- 上から順に試して最初にマッチした関数が適用されてしまうので、
any
は一番最後にする - 上記の修正だけだと
Variable 'myElem' is used before being assigned.
エラーが出るため、myElem!
として明確な割当てアサーションを行う-
strictNullChecks
をfalseにしておけば、修正しなくてもこのエラーは出ない
-
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: any, wat?
参考
TypeScriptはコンパイラオプションstrictNullChecksがtrueのとき、初期化されていない変数を参照した際にエラーを出します
答え
TypeScript は関数呼び出し時に最初にマッチしたオーバーライドを選ぶので、any だと最初に必ずマッチしてしまう
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
問42
こちら
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
修正してください
問43
こちら
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
// 1
y = x;
// 2
x = y;
1と2はそれぞれエラーになりますかなりませんか
解答
- 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); //渡す際に満たさないといけない,
答えの検討
- 数は関係なかった。。
- 「代入元が代入先の引数を持っているかどうか」が大事なので、bの引数の型を変えた以下はエラーになる
let x = (a: number) => 0;
let y = (b: string, s: string) => 0;
// 1
y = x;
Type '(a: number) => number' is not assignable to type '(b: string, s: string) => number'.
Types of parameters 'a' and 'b' are incompatible.
Type 'string' is not assignable to type 'number'.
翻訳
型 '(a: 数値) => 数値' は型 '(b: 文字列, s: 文字列) => 数値' に代入できません。
パラメーター 'a' と 'b' のタイプには互換性がありません。
型「string」を型「number」に割り当てることはできません。
問44
こちら
let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });
// 1
x = y;
// 2
y = x;
1, 2はそれぞれエラーになるかならないか
解答
- xは
location
プロパティを持たない
答え
- 代入元の返り型は代入先のプロパティを含んでいないといけない
let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });
x = y; // OK
y = x; // エラー。xの戻り値には location プロパティがない
Type '() => { name: string; }' is not assignable to type '() => { name: string; location: string; }'.
Property 'location' is missing in type '{ name: string; }' but required in type '{ name: string; location: string; }'.
問46
こちら
let identity = function<T>(x: T): T {
// ...
};
let reverse = function<U>(y: U): U {
// ...
};
identity = reverse;
は代入できるか。それぞれTとUの型は何か
問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になります。なぜですか。また正しく修正してください
問49
TypeScriptでconsole.logを呼び出してもコンパイルエラーにならないのはなぜですか?
答え
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 };
が代入できるように修正してください
解答
- interface定義にageプロパティがないから?
- 50問めにしてそんな簡単な問題出す??
interface Foo {
name: string;
age: number;
}
答え
// オブジェクトリテラル型は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、 値に代入しようとしている型を指定してください
問52
こちらは
const tupleStrNum = ["X", 2];
型推論で(string|number)[]
になります。
[string, number]
とするにはどうしたらいいですか