宮水の日記

宮水の日記

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

Typescript exercises やってみた 3問目

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

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

前回の問題

miyamizu.hatenadiary.jp

本日の問題

import chalk from 'chalk';

/*

Intro:

    As we introduced "type" to both User and Admin
    it's now easier to distinguish between them.
    Once object type checking logic was extracted
    into separate functions isUser and isAdmin -
    logPerson function got new type errors.

 UserとAdminの両方に「type」を導入したので、簡単にそれらを区別できるようになりました。
 オブジェクトのtypeをチェックするロジックがisUserとisAdminに分けられると、logPerson関数で新しいタイプのエラーが発生しました。

Exercise:

    Figure out how to help TypeScript understand types in
    this situation and apply necessary fixes.

 TypeScriptが型を理解するために、必要な修正を適用してください。

Run:

    npm run 3

    - OR -

    yarn -s 3

*/

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 isAdmin(person: Person) {
    return person.type === 'admin';
}

function isUser(person: Person) {
    return person.type === 'user';
}

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

console.log(chalk.yellow('Admins:'));
persons.filter(isAdmin).forEach(logPerson);

console.log();

console.log(chalk.yellow('Users:'));
persons.filter(isUser).forEach(logPerson);

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

答え

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

const isAdmin = (person: Person): person is Admin => person.type === 'admin';
const isUser = (person: Person): person is User => person.type === 'user';

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

console.log(chalk.yellow('Admins:'));
persons.filter(isAdmin).forEach(logPerson);

console.log();

console.log(chalk.yellow('Users:'));
persons.filter(isUser).forEach(logPerson)

解説&感想

今回は、isの問題でした。
自分の経験では解けなくて、問題文一番下にある公式ドキュメントのヒントを見ました。
www.typescriptlang.org

TypeScriptが引数で受け取ったの型を推測できない場合、私たちがこうやって書いてTypeScriptを助けてあげる必要もあるんですね。

// このpersonはAdminだよ!
const isAdmin = (person: Person): person is Admin => person.type === 'admin';

公式ドキュメントの同じ項目にinというものもあって、これは逆に"swim"はFishかBirdのプロパティだよ!と教えてあげることができます。
isとはちょっと違うけど、型を限定できるというものです。

function move(pet: Fish | Bird) {
  if ("swim" in pet) {
    return pet.swim();
  }
  return pet.fly();
}

うーん、だんだん難しくなってきました。以上です!