Jak zapobiec błędowi „Podpis indeksu typu obiektu niejawnie ma typ„ dowolny ”podczas kompilowania skryptu z włączoną flagą noImplicitAny?

309

Zawsze kompiluję Typescript z flagą --noImplicitAny. Ma to sens, ponieważ chcę, aby sprawdzanie mojego typu było jak najściślejsze.

Mój problem polega na tym, że następujący kod powoduje błąd Index signature of object type implicitly has an 'any' type:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Należy zauważyć, że idea polega na tym, że zmienna klucza pochodzi z innego miejsca w aplikacji i może być dowolnym kluczem w obiekcie.

Próbowałem jawnie rzutować ten typ przez:

let secondValue: string = <string>someObject[key];

A może mój scenariusz jest po prostu niemożliwy --noImplicitAny?

Jasper Schulte
źródło

Odpowiedzi:

337

Dodanie sygnatury indeksu poinformuje TypeScript, jaki powinien być typ.

W twoim przypadku tak byłoby [key: string]: string;

interface ISomeObject {
    firstKey:      string;
    secondKey:     string;
    thirdKey:      string;
    [key: string]: string;
}

Wymusza to jednak również wymuszenie zgodności wszystkich typów właściwości z podpisem indeksu. Ponieważ wszystkie właściwości są a, stringto działa.

Chociaż podpisy indeksowe są skutecznym sposobem na opisanie tablicy i wzorca „słownika”, wymuszają również, aby wszystkie właściwości pasowały do ​​ich typu zwracanego.

Edytować:

Jeśli typy nie pasują, można użyć typu unii [key: string]: string|IOtherObject;

W przypadku typów unii lepiej jest pozwolić, aby TypeScript wyprowadzał typ zamiast go definiować.

// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';

Chociaż może to być nieco nieporządne, jeśli masz wiele różnych typów w sygnaturach indeksu. Alternatywą jest użycie anyw podpisie. [key: string]: any;Następnie musisz rzucić typy tak, jak wcześniej.

thinkrepo
źródło
A jeśli twój interfejs wygląda jak interfejs ISomeObject {firstKey: string; secondKey: IOtherObject; } chyba nie jest to możliwe?
Jasper Schulte,
Dzięki! Połączenie dowolnego typu z rzutowaniem typu na skrzynkę wydaje się być sprzedaną drogą.
Jasper Schulte,
Cześć, jak obsłużyć „anyObject [key: Object] ['name']”?
Code_Crash
lub powiedz coś jak _obj = {}; niech _dbKey = _props [key] ['name']; _obj [_dbKey] = ten [klucz]; tutaj _props jest obiektem, a obiekt [klucz] również zwróci obiekt, który będzie miał właściwość name.
Code_Crash
180

Innym sposobem uniknięcia błędu jest użycie takiej obsady:

let secondValue: string = (<any>someObject)[key]; (Uwaga w nawiasie)

Jedynym problemem jest to, że nie jest to już bezpieczne dla typu, jak rzucasz any. Ale zawsze możesz przywrócić właściwy typ.

ps: używam maszynopisu 1.7, nie jestem pewien co do poprzednich wersji.

Pedro Villa Verde
źródło
20
Aby uniknąć ostrzeżeń tslint, możesz również użyć:let secondValue: string = (someObject as any)[key];
briosheje
96

TypeScript 2.1 wprowadził elegancki sposób obsługi tego problemu.

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

Możemy uzyskać dostęp do wszystkich nazw właściwości obiektów podczas fazy kompilacji według keyofsłowa kluczowego (patrz dziennik zmian ).

Trzeba tylko zastąpić stringtyp zmiennej keyof ISomeObject. Teraz kompilator wie, że keyzmienna może zawierać tylko nazwy właściwości z ISomeObject.

Pełny przykład:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   number;
}

const someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   3
};

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];

Kod aktywny na typescriptlang.org (ustaw noImplicitAnyopcję)

Dalsza lektura przy większej liczbie keyofzastosowań .

Piotr Lewandowski
źródło
6
Jednak to nie zadziała, jeśli zadeklarujemy keyjako const key = (keyof ISomeObject)= „drugi” + „Klucz”
rozczarowany
55

Poniższe ustawienie tsconfig pozwoli ci zignorować te błędy - ustaw na true.

suppressImplicitAnyIndexErrors

Pomiń noImplicitWszelkie błędy indeksowania obiektów bez sygnatur indeksu.

Scott Munro
źródło
14
tego nie powinieneś robić - prawdopodobnie ktoś z twojego zespołu wyraźnie ustawił tę opcję kompilatora, aby kod był bardziej kuloodporny!
atsu85
12
Nie zgadzam się, że właśnie po to została stworzona ta opcja: Zezwalaj na notacje w nawiasach za pomocą --noImplicitAny. Dopasuj idealnie op.
Ghetolay,
4
Zgadzam się z @Ghetolay. Jest to również jedyna opcja, jeśli modyfikacja interfejsu nie jest możliwa. Na przykład z wewnętrznymi interfejsami, takimi jak XMLHttpRequest.
Marco Roy,
1
Zgadzam się również z @Ghetolay. Jestem ciekawy, jak to jakościowo różni się od odpowiedzi Pedro Villa Verde (poza tym, że kod jest mniej brzydki). Wszyscy wiemy, że w miarę możliwości należy unikać dostępu do właściwości obiektu za pomocą ciągu, ale czasami możemy cieszyć się tą swobodą, rozumiejąc ryzyko.
Stephen Paul,
To tylko kompromisy. Wybierz to, co lubisz: mniejszy obszar błędu i ścisły dostęp do indeksu lub więcej obszaru dla błędów i łatwy dostęp do nieznanych indeksów. keyofOperator TS2.1 może pomóc zachować wszystko w ścisłej zgodności, patrz odpowiedź Piotra!
trusktr,
24

