Typescript: Hedef arayüzde belirtilenden daha fazla özelliğe sahip bir nesnenin atanmasını engellemek


Diyelim ki tipik bir 'kullanıcı' nesnem var ve içinde sıradan kullanıcı adı, e-posta, şifre vb. özellikler var. Böyle bir kullanıcının şifre dahil olmadığına dair garantilerle oluşturulmuş ve yönetilen gerçek bir 'alt kümesi' olan bir nesne yaratmak istiyorum. İşte kaba bir yaklaşım:

interface IUserSansPassword {
    username: string;
    email: string;
}

class UserSansPassword implements IUserSansPassword { ... }

interface IUser extends IUserSansPassword {
    password: string;
}

class User implements IUser { ... }

IUserSansPassword türünde bir nesne oluşturmaya çalışırken, bunun aşağıdaki şekilde hata vereceğini bekledim:

const userSansPassword: UserSansPassword = new User(); // No TS Error †††

Ancak, şaşırtıcı bir şekilde, TS zaten atanmış 'extra' özellikleri olan nesneleri atamayı yasaklamadığı için TS hatası almıyorum. Bu şaşırtıcı çünkü ekstra özelliği doğrudan tanımlamaya çalışırsam şöyle bir hata alırdım:

const userSansPassword: IUserSansPassword = {
    username: 'jake',
    email: '[email ',
    password: '' // TS Error ***
}

Sorularımın özeti:

  1. TS neden bu şekilde davranıyor? Fazladan özelliğe sahip bir tipe atama yapılmasına izin vermek kötü değil mi (bu yüzden yukarıda *** ile hata alıyorsunuz)?

  2. Yukarıdaki †††'de TS'nin hata vermesini sağlamak için kullanabileceğim bir TS ayarı veya tekniği var mı?

Cevap #1

Buradaki diğer cevaplar esasen doğru: TypeScript'te türler genellikle açık/genişletilebilir ve her zaman özellik eklenebilir; yani, yalnızca bilinen özelliklerin var olmasına izin verilen tam türler (microsoft/TypeScript#12936'da istendiği gibi) değillerdir. TypeScript genel olarak tam türleri gerçekten desteklemez, ancak fark ettiğiniz gibi, yeni oluşturulmuş nesne literalleri fazlalık özellik kontrolleri yoluyla tam türler olarak ele alınır.

TypeScript'te bir türden belirli bir özellik anahtarını gerçekten yasaklamak istiyorsanız, bunu özelliği isteğe bağlı yaparak ve türünü never veya undefined olarak ayarlayarak yapabilirsiniz:

interface IUserSansPassword {
  username: string;
  email: string;
  password?: never; // cannot have a password
}

declare class UserSansPassword implements IUserSansPassword {
  username: string;
  email: string;
  password?: never; // need to declare this also
}

Şimdi UserSansPassword'in tanımlı bir password özelliğine sahip olmadığı belli oldu. Elbette şimdi aşağıdakiler bir hatadır:

interface IUser extends IUserSansPassword { // error! 
// Types of property "password" are incompatible
  password: string;
}

Şifre ekleyerek IUserSansPassword öğesini genişletemezsiniz... eğer A extends B ise, her zaman bir B örneğinin beklendiği yerde bir A örneğini kullanabilirsiniz. Yapabileceğiniz şey, ilgili bir türü, yani orijinal IUserSansPassword öğenizi genişletmektir; bu, Omit yardımcı türü kullanılarak hesaplanabilir:

interface IUser extends Omit<IUserSansPassword, "password"> {
  password: string;
}

declare class User implements IUser {
  username: string;
  email: string;
  password: string;
}

Ve sonra beklediğiniz gibi aşağıdaki bir hata vardır:

const userSansPassword: UserSansPassword = new User();
// error, mismatch on "password" prop

Koda bağlantı

İlgili makaleler