generated at
branded types
「branded types」という名前はTypeScript特有の概念のようで他での実例は見かけないmrsekut
一方で、TypeScriptの文脈ではこの名前がよく使われる
他の構造的部分型で同じことをする時はOpaque Typeと呼ばれることが多い
TypeScript内でも使われている ref ref2

compile後の構造に影響を与えないことが特徴


定義の例
ts
type Branded<T, Name extends string> = T & { [key in `__${Name}`]: typeof tag }; declare const tag: unique symbol;
[key in \ __${V}\ の意味
key V を使うことで、 Branded で定義した型を組み合わすことができる
ts
type A = Branded<number, 'a'>; type B = Branded<A, 'b'>; // BrandedのBranded
ここが固定されたpropertyになっていると、組み合わすことが出来ない
例えば { _type: U } にしていると、
ts
type B = number & { _type: 'a' } & { _type: 'b' } // never
propertyが衝突しているため、そういう型は存在せず never になる
__ を付けているのは、意図せず同じような型が定義されることを避けるため
「このprojectでは __ から始まるpropertyは使わない」という前提があるmrsekut
unique symbolを使うことで、意図せず被る可能性を完全に排除している



使用例
ts
type UserId = Branded<number, 'UserId'>; const mkUserId = (n: number) => { return n as UserId; }; type GenreId = Branded<number, 'GenreId'>; const mkGenreId = (n: number) => { return n as GenreId; }; const doSomething = (id: UserId) => { console.log(id); }; const id1 = mkUserId(10); const id2 = 11; const id3 = mkGenreId(12); doSomething(id1); doSomething(id2); // error doSomething(id3); // error


当然、 "UserId" という名前が被った場合は同等のものになるということに注意する
ts
// 以下は同値 type UserId1 = Branded<number, 'UserId'>; type UserId2 = Branded<number, 'UserId'>;
これは構造的部分型の限界ではあるが、運用でカバーしましょう




Branded の定義例は無限に思いつくので好きなように定義すれば良い
要は、「そんな型、自分で定義することないっしょ」という構造にしておけば良い
例えば、以下のような型はもしかしたら問題になるかもしれない
ts
type Branded<T, U extends string> = T & { _type: U };
当然以下の2つの型定義は区別されない
ts
type UserId1 = Branded<number, 'UserId'> type UserId2 = number & { _type: 'UserId' }
プロジェクト内で意図せず UserId2 のような定義をすると区別されないことになる
_ から始まるpropertyは使わない」のようなルールがチーム内にあれば、これでも問題になることはない
その塩梅は適当で良い




型を厳格に扱う系のlibraryでは用意されていることが多い
zod を使うprojectではこれを使えば良いと思うmrsekut
io-ts t.brand ref
type-fest Opaque ref
組み合わすと never になってしまう