Як я можу запобігти помилці "Індексний підпис типу об'єкта неявно має" будь-який "тип під час компіляції машинопису з увімкненим прапором noImplicitAny?


309

Я завжди компілюю Typescript з прапором --noImplicitAny. Це має сенс, оскільки я хочу, щоб перевірка мого типу була максимально щільною.

Моя проблема полягає в тому, що з наступним кодом я отримую помилку 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];

Важливо зазначити, що ідея полягає в тому, що змінна ключа походить десь із програми та може бути будь-якою з клавіш об'єкта.

Я спробував явно передати тип:

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

Або мій сценарій просто неможливий --noImplicitAny?

Відповіді:


337

Додавання підпису індексу дозволить TypeScript знати, яким має бути тип.

У вашому випадку це було б [key: string]: string;

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

Однак це також примушує всі типи властивостей відповідати підпису індексу. Оскільки всі властивості є, stringце працює.

Хоча підписи індексів є потужним способом описати шаблон масиву та "словник", вони також примушують всіх властивостей відповідати їхньому типу повернення.

Редагувати:

Якщо типи не збігаються, можна використовувати тип об'єднання [key: string]: string|IOtherObject;

З типами об'єднань краще, якщо ви дозволяєте TypeScript робити висновок типу, а не визначати його.

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

Хоча це може стати трохи безладним, якщо у підписі індексу багато різних типів. Альтернативою цьому є використання anyв підписі. [key: string]: any;Тоді вам потрібно буде видати типи, як ви робили вище.


І якщо ваш інтерфейс виглядає як інтерфейс ISomeObject {firstKey: string; secondKey: IOtherObject; } це неможливо, я думаю?
Джаспер Шульте

Дякую! Поєднання будь-якого типу разом із виданням типу на кожний вигляд здається проданим способом.
Джаспер Шулте

Привіт, як поводитися з "anyObject [key: Object] ['name']"?
Code_Crash

або сказати щось на зразок _obj = {}; нехай _dbKey = _props [key] ['name']; _obj [_dbKey] = цей [ключ]; тут _props - це об'єкт, а об'єкт [key] також поверне об'єкт, який матиме властивість name.
Code_Crash

180

Ще один спосіб уникнути помилки - це використовувати ролик таким чином:

let secondValue: string = (<any>someObject)[key]; (Зверніть увагу на дужки)

Єдина проблема полягає в тому, що це більше не є безпечним для типу, як ви це робите any. Але завжди можна повернутись до потрібного типу.

ps: Я використовую typecript 1.7, не впевнений у попередніх версіях.


20
Щоб уникнути попереджень tslint, ви також можете використовувати:let secondValue: string = (someObject as any)[key];
briosheje

96

TypeScript 2.1 представив елегантний спосіб вирішення цього питання.

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

Ми можемо отримати доступ до всіх імен властивостей об'єкта під час фази компіляції за keyofключовим словом (див. Журнал змін ).

Вам потрібно лише замінити stringзмінний тип на keyof ISomeObject. Тепер компілятор знає, що keyзмінна може містити лише імена властивостей з ISomeObject.

Повний приклад:

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];

Живий код на typecriptlang.org (встановити noImplicitAnyваріант)

Подальше читання з більшою кількістю keyofзвичок .


6
Однак це не спрацює, якщо ми оголосимо keyяк const key = (keyof ISomeObject)= "другий" + "Ключ"
Розчарований

55

Наведені нижче параметри tsconfig дозволять вам ігнорувати ці помилки - встановіть її на true.

suppressImplicitAnyIndexErrors

Пригнічуйте помилки noImplicitAny для індексації об'єктів, у яких відсутні підписи індексу.


14
це те, що ви не повинні робити - напевно, хтось із вашої команди явно встановив цей варіант компілятора, щоб зробити код більш бездоганним!
atsu85

12
Я не погоджуюсь, саме для цього був зроблений цей параметр: Дозволити позначення дужок --noImplicitAny. Відповідь ідеально на запитання.
Гетолай

4
Я згоден з @Ghetolay. Це також єдиний варіант, якщо змінити інтерфейс неможливо. Наприклад, із внутрішніми інтерфейсами, як XMLHttpRequest.
Марко Рой