Wspomniane wyżej rozwiązanie „keyof” działa. Ale jeśli zmienna zostanie użyta tylko raz, np. W wyniku zapętlenia obiektu itp., Można go także rzutować.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}
Karna
źródło
Dzięki. Działa to w przypadku dowolnego dostępu do klucza podczas iteracji kluczy innego obiektu.
bucabay
19

posługiwać się keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]
alsotang
źródło
7

Podobne do odpowiedzi @Piotra Lewandowskiego, ale w forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });
Steve Brush
źródło
Jak do tego doszło? Próbuję to samo (TS 3.8.3), choć zgłasza błąd mówiąc: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Mój kod wygląda tak Object.keys(components).forEach((comp: Component) => {...}, gdzie Componentjest typ (jak MyConfig).
theGirrafish
6

Zadeklaruj obiekt w ten sposób.

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}
Supun Dharmarathne
źródło
6

Nie masz indeksatora? Następnie stwórz własny!

Globalnie zdefiniowałem to jako łatwy sposób zdefiniowania podpisu obiektu. Tmoże być w anyrazie potrzeby:

type Indexer<T> = { [ key: string ]: T };

Po prostu dodaję indexerjako członek klasy.

indexer = this as unknown as Indexer<Fruit>;

Skończyłem z tym:

constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {

}

apple: Fruit<string>;
pear: Fruit<string>;

// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;

something() {

    this.indexer['apple'] = ...    // typed as Fruit

Zaletą tego jest to, że odzyskujesz właściwy typ - wiele używanych rozwiązań <any>utraci dla Ciebie pisanie. Pamiętaj, że nie wykonuje to żadnej weryfikacji środowiska wykonawczego. Nadal musisz sprawdzić, czy coś istnieje, jeśli nie wiesz na pewno.

Jeśli chcesz być zbyt ostrożny i używasz tego, strictmożesz to zrobić, aby odsłonić wszystkie miejsca, które mogą być potrzebne do wykonania jawnej, niezdefiniowanej kontroli:

type OptionalIndexed<T> = { [ key: string ]: T | undefined };

Zwykle nie uważam tego za konieczne, ponieważ skoro mam gdzieś właściwość ciągu, zwykle wiem, że jest poprawna.

Uważam tę metodę za szczególnie przydatną, jeśli mam dużo kodu, który musi uzyskać dostęp do indeksu, a pisanie można zmienić w jednym miejscu.

Uwaga: Używam stricttrybu i unknownjest to zdecydowanie konieczne.

Skompilowany kod będzie po prostu indexer = this, więc jest bardzo podobny do tego, kiedy tworzy się _this = thisdla Ciebie maszynopis .

Simon_Weaver
źródło
1
W niektórych przypadkach możesz Record<T>zamiast tego użyć tekstu - nie jestem teraz w stanie zbadać dokładnych szczegółów tego, ale w niektórych ograniczonych przypadkach może to działać lepiej.
Simon_Weaver,
5

Utwórz interfejs, aby zdefiniować interfejs „indeksatora”

Następnie utwórz obiekt z tym indeksem.

Uwaga: nadal będą występować te same problemy, które opisały inne odpowiedzi dotyczące egzekwowania typu każdego elementu - ale często tego właśnie chcesz.

Możesz ustawić parametr typu ogólnego, cokolwiek potrzebujesz: ObjectIndexer< Dog | Cat>

// this should be global somewhere, or you may already be 
// using a library that provides such a type
export interface ObjectIndexer<T> {
  [id: string]: T;
}

interface ISomeObject extends ObjectIndexer<string>
{
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Plac zabaw dla maszynopisu


Możesz nawet użyć tego w ograniczeniu ogólnym podczas definiowania typu ogólnego:

export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup

Następnie Twewnątrz klasy można indeksować :-)

Simon_Weaver
źródło
Nie sądzę, aby istniał standardowy „wbudowany” interfejs do Dictionaryreprezentowania tego { [key: string]: T }, ale jeśli kiedykolwiek istnieje, edytuj to pytanie, aby usunąć moje ObjectIndexer.
Simon_Weaver
3

Zadeklaruj typ, którego kluczem jest łańcuch i wartością może być dowolny, a następnie zadeklaruj obiekt za pomocą tego typu, a włókna nie będą się wyświetlać

type MyType = {[key: string]: any};

Twój kod będzie

type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];
O.AbedElBaset
źródło
1

Obecnie lepszym rozwiązaniem jest deklarowanie typów. Lubić

enum SomeObjectKeys {
    firstKey = 'firstKey',
    secondKey = 'secondKey',
    thirdKey = 'thirdKey',
}

let someObject: Record<SomeObjectKeys, string> = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue',
};

let key: SomeObjectKeys = 'secondKey';

let secondValue: string = someObject[key];
Artsiom Tymchanka
źródło
1

Najprostszym rozwiązaniem, które można znaleźć za pomocą Typescript 3.1 w 3 krokach, jest:

1) Utwórz interfejs

interface IOriginal {
    original: { [key: string]: any }
}

2) Wykonaj kopię na maszynie

let copy: IOriginal = (original as any)[key];

3) Używaj w dowolnym miejscu (w zestawie JSX)

<input customProp={copy} />
Artokun
źródło