Я всегда компилирую 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
?
Добавление индексной подписи позволит TypeScript знать, какой тип должен быть.
В вашем случае это будет [key: string]: string;
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
[key: string]: string;
}
Однако это также приводит к тому, что все типы свойств будут соответствовать сигнатуре индекса. Поскольку все свойства являются string
, он работает.
Хотя индексные подписи являются мощным способом описания массива и шаблона словаря, они также обеспечивают, чтобы все свойства соответствовали их возвращаемому типу.
Edit:
Если типы не совпадают, можно использовать тип объединения [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;
Затем вам нужно будет использовать типы, подобные тому, что вы делали выше.
Другой способ избежать ошибки заключается в том, чтобы использовать приведение как это:
let secondValue: string = (<any>someObject)[key];
(Обратите внимание на скобки)
Единственная проблема заключается в том, что это больше не безопасно для типов, поскольку вы выполняете any
. Но вы всегда можете вернуться к правильному типу.
ps: Я использую typescript 1.7, не уверен в предыдущих версиях.
let secondValue: string = (someObject as any)[key];
В TypeScript 2.1 представлен элегантный способ решения этой проблемы.
const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];
Мы можем получить доступ ко всем именам объектов объекта на этапе keyof
ключевыми словами keyof
(см. 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
.
key
как const key = (keyof ISomeObject)
= 'second' + 'Key'
Следующая настройка tsconfig позволит вам игнорировать эти ошибки - установите для нее значение true.
suppressImplicitAnyIndexErrors
Подавить noImplicitAny ошибки для индексирования объектов, не имеющих индексных подписей.
--noImplicitAny
. Совпадение идеально оп вопрос.
Вышеупомянутое решение "keyof" работает. Но если переменная используется только один раз, например, через объект и т.д., Вы также можете его выводить.
for (const key in someObject) {
sampleObject[key] = someObject[key as keyof ISomeObject];
}
Как и ответ @Piotr Lewandowski, но в пределах forEach
:
const config: MyConfig = { ... };
Object.keys(config)
.forEach((key: keyof MyConfig) => {
if (config[key]) {
// ...
}
});
Объявите объект следующим образом.
export interface Thread {
id:number;
messageIds: number[];
participants: {
[key:number]: number
};
}
Я глобально определил это как простой способ определить сигнатуру объекта. 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
создает _this = this
для вас.
Затем создайте свой объект с этим индексом.
Примечание: это все равно будет иметь те же проблемы, что и другие ответы, описанные применительно к принудительному типу каждого элемента, но часто это именно то, что вы хотите.
Вы можете сделать универсальный параметр типа любым, что вам нужно: ObjectIndexer< Dog | Cat>
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
.
Простейшим решением, которое я мог бы найти с помощью TypScript 3.1 в 3 этапа, является:
1) Сделать интерфейс
interface IOriginal {
original: { [key: string]: any }
}
2) Сделайте типизированную копию
let copy: IOriginal = (original as any)[key];
3) Используйте везде (включая JSX)
<input customProp={copy} />
В настоящее время лучшим решением является объявление типов. подобно
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];