Saya memiliki dua kelas, A dan B.

class A {
  id: number;
}

class B {
  id: number;
}

Apakah mungkin memiliki fungsi yang diketik sehingga hanya menerima id dari kelas A? Mungkin dengan mengubah number menjadi semacam jenis khusus?

1
rablentain 12 Mei 2021, 22:47

1 menjawab

Jawaban Terbaik

Sepertinya Anda ingin properti id kelas A menjadi < em>tipe nominal kompatibel dengan number. Jika tipe ini dideklarasikan sebagai ClassANumber, itu akan dianggap berbeda dari tipe number biasa, karena mereka memiliki deklarasi dan nama yang berbeda. Jadi perilaku ideal Anda mungkin terlihat seperti ini:

class A {
  id: ClassANumber;
  constructor(id: ClassANumber) {
    this.id = id;
  }
}

class B {
  id: number;
  constructor(id: number) {
    this.id = id;
  }

}

function acceptClassAId(id: ClassANumber) { }
acceptClassAId(new A(1).id) // okay
acceptClassAId(new B(1).id) // error, number is not a ClassANumber

Sayangnya, sistem tipe TypeScript adalah struktural, dan bukan nominal. Jika tipe number dan ClassANumber memiliki struktur yang sama (yaitu, keduanya berperilaku seperti number saat digunakan), maka kompiler akan menganggapnya sebagai tipe yang sama:

type ClassANumber = number;

acceptClassAId(new A(1).id) // no error, as desired
acceptClassAId(new B(1).id) // ALSO no error, 😢

Tidak ada kesalahan di sini karena ClassANumber adalah tipe yang sama dengan number, sehingga tipe new B(1).id, number, dapat ditetapkan ke ClassANumber.


Ada berbagai trik yang dapat Anda gunakan untuk mensimulasikan tipe nominal di TypeScript; Anda dapat melihat bagian Pengetikan Nominal dari TypeScript Deep Dive @basarat, atau < a href="https://github.com/Microsoft/TypeScript/wiki/FAQ#can-i-make-a-type-alias-nominal" rel="nofollow noreferrer">entri FAQ TypeScript untuk "Dapatkah saya membuat jenis alias nominal"?. Tempat kanonik untuk mendiskusikan/mempertimbangkan/menyayangkan ini mungkin microsoft/TypeScript#202, masalah terbuka sejak 2014.

Yang akan saya sajikan di sini adalah dari entri FAQ "Dapatkah saya membuat tipe alias nominal". Ini menggunakan apa yang disebut type branding untuk berpotongan primitif number dengan tipe objek yang berisi properti tunggal yang dapat digunakan untuk membedakan number dari ClassANumber:

type ClassANumber = number & { __classAId: true };

Sekarang kompiler dapat membedakannya:

acceptClassAId(new A(1).id) // okay
acceptClassAId(new B(1).id) // error!
// Argument of type 'number' is not assignable to parameter of type 'ClassANumber'.

Ini hanya solusi, tentu saja. Secara teknis Anda berbohong kepada kompiler: saat runtime, ClassANumber hanya akan menjadi number, tanpa properti __classAId. Jadi ada berbagai kutil yang muncul pada waktu kompilasi. Misalnya, karena tidak ada cara untuk mengubah number menjadi ClassANumber secara nyata, Anda harus menggunakan sesuatu seperti ketik pernyataan dalam kode apa pun yang perlu melakukan ini:

class A {
  id: ClassANumber;
  constructor(id: number) {
    this.id = id as ClassANumber; // need to assert here
  }
}

Dan begitu Anda memiliki sesuatu yang diyakini oleh kompiler sebagai ClassANumber, Anda akan melihat properti __classAId phantom ini yang perlu Anda hindari:

new A(2).id.__classAId.valueOf(); // accepted by compiler, but error at runtime

Jadi lanjutkan dengan hati-hati.

Tautan taman bermain ke kode

1
jcalz 12 Mei 2021, 20:25