宮水の日記

宮水の日記

主に書評や資格取得について記事を書いています。

Typescript exercises やってみた 2問目

みなさんこんにちは。宮水です。
今日は、TypeScript エクササイズの2に取り組んでみました。
英語も苦手なので、翻訳も自分でしてみました。

こちらのリポジトリをforkして、cloneして取り組みます。
rootディレクトリで、yarn installしてから問題文にあるRun this exerciseのコマンドを叩くと、答え合わせができます。
github.com

前回の問題

miyamizu.hatenadiary.jp


本日の問題

import chalk from 'chalk';

/*

Intro:

    Since we already have some of the additional
    information about our users, it's a good idea
    to output it in a nice way.

 私たちはすでにいくつか情報を追加されたユーザーを持っているので、適切な方法で出力しましょう。

Exercise:

    Fix type errors in logPerson function.

    logPerson function should accept both User and Admin
    and should output relevant information according to
    the input: occupation for User and role for Admin.

 logPerson関数の型エラーを修正してください。
 logPerson関数は、UserとAdminの両方を受け入れ、Userの職業とAdminの役割を出力する必要があります。

Run:

    npm run 2

    - OR -

    yarn -s 2

*/

interface User {
    name: string;
    age: number;
    occupation: string;
}

interface Admin {
    name: string;
    age: number;
    role: string;
}

type Person = User | Admin;

const persons: Person[] = [
    {
        name: 'Max Mustermann',
        age: 25,
        occupation: 'Chimney sweep'
    },
    {
        name: 'Jane Doe',
        age: 32,
        role: 'Administrator'
    },
    {
        name: 'Kate Müller',
        age: 23,
        occupation: 'Astronaut'
    },
    {
        name: 'Bruce Willis',
        age: 64,
        role: 'World saver'
    }
];

function logPerson(person: Person) {
    let additionalInformation: string;
    if (person.role) {
        additionalInformation = person.role;
    } else {
        additionalInformation = person.occupation;
    }
    console.log(` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`);
}

persons.forEach(logPerson);

// In case if you are stuck:
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-the-in-operator

答え

interface User {
    type: 'user';
    name: string;
    age: number;
    occupation: string;
}

interface Admin {
    type: 'admin';
    name: string;
    age: number;
    role: string;
}

type Person = User | Admin;

const persons: Person[] = [
    {
        type: 'user',
        name: 'Max Mustermann',
        age: 25,
        occupation: 'Chimney sweep'
    },
    {
        type: 'admin',
        name: 'Jane Doe',
        age: 32,
        role: 'Administrator'
    },
    {
        type: 'user',
        name: 'Kate Müller',
        age: 23,
        occupation: 'Astronaut'
    },
    {
        type: 'admin',
        name: 'Bruce Willis',
        age: 64,
        role: 'World saver'
    }
];

function logPerson(person: Person) {
    let additionalInformation: string;
    if (person.type === 'admin') {
        additionalInformation = person.role;
    } else {
        additionalInformation = person.occupation;
    }
    console.log(` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`);
}

persons.forEach(logPerson);

んー、ちょっと微妙なコードだと思うけど、一応動きました。
f:id:kattyan53:20200724163641p:plain

感想

今回は、Type Guardの問題だと思います。

今回の問題では、User型にroleプロパティが、Admin型にoccupationプロパティがないという型エラーが出ていました。
(ちなみに、前回の問題では、roleもoccupationも参照していなかったので、エラーにはなりませんでした。)

そこで、型定義にuser か adminかわかる情報を追加してあげて、データにもtypeプロパティを追加し、
if文でtypeプロパティごとに出力する値を変えるような処理を入れました。
この問題はいろんな書き方ができると思います。

初めは、typeofやinstanceofをそれを使おうと思ったのですが、使えませんでした。

typeofは、stringやnumberやnullといった基本型に対して使えるものです。
instanceofは、interfaceで定義された型に対しては使うことができません。

interfaceのタイプガードをするとしたら、以下のように自分でタイプガードを定義しないといけないようです。(ユーザー定義タイプガードというそうです。)

interface Foo {
  foo: number
}

interface Bar {
  bar: number
}

// ユーザ定義タイプガード
function implementsFoo(arg: any): arg is Foo {
  return arg !== null &&
    typeof arg === "object" &&
    typeof arg.foo === "number"
}

function getNumber(arg: Foo | Bar): number {
  if (implementsFoo(arg)) { // implementsFooをタイプガードに使用
    return arg.foo
  } else {
    return arg.bar
  }
}

※ 参考のQiitaから引用しました

以上です!

参考:TypeScript: interfaceにはinstanceofが使えないので、ユーザ定義タイプガードで対応する - Qiita