branded types
「branded types」という名前はTypeScript特有の概念のようで他での実例は見かけない

一方で、TypeScriptの文脈ではこの名前がよく使われる
compile後の構造に影響を与えないことが特徴
定義の例
tstype Branded<T, Name extends string> = T & { [key in `__${Name}`]: typeof tag };
declare const tag: unique symbol;
[key in \
__${V}\
の意味
key
に V
を使うことで、 Branded
で定義した型を組み合わすことができる
tstype A = Branded<number, 'a'>;
type B = Branded<A, 'b'>; // BrandedのBranded
ここが固定されたpropertyになっていると、組み合わすことが出来ない
例えば { _type: U }
にしていると、
tstype B = number & { _type: 'a' } & { _type: 'b' } // never
propertyが衝突しているため、そういう型は存在せず never
になる
__
を付けているのは、意図せず同じような型が定義されることを避けるため
「このprojectでは
__
から始まるpropertyは使わない」という前提がある

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

組み合わすと never
になってしまう