1
Я також згоден з @Ghetolay. Мені цікаво, чим це якісно відрізняється від відповіді Педро Вілла Верде (крім того, що код менш некрасивий). Всі ми знаємо, що слід уникати доступу до об’єктної власності за допомогою рядка, але ми іноді отримуємо таку свободу, розуміючи ризики.
Стівен Пол

Це просто компроміси. Виберіть те, що вам подобається: менша площа помилки та суворий доступ до індексу або більша площа поверхні для помилок та легкий доступ до невідомих індексів. Оператор TS2.1 keyofможе допомогти утримувати все суворо, дивіться відповідь Піотра!
trusktr

24

Згадане вище рішення 'keyof' працює. Але якщо змінна використовується лише один раз, наприклад, перебираючи об'єкт тощо, ви також можете ввести його.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}

Дякую. Це працює для довільного доступу до ключів під час ітерації ключів іншого об'єкта.
bucabay


7

Схожа на відповідь @Piotr Lewandowski, але в межах forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });

Як ти змусив це працювати? Я намагаюся те ж саме (т.с. 3.8.3), хоча він видає помилку , кажучи: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Мій код виглядає так Object.keys(components).forEach((comp: Component) => {...}, де Componentтип (подібний MyConfig).
theGirrafish


6

Немає індексатора? Тоді зробіть своє!

Я глобально визначив це як простий спосіб визначення підпису об'єкта. Tможе бути , anyякщо це необхідно:

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

Я просто додаю indexerяк член класу.

indexer = this as unknown as Indexer<Fruit>;

Отже, закінчую це:

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

Перевага цього полягає в тому, що ви повернете належний тип - багато використовуваних рішень <any>втратять набір тексту для вас. Пам'ятайте, що це не виконує жодної перевірки часу виконання. Вам все одно доведеться перевірити, чи щось існує, якщо ви точно не знаєте, що воно існує.

Якщо ви хочете бути дуже обережними, і ви користуєтесь цим, strictви можете зробити це, щоб виявити всі місця, які можуть знадобитися, щоб зробити явну не визначену перевірку:

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

Зазвичай я не вважаю це необхідним, оскільки, якщо у мене є десь властивість string, я зазвичай знаю, що це дійсно.

Я вважаю цей метод особливо корисним, якщо у мене є багато коду, який потребує доступу до індексатора, і введення тексту можна змінити лише в одному місці.

Примітка: я використовую strictрежим, і unknownобов'язково потрібно.

Складений код просто буде indexer = this, тому він дуже схожий на те, коли машинопис створює _this = thisдля вас.


1
Деякі випадки, які ви можете використовувати Record<T>замість цього типу, я зараз не в змозі вивчити деталі цього, але в деяких обмежених випадках це може працювати краще.
Simon_Weaver

5

Створіть інтерфейс для визначення інтерфейсу 'індексатора'

Потім створіть об'єкт за допомогою цього індексу.

Примітка: це все ще матиме ті самі проблеми, які описано в інших відповідях щодо застосування типу кожного елемента, але це часто саме те, що ви хочете.

Ви можете зробити параметр загального типу все, що вам потрібно: 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];

Ігровий майданчик


Ви навіть можете використовувати це в загальному обмеженні, визначаючи загальний тип:

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

Тоді Tвсередині класу можна проіндексувати :-)


Я не думаю , що є стандарт «вбудований» інтерфейс , Dictionaryякий представляє { [key: string]: T }, але якщо коли - небудь це Виправте це питання видалити мій ObjectIndexer.
Simon_Weaver

3

Оголосіть тип, клавішем якого є рядок і значенням може бути будь-який, тоді оголосіть об'єкт із цим типом, і обличчя не з’явиться

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

Тож ваш код буде

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

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

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];

1

На сьогодні кращим рішенням є оголошення типів. Подібно до

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];

1

Найпростіше рішення, яке я міг знайти за допомогою Typescript 3.1 в 3 етапи:

1) Складіть інтерфейс

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

2) Зробіть набрану копію

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

3) Використовуйте будь-де (JSX включений)

<input customProp={copy} />
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.