Ошибка 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;
}
Другие вопросы по тегам