string 'T' tipini indekslemek için kullanılamaz

Sep 15 2020

TypeScript'te katı mod etkin ve türüne bir dize indeksleyici kurmuş olsam bile bir nesnede bir indeks ayarlamayla ilgili sorunlar yaşıyorum.

NoImplicitAny'nin devre dışı bırakılması sorunu düzeltir, ancak bunu tercih etmem.

Bu cevabı yakından inceledim ama önerilerle başarılı olamadım.

type stringKeyed = { [key: string]: any }
type dog = stringKeyed & { name: string }
type cat = stringKeyed & { lives: number }

function setLayerProps<T extends dog | cat>(
    item: T,
    props: Partial<T>
) {
    if (item) {
        Object.entries(props).forEach(([k, v]) => {
                item[k] = v; // Error: Type 'string' cannot be used to index type 'T'.ts(2536)

        });
    }
}


let d = { name: 'fido' } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: 'jim' })
setLayerProps(c, { lives: --c.lives })

Neyi yanlış yaptığımı gören var mı?

Teşekkürler!

Yanıtlar

3 arslan2012 Sep 15 2020 at 10:43

Bu, # 30769'un bir etkisidir ve bilinen bir kırılma değişikliğidir. Daha önce böyle çılgın şeylere hatasız izin vermiştik:

function foo<T extends Record<string, any>>(obj: T) {
    obj["s"] = 123;
    obj["n"] = "hello";
}
let z = { n: 1, s: "abc" };
foo(z);
foo([1, 2, 3]);
foo(new Error("wat"));

Genel olarak, Record <string, XXX> kısıtlaması, bir bağımsız değişkenin bir dize indeksi imzasına sahip olmasını sağlamaz, yalnızca bağımsız değişkenin özelliklerinin XXX türüne atanabilir olmasını sağlar. Dolayısıyla, yukarıdaki örnekte herhangi bir nesneyi etkili bir şekilde iletebilirsiniz ve işlev herhangi bir denetim olmadan herhangi bir özelliğe yazabilir.

3.5'te, yalnızca söz konusu nesnenin bir dizin imzasına sahip olduğunu bildiğimizde bir dizin imza öğesine yazabileceğinizi zorlarız. Yani, klon işlevinde küçük bir değişikliğe ihtiyacınız var:

function clone<T extends Record<string, any>>(obj: T): T {
    const objectClone = {} as Record<string, any>;

    for (const prop of Reflect.ownKeys(obj).filter(isString)) {
        objectClone[prop] = obj[prop];
    }

    return objectClone as T;
}

Bu değişiklikle artık objectClone'un bir dize indeksi imzası olduğunu biliyoruz.

Yani kodunuz

function setLayerProps<T extends dog | cat>(
    item: T,
    props: Partial<T>
) {
    if (item) {
        Object.entries(props).forEach(([k, v]) => {
            (item as dog | cat)[k] = v;

        });
    }
}

Referans: https://github.com/microsoft/TypeScript/issues/31661#issuecomment-497138929

2 Al-un Sep 15 2020 at 10:37

Sorumluluk reddi: Bu gerçekten bir cevap değil, bu konu etrafında dolaşmamın sonucudur. Açıklık adına bir cevap yazıyorum.

TL; DR: Nesne anahtarları türü string | number, değilstring

Çalışmasını sağlamak

Ben kullanılan typescript oyun alanı ve ile sona erdi çalışıyor şey typescript 4.0.2 altında:

type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };
type cat = stringKeyed & { lives: number };

function setLayerProps<T extends dog | cat>(item: T, props: Partial<T>) {
  if (item) {
    Object.entries(props).forEach(([k, v]) => {
      // With an explicit key casting, TypeScript does not complain
      const key = k as keyof T;
      item[key] = v;
    });
  }
}

let d = { name: "fido" } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: "jim" });
setLayerProps(c, { lives: --c.lives });

console.log(d); // [LOG]: { "name": "jim" }
console.log(c); // [LOG]: { "lives": 8 }

Neden işe yarıyor?

Kavşak türü ... bir kavşak değil

TypeScript belgelerine göre :

Bir kesişim türü, birden çok türü bir arada birleştirir. Bu, ihtiyacınız olan tüm özelliklere sahip tek bir tür elde etmek için mevcut türleri bir araya getirmenize olanak tanır.

Kahretsin, keşke daha önce bu iki satıra daha çok dikkat etsem.

İfade kesişimi beni kandırdı ve bu dogtipin yalnızca bir tuşa ( name) izin vereceğini düşündüm, ancak durum böyle değil. Coddingblast'taki TypeScript makalesinde yer alan Kesişim türlerine göre , kesişim türü, alt türü kesişen türlerden tüm özellikleri miras alır.

Başka bir deyişle:

type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };

anlamına gelir

  • İçin nameanahtarın, bir dize değeri bekleniyor
  • Diğer herhangi bir anahtar için herhangi bir değere izin verilir

Bu durumda aşağıdaki beyan doğrudur:

let d: dog = {name: 'fido' };
d.something = else;
d.age = 3;

Bu aşamada, çıkarmak istediğimiz tek sonuç, tüm doganahtarların yalnızca dizedir . Şey ... tam olarak değil.

JavaScript ... bir sayı anahtarı ile bir dize anahtarı arasındaki farkı bilmiyor T_T

Bu mükemmel StackOverflow cevabından alıntı yapmak için :

TypeScript 2.9 Sürüm Notlarında tanımlandığı gibi, bir dize indeksi imzasına sahip bir arabirim anahtarlarsanız, bir dizi ve sayı birleşimi döndürür.

Ve typescript belgeleri bahseder:

Türün bir dize veya sayı indeksi imzası varsa, keyof bunun yerine şu türleri döndürür:

type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
//   ^ = type M = string | number

Kendimi ikna etmek için şunları denedim:

type keys = keyof stringKeyed;
type dogKeys = keyof dog;
const keyAsStr: dogKeys = "this is OK";
const keyAsNum: dogKeys = 42;

Hem keysve dogKeyskarar verin string | number.

string! == string | numara

Şimdi, Error: Type 'string' türünün 'T' .ts (2536) tipini indekslemek için kullanılamamasının nedeni, sadece bir anahtar tipinin asla stringsadece olamayacağı içindir. "Minimal" sürüm string | number, dolayısıyla yazım hatasıdır. İlginç bir şekilde, içindeki Tanahtar türünü almaya çalışırken, türün üzerine gelmek setLayerPropstürü hemen görüntülemiyor. Ben de denedim

// Inside the setLayerProps function
type tKey = keyof T;
const asStr: tKey = "43";
const asNum: tKey = 43;
const asObj: tKey = { a: "a" };

Ve şu hatayla sonlandırdım:

Type '{ a: string; }' is not assignable to type 'keyof T'.
  Type '{ a: string; }' is not assignable to type 'string | number'.
    Type '{ a: string; }' is not assignable to type 'number'.

Yani TypeScript gerçekten de birstring | number .

Nedense henüz anlamıyorum, bu işe yaramıyor:

const key = k as string | number;

Hatalara yol açtığı için:

Type 'number' cannot be used to index type 'T'.(2536)
Type 'string' cannot be used to index type 'T'.(2536)

Açık döküm neden güvenlidir?

In setLayerPropsfonksiyonu, bunu biliyoruz itemanahtar ya bir olduğunu stringya bir number. Yani açıkça döküm

const key = k as keyof T;

Yani , bir dizenin bir dizi veya bir sayı olduğunu söylüyorum , bu benim inancıma göre her zaman doğrudur.