Как предотвратить ошибку «Индексная сигнатура типа объекта неявно имеет тип« любой »» при компиляции машинописи с включенным флагом 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 [ключ: объект] ['имя']"?
Code_Crash

или сказать что-то вроде _obj = {}; let _dbKey = _props [key] ['name']; _obj [_dbKey] = этот [ключ]; здесь _props - это объект, а объект [ключ] также вернет объект, который будет иметь свойство name.
Code_Crash

180

Другой способ избежать ошибки - использовать приведение типа этого:

let secondValue: string = (<any>someObject)[key]; (Обратите внимание на круглые скобки)

Единственная проблема заключается в том, что это больше не является типобезопасным, как вы используете any. Но вы всегда можете привести к правильному типу.

PS: я использую машинопись 1.7, не уверен насчет предыдущих версий.


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

97

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

Живой код на typescriptlang.org (установить noImplicitAnyопцию)

Дальнейшее чтение с большим количеством keyofиспользования .


6
Однако это не будет работать, если мы объявим keyкак const key = (keyof ISomeObject)= 'second' + 'Key'
Разочаровано

55

Следующая настройка tsconfig позволит вам игнорировать эти ошибки - установите для нее значение true.

suppressImplicitAnyIndexErrors

Подавлять ошибки noImplicitAny для объектов индексации, в которых отсутствуют подписи индекса.


14
Это то, что вы не должны делать - возможно, кто-то в вашей команде явно установил этот параметр компилятора, чтобы сделать код более пуленепробиваемым!
atsu85

12
Я не согласен, это именно то, для чего была сделана эта опция: Разрешить обозначения в скобках с помощью --noImplicitAny. Совпадение идеально оп вопрос.
Гетолай

4
Я согласен с @Getholay. Это также единственный вариант, если изменение интерфейса невозможно. Например, с внутренними интерфейсами вроде XMLHttpRequest.
Марко Рой

1
Я также согласен с @Getholay. Мне любопытно, насколько это качественно отличается от ответа Педро Вилла Верде (кроме того факта, что код менее уродлив). Мы все знаем, что по возможности следует избегать доступа к свойству объекта с использованием строки, но иногда мы наслаждаемся этой свободой, понимая риски.
Стивен Пол

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

24

Решение «keyof», упомянутое выше, работает. Но если переменная используется только один раз, например, циклически проходя по объекту и т. Д., Вы также можете ввести ее.

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

Спасибо. Это работает для произвольного доступа к ключу при итерации ключей другого объекта.
Bucabay

19

использование keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

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

Обычно я не нахожу это необходимым, поскольку, если у меня есть свойство строки откуда-то, я обычно знаю, что оно действительно.

Я нашел этот метод особенно полезным, если у меня много кода, необходимого для доступа к индексатору, и типизацию можно изменить только в одном месте.

Примечание: я использую 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

Объявите тип, ключ которого является строковым, а значение может быть любым, затем объявите объект с этим типом, и lint не будет отображаться

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.