TypeScript'te güçlü tipli kimlikleri nasıl başarmalı?


Güçlü tipli bir kimlik, yanlış kimliği bir fonksiyona parametre olarak geçirmeyi daha zor hale getirirdi. Örneğin, eğer hem Thing hem de NotAThing aynı türde id'ye sahipse, getThingById(notAThing.id) hatasını yapmak oldukça kolaydır.

İşte ilk denemeniz, ama sonuçta Id<TModel, TId> her ikisi de number olduğu için hedefe ulaşmıyor. Son iki fonksiyon çağrısının başarısız olması için bunu nasıl değiştirebilirsiniz?

type Thing = { id: Id<Thing> };
type NotAThing = { id: Id<NotAThing> };

type Id<TModel, TId = number> = TId;

const thing: Thing = {
  id: 123,
};

const notAThing: NotAThing = {
  id: 456,
};

function getThing(id: Id<File>) {
  return "here's the thing";
}

function getNotAThing(id: Id<NotAThing>) {
    return "here's the not a thing";
}


// This works.
getThing(thing.id);
getNotAThing(notAThing.id);


// How to make it not work (fail compilation) if you pass the "wrong type of id"?
getThing(notAThing.id);
getNotAThing(thing.id);

Oyun Alanında test etmeye hazır

Cevap #1

Bu kavrama "opak tipler" denir. Mevcut bir tipe (örneğin bir string veya bir sayı) benzer bir tiptir ancak onunla değiştirilebilir değildir.

Bunun harika bir uygulaması, TypeScript markalamasını kullanan ama örtük dönüşümlere izin veren Flavoring: Flexible Nominal Typing for TypeScript makalesinde Drew Colthorp tarafından tanımlanmıştır. Şöyle görünüyor:

interface Flavoring<FlavorT> {
  _type?: FlavorT;
}
export type Flavor<T, FlavorT> = T & Flavoring<FlavorT>;

Daha sonra şunlar için kullanılabilir (makaleden örnek olarak):

type PersonId = Flavor<number, "Person">
type BlogPostId = Flavor<number, "BlogPost">

Ve şimdi basit sayılar her ikisine de atanabilir, ancak değerler PersonId ve BlogPostId olarak tanımlanan değişkenler arasında çapraz atanamaz:

let p: PersonId = 1;
let b: BlogPostId= 2;

p = b; //error
b = p; //error

Oyun Alanı Bağlantısı


Minimum değişikliklerle, sorudaki kod Id türünü şu şekilde tanımlamak için değiştirilebilir:

type Id<T> = Flavor<number, T>;

Ve sonra bir dize geçirerek kullanılabilir:

type Thing = { id: Id<"Thing"> };
type NotAThing = { id: Id<"NotAThing"> };

Bu da şimdi örneğin beklendiği gibi davranmasını sağlıyor: Playground Linki

İlgili makaleler