Closed159

『TypeScript 練習問題集』を解く

wsigma21wsigma21

『TypeScript 練習問題集』を解いていき、調べたことや疑問点をメモしていきます。

https://gist.github.com/kenmori/8cea4b82dd12ad31f565721c9c456662

wsigma21wsigma21

問5

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

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

答え

  • 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でリテラルタイプで指定した物と同等の扱いになります。

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

wsigma21wsigma21

問6

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

wsigma21wsigma21

解答

TはUがtrueならX型、falseならY型をextends(継承)する

"Uがtrueなら"ってどういう状況??

wsigma21wsigma21

答え

// TがUに代入可能ならXを、そうではない場合Yを返す
// T型がU型と互換性あるならXを、そうでない場合Yを返す
// T型がU型のサブタイプならXを、そうでない場合Yを返す
// T型がU型の部分型ならXを、そうでない場合Yを返す
// ()=> void extends Functionは互換性がある
// "hello!" extends String

wsigma21wsigma21

参考

  • T extends U ? X : YはConditional Types(条件付き型、型の条件分岐)

https://typescriptbook.jp/reference/type-reuse/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に含まれるプロパティしか指定できないということ。

https://zenn.dev/kotaesaki/articles/f927aaff02d621#解説-1

wsigma21wsigma21

問7

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

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

答え

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"が表示される。

私の解答と結果は同じだが、答えは汎用的。

ただ、何をやっているのか理解できない、、

wsigma21wsigma21

答えの検討

type FunctionPropertyNames<T> = {
type result = FunctionPropertyNames<Part>

FunctionPropertyNamesはジェネリクス化されていて<T>で型を受け取る。今回はPart型を受け取り、Part型に含まれるメソッドの型だけ返している。返り値がresult型に入る。

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

https://typescriptbook.jp/reference/values-types-variables/object/index-signature

https://zenn.dev/sjbworks/articles/b8bc7d7bacfd07

keyofはオブジェクトのキーをユニオン型に変更する

https://typescriptbook.jp/reference/type-reuse/mapped-types

wsigma21wsigma21

TのプロパティK(具体的にはPart[name], Part[age], Part[add])がFunction型ならKを、そうでないならneverを返す

T[K] extends Function ? K : never
wsigma21wsigma21
}[keyof T]

keyof TでTの全プロパティname, age, addの型がユニオン型で得られる。
つまり、この場合はnever | never | addとなる。
おそらくneverは無視されて?addのみが抽出される。

もしこれがなかったら、resultにはTの全プロパティが入る。

参考

keyof型演算子と組み合わせると、オブジェクトの全プロパティの型がユニオン型で得られます。

https://typescriptbook.jp/reference/type-reuse/indexed-access-types

wsigma21wsigma21

まだ理解しきれてないのはインデックスの理解不足なのでは?

シンプルにしてみる。

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]がバリューなのでは??

wsigma21wsigma21

ということは

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で返している。

wsigma21wsigma21

再挑戦

解答

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

問8

neverとはどんな型ですか?

wsigma21wsigma21

答え

絶対に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型が返る
wsigma21wsigma21

もうちょっと簡単な例

  • 終了しないので絶対にreturnされない関数
function forever(): never {
  while (true) {
   // 処理
  }  
}
  • 常にthrowされる関数
function throwError(): never {
  throw new Error();
}
wsigma21wsigma21

問9

これは

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

どういう意味ですか?

wsigma21wsigma21

解答

引数が0個以上あって、それらの型はなんでもよく、何を返り値としても良い関数

wsigma21wsigma21

答えの検討

  • (...args: any[]): は可変長引数(残余引数ともいう)
  • 可変長引数の型は配列にするのでany[]
wsigma21wsigma21

問10

これは

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

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

wsigma21wsigma21

調べた

  • infer推測するという意味
  • (...args: any[])で捕捉した部分をinfer RRに置き換える

