1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NonNullable<T>の実装が簡略化されていた(TS 4.8)

Posted at

どしたん

TypeScript には、型 T から nullundefined を除外するユーティリティ型 NonNullable<T> が用意されています。過去の動画を参照していたところ、以下のような定義が使われているのを確認しました。

type NonNullable<T> = T extends null | undefined ? never : T;

一方、現在の TypeScript のバージョンでは、次のような実装に変更されています。

type NonNullable<T> = T & {};

この変化について気になったため、変更の経緯を調べてみました。

実装変更の背景

TypeScript 4.8 のリリースノートに、次のような記述がありました。

Another change is that {} intersected with any other object type simplifies right down to that object type. That meant that we were able to rewrite NonNullable to just use an intersection with {}, because{} & null and {} & undefined just get tossed away.
TypeScript 4.8 Release Notes

つまり、交差型の簡略化により、T & {} という形で nullundefined を自然に排除できるようになった、ということのようです。

具体的には、以下のような挙動が保証されるようになったとのことでした。

null & {}       → never
undefined & {}  → never
string & {}     → string

この結果、従来のように条件型を使わなくても NonNullable<T> の目的を達成できるようになった、というわけです。

そもそも変更前の NonNullable<T> とは

NonNullable<T> は、型 T から nullundefined を除外するためのユーティリティ型です。従来の定義は次のとおりでした。

type NonNullable<T> = T extends null | undefined ? never : T;

この定義は、分配型条件型(Distributive Conditional Types) という機能を利用しています。

条件型の動作確認

以下のような型を定義するとします。

type A = string extends null | undefined ? null : boolean;

stringnull | undefined に代入できないため、この評価結果は boolean になります。

一方、次のような型の場合:

type B = (string | null) extends null | undefined ? null : boolean;

このときは、string | null 全体が null | undefined に代入可能かどうかを判定するため、分配されず boolean になるようです。

ただし、以前までのNonNullableのようにジェネリクス型を用いた場合には、分配されるようになっています。

type NonNullable<T> = T extends null | undefined ? never : T;
type C = NonNullable<string | null | undefined>; // 結果: string

この処理の流れは以下の通りです。

  1. string extends null | undefined → false → string
  2. null extends null | undefined → true → never
  3. undefined extends null | undefined → true → never

結果として、string | never | neverstring となり、nullundefined を除外できるわけです。

実装が変更された理由

前述のとおり、TypeScript の型システムの仕様変更によって {} との交差による型の簡略化が行われました。これにより、以下のような効果が得られるようになったそうです。

null & {}        never
undefined & {}   never
string & {}      string

これによって T & {} という記述だけで nullundefined を排除できるようになり、よりシンプルな形で NonNullable<T> を定義できるようになったとのことです。

じゃあ無害なんですか

少なくとも、普通に使う分には問題はなさそう。

ありそうなところは、voidに交差型をすると

// 元の実装
type NonNullable_before<T> = T extends null | undefined ? never : T;

// 新しい実装
type NonNullable_new<T> = T & {};

// void型との相互作用
type Test1_before = NonNullable_before<void>; // void
type Test1_new = NonNullable_new<void>; // void & {}

となり、void & {}となってしまうことだったり、奥深いところで違ってkくる可能性がありそうです。

まぁ少なくとも私が関わってきた極細プロジェクトに影響出ることはないからヨシ!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?