Ошибка TS2339: свойство 'x' не существует для типа 'Y'
Я не понимаю, почему этот код генерирует ошибку TypeScript. (Это не оригинальный код, а немного производный, поэтому, пожалуйста, игнорируйте бессмысленный пример):
interface Images {
[key:string]: string;
}
function getMainImageUrl(images: Images): string {
return images.main;
}
Я получаю сообщение об ошибке (используя TypeScript 1.7.5):
ошибка TS2339: свойство 'main' не существует для типа 'Images'.
Конечно, я мог бы избавиться от ошибки при написании:
return images["main"];
Я бы предпочел не использовать строку для доступа к свойству. Что я могу сделать?
8 ответов
Если вы хотите иметь доступ images.main
тогда вы должны определить это явно:
interface Images {
main: string;
[key:string]: string;
}
function getMainImageUrl(images: Images): string {
return images.main;
}
Вы не можете получить доступ к индексированным свойствам, используя точечную нотацию, потому что машинопись не имеет возможности узнать, есть ли у объекта это свойство.
Однако, когда вы конкретно определяете свойство, тогда компилятор знает, что оно есть (или нет), независимо от того, является ли оно необязательным и каков его тип.
редактировать
Вы можете иметь вспомогательный класс для экземпляров карты, например:
class Map<T> {
private items: { [key: string]: T };
public constructor() {
this.items = Object.create(null);
}
public set(key: string, value: T): void {
this.items[key] = value;
}
public get(key: string): T {
return this.items[key];
}
public remove(key: string): T {
let value = this.get(key);
delete this.items[key];
return value;
}
}
function getMainImageUrl(images: Map<string>): string {
return images.get("main");
}
У меня что-то подобное реализовано, и я считаю это очень полезным.
Правильное решение - добавить свойство в определение типа, как объяснено в ответе @Nitzan Tomer. Если это не вариант, хотя:
(Hacky) Обходной путь 1
Вы можете присвоить объекту константу типа any, а затем вызвать свойство "несуществующий".
const newObj: any = oldObj;
return newObj.someProperty;
Вы также можете разыграть его как any
:
return (oldObj as any).someProperty;
Это не может обеспечить безопасность типов, хотя это и есть смысл TypeScript.
(Hacky) Обходной путь 2
Еще одна вещь, которую вы можете рассмотреть, если вы не можете изменить исходный тип, это расширение типа следующим образом:
interface NewType extends OldType {
someProperty: string;
}
Теперь вы можете привести свою переменную как это NewType
вместо any
, Все еще не идеал, но менее разрешительный, чем any
, давая вам больше типа безопасности.
return (oldObj as NewType).someProperty;
Предположительно менее хакерское решение:
if ("property" in obj) {
console.log(`Can access ${obj.property}`)
}
Я не эксперт в Typescript, но я думаю, что главная проблема - это доступ к данным. Видя, как вы описали свой Images
Интерфейс, вы можете определить любой ключ в виде строки.
При доступе к свойству, синтаксис "точка" (images.main
Я полагаю, что он уже существует. У меня были такие проблемы без Typescript, в "ванильном" Javascript, где я пытался получить доступ к данным как:
return json.property[0].index
где индекс был переменной. Но это интерпретируется index
, в результате чего:
cannot find property "index" of json.property[0]
И мне пришлось найти обходной путь, используя ваш синтаксис:
return json.property[0][index]
Это может быть ваш единственный вариант там. Но, опять же, я не эксперт Typescript, если кто-то знает лучшее решение / объяснение того, что происходит, не стесняйтесь меня поправлять.
Начиная с TypeScript 2.2 использование точечной нотации для доступа к индексированным свойствам разрешено. Вы не получите ошибку TS2339 на вашем примере.
См. Пунктирное свойство для типов со строковыми индексными сигнатурами в примечании к выпуску TypeScript 2.2.
Правильное исправление - добавить свойство в определение типа, как объяснил @Nitzan Tomer. Но также вы можете просто определить свойство какany
, если вы хотите писать код почти как в JavaScript:
arr.filter((item:any) => {
return item.isSelected == true;
}
Я получал эту ошибку на Vue 3. Это было потому, что
defineComponent
должны быть импортированы так:
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: String,
},
created() {
this.testF();
},
methods: {
testF() {
console.log("testF");
},
},
});
</script>
Что мне помогло, так это сделать переменную общедоступной, в этом примере:
interface Images {
public[key:string]: string;
}
function getMainImageUrl(images: Images): string {
return images.main;
}