Conditional type T extends U ? X : Y の条件(Uのとこ)に infer S と書くと、Sに補足された型を X の部分で再利用可能になります。

最終的にこの ReturnType<T> という型は「Tが関数であればその戻り値型」を表すことになります。

https://qiita.com/Quramy/items/b45711789605ef9f96de#type-inference-in-conditional-types

wsigma21wsigma21

補足: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、つまり関数の戻り値の型を返す

参考

wsigma21wsigma21

問11

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

wsigma21wsigma21

答えの検討

  • あんまり意味がわかっていない
  • 以下のように考えたが、、
    • TはPromise型?
      • True: Promiseを返す
      • False: TはObservable型 か?
        • True:Observable型を返す
        • False: Tを返す
wsigma21wsigma21

問12

Nullableな型を作ってください

wsigma21wsigma21

解答

作ってくださいと言われても、何を作ればいいのかよくわからない。。

Nullable型の例としては、const name: string | null = null;のようにユニオン型でnullを指定することでnullの代入を許容する型のことだと思う。

wsigma21wsigma21

答え

  • 上記を汎用的にした型を作ればよい
  • {[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 }
wsigma21wsigma21

問13

こちら

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

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

wsigma21wsigma21

解答

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

答え

  • unknown型は安全なAny型
    • unknown型の値を他の具体的な型の変数に代入できない
      • any型, unknown型には代入できる
    • T extends unknownとすると、Tはどんな型でも受け取れる
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;
}

参考

https://typescriptbook.jp/reference/statements/unknown

wsigma21wsigma21

問15

こちらの

arr(["a", 1]);

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

wsigma21wsigma21

解答

  • 問12みたいに汎用的な型を作る問題
let arr = <T extends any[]>(...args: T) {
  return args
}

arr(["a", 1]);
wsigma21wsigma21

問16

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

wsigma21wsigma21

調べた

  • Widening (型の拡大)は変数の型を拡大して受け入れる値の範囲を広くすること

  • let は再代入可能なので const よりも広い (wide) 型を受け入れるように型推論が働く

// 例
let letSample = "sample" // string型
const constSample = "sample" // "sample"という文字列リテラル型 

参考

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

wsigma21wsigma21

答え

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

let a = "a" //string

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

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

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

問17

下記

let a;

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

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

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

wsigma21wsigma21

解答

  • let aで初期化するときに型を指定していないし何も代入していないからundefined型になる
  • その後a = 123と代入されるのでnumber型になる
wsigma21wsigma21

答え

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

wsigma21wsigma21

問19

こちら

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

型推論は何ですか

wsigma21wsigma21

解答

  • let a = 1anumber
  • const num = a && "hoge"number | "hoge"型?
wsigma21wsigma21

答え

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

検証

確かにTypeScript Playgroundでもnumにカーソルを当てると以下のように表示される

const num: 0 | "hoge"

が、理屈が理解できていない。。なぜaが0になる?

調べた

  • a && b is equivalent to a ? a : b

    • 本当にそう?
    • aがfalse, bがtrueのとき
      • a && bはfalse, a ? a : bbつまりtrueになり、一致しないのでは
  • a && b

    • aがtrueならa && bの結果はbに依存するため、bを返す
    • aがfalseならa && bもfalseになるため、a(=false)を返す
    • ということはa && ba ? b : aでは?
      • これが正しいとしてもなぜaの型推論が0になるのかはわからない。。

参考

https://zenn.dev/fujii0112/articles/78fe69c4c30731#◆「%26%26」について

wsigma21wsigma21

追記

  • a && b is equivalent to a ? a : bではなくa || b is equivalent to a ? a : bなのでは?と教えていただいて納得
  • そうなると今回の問題にはa ? a : bは関係ないな、、
wsigma21wsigma21

問20

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

wsigma21wsigma21

答え

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

wsigma21wsigma21

問21

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

解答

