Почему key in object недостаточно, чтобы успокоить TS?

Вопрос или проблема

type LanguageKey =
    | 'c++'
    | 'python'
    | 'javascript'
    | 'typescript';

class Test {
    languages: Partial<Record<LanguageKey, string>> = {};
    
    needsString(str: string): void { console.log(str);}

    useLangKey(langKey: LanguageKey): void {
        if (langKey in this.languages) {
            const language = this.languages[langKey];
            this.needsString(language); // <-- "'string | undefined' не может быть присвоен 'string'" ??
        }
    }
}

TS жалуется на отмеченной строке, что “Аргумент типа ‘string | undefined’ не может быть присвоен параметру типа ‘string’.”. Разве проверка langKey in this.languages не должна быть достаточной?

Ссылка на TS playground

Рассмотрим:

const foo = {
  bar: undefined
}

console.log('bar' in foo); // равно 'true'

Поскольку вы использовали Partial, вы сделали undefined допустимым значением для свойств в этом объекте.

Проверка, которая вам нужна:

if (this.languages[langKey] !== undefined) {

🤔

Как вы гарантируете, что запись существует? Undefined не входит ни в String, ни в LanguageKey, и я думаю, что именно это вызывает жалобу.

Вы можете исправить это, добавив проверку на пустое значение

...
const language = this.languages[langKey];
if(!language) throw Error();
this.needsString(language);
...

Ответ или решение

Вопрос, с которым вы столкнулись, требует внимательного рассмотрения особенностей работы TypeScript и механизма, который стоит за проверкой наличия ключей в объектах. Давайте разберемся, почему конструкция key in object не достаточно, чтобы удовлетворить TypeScript при работе с типом, который может принимать значение undefined.

Введение в проблему

Вам представлена классическая ситуация, когда вы используете тип Partial<Record<LanguageKey, string>> для определения свойств объекта, который может содержать не все ключи и допускает undefined в качестве значений. Данная конструкция позволяет заявить, что свойства объекта могут отсутствовать, но также подразумевает, что при этом элементы, которые существуют, могут иметь значение string или undefined.

Почему key in object недостаточно

  1. Проверка на существование ключа: Когда вы используете if (langKey in this.languages), TypeScript успешно определяет, что ключ действительно существует в объекте. Однако TypeScript не может гарантировать, что значение, соответствующее данному ключу, не будет undefined. Это связано с тем, что вы используете Partial, что делает возможным существование свойств с неопределёнными значениями.

  2. Тип значения переменной: После проверки ключа вы выполняете const language = this.languages[langKey];. На этом этапе language может иметь тип string | undefined, так как значением свойства, которое вы извлекаете, вполне может быть undefined. Когда вы пытаетесь передать language в метод needsString, TypeScript не позволяет это сделать, так как метод ожидает аргумент типа string, и undefined не подходит для этого параметра.

Способы решения проблемы

Чтобы справиться с этой проблемой, есть несколько подходов:

  1. Проверка на undefined: Вы можете добавить дополнительную проверку на undefined перед передачей переменной в метод needsString. Например:

    const language = this.languages[langKey];
    if (language !== undefined) {
       this.needsString(language);
    } else {
       // Обработка случая, когда язык отсутствует
    }
  2. Использование нулевых значений через оператор опциональной цепочки: Вы можете использовать оператор опциональной цепочки, чтобы избежать передачи undefined значений:

    this.needsString(this.languages[langKey] ?? "default value");

Заключение

Таким образом, конструкция key in object не может сама по себе гарантировать, что значение свойства не окажется undefined. Поэтому важно не забывать о дополнительных проверках, чтобы обеспечить строгую типизацию и избежать возможных ошибок во время выполнения программы. Это ключевой момент, которымTypeScript стремится поддерживать качество кода, делая его более устойчивым к ошибкам.

Оцените материал
Добавить комментарий

Капча загружается...