見出し画像

TypeScript 入門の記録(68)プロを目指す人のためのTypeScript入門(52)第6章 高度な型(1)

こんにちは。貴島(@jnkykn)です。先日ちらっと見かけたNixが気になっています。「そういうものがあるらしい」ということしかわかっていませんが、そのうち「Nix入門」を参考に触ってみたいな~と思っています。

Nixは気になっていますが、今日もTypeScript入門の続きです。先週は、プロを目指す人のためのTypeScript入門の第5章の力試しまで進みました。今日から、第6章 高度な型に入ります。


ユニオン型とインターセクション型


最初、ユニオン型は、CやC++のunionに近いのかなと思いました。CやC++のunionは、1つの変数を複数の異なる型として扱うことができますが、TypeScriptのunionは、どちらかの型のデータが格納できるというものなので、機能が違いました。

伝搬するユニオン型

type C = A | B ; の場合、Cは型Aまたは型Bのどちらでも良いということで、両方の型のプロパティを備えているということではないのですよね。なので、存在しないかもしれないプロパティにアクセスするとコンパイルエラーです。ということは、共通のプロパティに対してのみアクセス可能ということですね。と、わかったつもりになっていたのですが、ユニオン型の伝搬は「そういう判断をされるのか!」という驚きがありました。

// 6.1.2 伝搬するユニオン型
type Animal = {
    species: string;
    age: string;
}

type Human = {
    name: string;
    age: number;
}

type User = Animal | Human;

const tama: User = {
    species : "Felis silverstris catus",
    age: "永遠の17歳"
}

const uhyo: User = {
    name: "uhyo",
    age: 26
};

function showAge(user: User) {
    const age = user.age;
    console.log(`user.age = ${user.age}`);
}

showAge(tama);
showAge(uhyo);

実行結果を見てみると、Animalの場合はage:string、Humanの場合はage:numberであることを、そのまま受け入れされていることがわかります。

$ npx tsc
$ node ./dist/src/index_6.js
user.age = 永遠の17歳
user.age = 26

プロパティ名が共通でも型が異なる場合は、そのプロパティの型もunionで扱われるということだそうです。これは、関数についてもあてはまるそうなので、試してみます。

関数のユニオン型の確認

type MysteryFunc = 
  | ((str: string) => string)
  | ((str: string) => number);

function useFunc(func: MysteryFunc) {
    const result = func("uhyo");
    console.log(result);
}

const myFunc: MysteryFunc = ((str: string) => {
    return str;
});

const res = useFunc(myFunc);
console.log(`結果は、${res}`);

実行結果は、

$ npx tsc
npm notice
npm notice New major version of npm available! 10.8.1 -> 11.0.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.0.0
npm notice To update run: npm install -g npm@11.0.0
npm notice
$ node ./dist/src/index_6.js
uhyo
結果は、undefined

そして、npmの新しいバージョンがあるので更新すると良さそうです。

$ sudo npm install -g npm@11.0.0
npm error code EBADENGINE
npm error engine Unsupported engine
npm error engine Not compatible with your version of node/npm: npm@11.0.0
npm error notsup Not compatible with your version of node/npm: npm@11.0.0
npm error notsup Required: {"node":"^20.17.0 || >=22.9.0"}
npm error notsup Actual:   {"npm":"10.8.1","node":"v20.14.0"}
npm error A complete log of this run can be found in: /root/.npm/_logs/2025-01-19T06_43_02_966Z-debug-0.log

今の環境のnode/npmと互換性が無いって、言われました。npmを最新に。

 nvm install-latest-npm
Attempting to upgrade to the latest working version of npm...
* Installing latest `npm`; if this does not work on your node version, please report a bug!

removed 8 packages, and changed 98 packages in 8s

24 packages are looking for funding
  run `npm fund` for details
* npm upgraded to: v11.0.0

nodeも更新

e$ nvm install v20.18.1
Downloading and installing node v20.18.1...
Downloading https://nodejs.org/dist/v20.18.1/node-v20.18.1-linux-x64.tar.xz...
################################################################################################################# 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v20.18.1 (npm v10.8.2)

コンパイルし直して、再実行してみます

$ npx tsc
$ node ./dist/src/index_6.js
uhyo
結果は、undefined

今度は大丈夫です。
さて、"結果はundefined"と出力されているのは、戻り値が無い関数の戻り値を出力しているからですね。

インターセクション型

ユニオン型が、型Tまたは型Uを表現していたのに対して、インターセクション型は、T&Uと表し、意味も「型Tかつ型U」の値です。実際には、オブジェクト型を拡張して新しい型を作るために使われるそうです。なるほど。
インターセクション型は、それぞれの構成要素の型の、部分型になるというのも、見たままなので違和感ありません。

// インターセクション型
type Animal = {
    species: string;
    age: number;
}

type Human = Animal & {
    name: string;
}

const tama: Animal = {
    species : "Felis silverstris catus",
    age: 3
}

const uhyo: Human = {
    species: "Homo sapiens sapiens",
    name: "uhyo",
    age: 26
};

インターセクション型の使い所のイメージが湧きにくかったのですが、関数同士のユニオン型があり、それを呼び出す場合の引数の型としてはインターセクション型以外では成立しないようです。実際に試してみます。

// ユニオン型とインターセクション型の関係
type Human = { name: string };
type Animal = { species: string };

function getName(human: Human) {
    return human.name;
}

function getSpecies(animal: Animal) {
    return animal.species;
}

// 実行される関数を制御するために乱数を利用
const mysteryFunc = Math.random() < 0.5 ? getName : getSpecies;

const uhyo: Human & Animal = {
    name : "uhyo",
    species: "Homo sapiens sapiens"
}

const value = mysteryFunc(uhyo);
console.log(value);

実行結果

$ npx tsc
$ node ./dist/src/index_6.js
Homo sapiens sapiens
$ node ./dist/src/index_6.js
uhyo
$ node ./dist/src/index_6.js
uhyo
$ node ./dist/src/index_6.js
uhyo
$ node ./dist/src/index_6.js
uhyo
$ node ./dist/src/index_6.js
uhyo
$ node ./dist/src/index_6.js
Homo sapiens sapiens

実行結果に偏りがありますが、毎回同じ結果ではないことが確認できました。自分でこういう使い方をする可能性は低そうですが、知識として持っておくのは大事だなと思いました。

まだオプショナルプロパティが残っていますが、今日はここまでにします。

いいなと思ったら応援しよう!