戻り値があるかどうかで使い分ける。

  • 「return文はあるけどreturnせずに関数が終了する場合がある」
    • つまり、戻り値がある場合がある
function exampleFunction(): string | undefined {
    if (Math.random() > 0.5) {
        return "success";
    }
    // ここでreturnせずに関数が終了する可能性がある
}
  • 「return文がない」
    • 必ず戻り値はない
function exampleFunction(): void {
    console.log("戻り値なし!");
    // return文がない
}
wsigma21wsigma21

問22

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

wsigma21wsigma21

答え

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

wsigma21wsigma21

補足

例えばconst a = "sample";みたいに、aがstring型であることが明らかなときは、わざわざa: stringのように書かなくても良い

wsigma21wsigma21

問23

こちらのコード

type MyObj = {
  name?: string;
}

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

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

wsigma21wsigma21

調べた

  • !は『 非Nullアサーション (non-null assertion operator)』

値がnullやundefinedでないことを宣言し、コンパイラーに値を非Nullとして解釈させます。

https://typescriptbook.jp/symbols-and-keywords#-非nullアサーション-non-null-assertion-operator-ts

  • MyObj nameはオプションプロパティ
    • fooの引数として与えられたobjnameプロパティを持たない可能性がある
    • にもかかわらず、obj.name!としてobj.nameがnullでもundefinedでもないとしてしまっている
wsigma21wsigma21

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

解答

isStringArray関数の戻り値であるオブジェクトをstring型のArrayに限定している

wsigma21wsigma21

答え

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

wsigma21wsigma21

振り返り

  • Array<string>を返すわけではない

  • 関数名からも真偽値を返す関数であることは明らかだった。。

  • Array.prototype.every()は何?

every() は Array インスタンスのメソッドは、列内のすべての要素が指定された関数で実装されたテストに合格するかどうかをテストします。これは論理値を返します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/every

  • Array.isArray(obj) && obj.every(value => typeof value === "string");
    • objが配列かつ、objの要素が全てstring型なら
wsigma21wsigma21

検証

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

参考

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型として解釈される

https://typescriptbook.jp/reference/functions/type-guard-functions#ユーザー定義の型ガード関数

wsigma21wsigma21

問25

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

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

解答

  • 使わないけど受け取りは必要なので_で受け取っておく
  • TypeScriptというよりJavaScriptの範囲
(_: number) => { 
  return "return";
}
wsigma21wsigma21

答え

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

疑問

  • _numだとプライベート変数っぽいんだけどこれでも良いの?
wsigma21wsigma21

問28

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

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

解答

「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 オブジェクトを指数表記で表した文字列を返します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential

const test = 123;
console.log(test.toExponential(3)); // 1.230e+2
wsigma21wsigma21

問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に型付けしてください)

wsigma21wsigma21

解答

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

答え

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

問30

こちら

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

の型を説明してください

wsigma21wsigma21

解答

  • 問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のプロパティ名を返す

  • 上記をまとめると、OnlySpecificPropertyYukarinoChi型("tokyo"リテラル)のキーだけを返す型

wsigma21wsigma21

答え

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

検討

  • 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'}
wsigma21wsigma21

問31

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

wsigma21wsigma21

答え

let arr: (string | null)[] = []
  • ()で囲む必要がある
  • 私の解答array: string | null []だと、「stringまたは要素がnullの配列」と解釈されてしまう
wsigma21wsigma21

問32

こちらの

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

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

wsigma21wsigma21

解答

一応エラーは出なくなったけどコレジャナイ感がしますね

type F = {
    foo: string;
    bar: number;
}
const E:F = { foo: "fafa", bar: "fafa" as unknown as number}
wsigma21wsigma21

答え

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

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

問33

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

wsigma21wsigma21

答え

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

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

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

答えの検討

  • 答え、正しくなさそう
// 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
wsigma21wsigma21

問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が渡って来てもいいように対応してください

wsigma21wsigma21

解答

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

答え

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

#問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を渡さないでもいいようにしてください。

wsigma21wsigma21

問36

  • 関数 a はServiceUser or User or AppUserをa に渡してそれを返す関数
  • 期待型は ServiceUser | User | AppUser になっています
  • これを それぞれ以下を返す関数に直す
    • ServiceUserserviceID
    • Userid
    • AppUserappId
  • 期待型を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)
wsigma21wsigma21

解答

  • 最初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
wsigma21wsigma21

答え

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
wsigma21wsigma21

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

解答

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

問38

こちら

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

a(o)
a();

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

wsigma21wsigma21

答え

  • 何をやっているのか理解できない
    • 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();
wsigma21wsigma21

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

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

wsigma21wsigma21

解答

  • NotHumman型はAnimalBirdのUnion型であり、runプロパティを持たないBird型である可能性があるため、b.runのように呼び出すことはできない
  • 呼び出したいならチェックが必要
const a = (b:NotHumman) => {
    if ("run" in b) b.run
}
wsigma21wsigma21

答え

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

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

問40

こちら

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

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

wsigma21wsigma21

答え

  • コールバックの引数が違うだけでオーバーライドしないようにしましょう。
  • コールバックがパラメータを無視することは常に正当です。渡って来なくても無視されるだけです。
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;
wsigma21wsigma21

参考

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.

https://github.com/microsoft/TypeScript-Handbook/blob/master/pages/declaration files/Do's and Don'ts.md#overloads-and-callbacks

wsigma21wsigma21

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

修正してください。

wsigma21wsigma21

解答

  • 上から順に試して最初にマッチした関数が適用されてしまうので、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のとき、初期化されていない変数を参照した際にエラーを出します

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

wsigma21wsigma21

答え

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

問42

こちら

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

修正してください

wsigma21wsigma21

解答

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

問43

こちら

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

// 1
y = x;

// 2
x = y;

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

wsigma21wsigma21

解答

  • yの方が引数が多いため、1はエラーにならないが2はエラーになる
wsigma21wsigma21

答え

  • 返る型が同じ場合、引数の数は関係ない。代入元が代入先の引数を持っているかどうか。
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); //渡す際に満たさないといけない,
wsigma21wsigma21

答えの検討

  • 数は関係なかった。。
  • 「代入元が代入先の引数を持っているかどうか」が大事なので、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」に割り当てることはできません。

wsigma21wsigma21

問44

こちら

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

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

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

wsigma21wsigma21

答え

  • 代入元の返り型は代入先のプロパティを含んでいないといけない
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; }'.

wsigma21wsigma21

問46

こちら

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

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

wsigma21wsigma21

答え

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

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

なぜですか

wsigma21wsigma21

答え

  • 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...
wsigma21wsigma21

答えの検討

  • ジェネリクスを作って型が違っても対応できるようにしている
wsigma21wsigma21

問48

こちらは

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

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

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

wsigma21wsigma21

解答

ageプロパティがないため

let obj: MyObj = {
  name: "kenji",
  age: undefined
};
wsigma21wsigma21

答え

  • 上記の通りageを追加するか、もしくはオプショナルプロパティにする
interface MyObj {
  name: string;
  age?: number | undefined;
}
wsigma21wsigma21

問49

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

wsigma21wsigma21

答え

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

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

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

問50

こちらは

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

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

wsigma21wsigma21

解答

  • interface定義にageプロパティがないから?
    • 50問めにしてそんな簡単な問題出す??
interface Foo {
  name: string;
  age: number;
}
wsigma21wsigma21

答え

// オブジェクトリテラル型は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 };
wsigma21wsigma21

問51

こちらは

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

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

wsigma21wsigma21

答え

let foo:{ [index: string]: { message: string }} = {}
foo["a"] = { message: "some message"};
このスクラップは3ヶ月前にクローズされました