Почему доступ к отсутствующему получателю в Typescript возвращает «неопределенный», а не вызывает ошибку компиляции?

#typescript #getter-setter

Вопрос:

При написании задатчика свойств в Typescript не требуется одновременно включать получатель:

 set name(name: string)
{
    ... 
}
 

Однако при доступе к соответствующему получателю свойств через this.name (который не определен) это не ошибка времени компиляции и возвращает значение undefined . Я не могу понять, почему, и я также не могу найти никаких обсуждений по этому поводу.

 const theName = this.name;   // undefined at runtime, but typed as 'string'
 

Я, скорее всего, окажусь в этой ситуации случайно, и это может отнять много времени в том редком случае, когда я не сразу замечаю.

«Традиционный» способ определения свойства таков, так что, возможно, это объясняет, почему было принято решение.

 Object.defineProperty(Dog.prototype, "name", {
    set: function (name) {
    },
    enumerable: false,
    configurable: true
});
 

Конечно, я принимаю желаемое за действительное, но я бы предпочел, чтобы поведение заключалось в том, чтобы вернуть исходное «сырое» значение, переданное — это было бы удивительно. И избегайте этого ужасного беспорядка :-/

 set name(name: string)
{
   this._name = name;
   doNameStuff(name);
}
get name() 
{
   return this._name;
}
private _name!: string;
 

Комментарии:

1. Поскольку TypeScript не имеет понятия writeonly свойств (см. microsoft/TypeScript#21759 и microsoft/TypeScript#30852 ), в настоящее время он также не позволяет утверждать, что поведение получения и установки свойства не связано по типу (см. microsoft/TypeScript#43362 ). Это в основном отсутствующая функция и, по-видимому, не имеет высокого приоритета, потому что типы только для установщиков, похоже, не очень распространены.

2. @jcalz Я наткнулся на дискуссии о разных типах, но это просто похоже на отдельную ошибку во время компиляции. Почему бы не обеспечить, чтобы и то, и другое было определено? Я полагаю, они фактически говорят, что если вам действительно нужно свойство только для записи, ваш геттер автоматически нарушается — и если вы забудете или сделаете опечатку, вы сами по себе!

3. github.com/microsoft/TypeScript/issues/… имеет значение

4. @jcalz Вы можете понять, почему я не нашел этот выпуск по названию! Я тоже проверю правило линтера.

5. Полагаю, я напишу ответ, указывающий на это, если это покажется разумным.

Ответ №1:

Да, это дыра в надежности машинописного текста. Компилятор с радостью позволит вам написать set средство доступа к свойству без соответствующего get средства доступа, но он не очень хорошо моделирует результирующее поведение свойства. Концептуально свойство с задатчиком, но без задатчика, должно быть « writeonly » таким же образом, как и свойство с задатчиком, но без readonly задатчика . Действительно, если вы пишете геттер, но не сеттер, компилятор делает вывод, что свойство readonly :

 const foo = {
    get bar() { return 1 }
}
/* const foo: {
    readonly bar: number;
} */
 

Но в настоящее время нет такого понятия, как writeonly в TypeScript, и компилятор вместо этого моделирует такое свойство как обычное свойство чтения-записи:

 const baz = {
    set qux(x: number) { }
}
/* const baz: {
    qux: number;
} */
 

Существует давняя открытая проблема с запросом writeonly свойств в microsoft/TypeScript#21759, но неясно, будет ли она когда-либо решена.

В TypeScript 4.3 появилась некоторая поддержка вариантов доступа, которая позволяет системе типов моделировать различные типы геттеров и сеттеров. Но в настоящее время существует требование, чтобы тип геттера можно было назначить типу сеттера. Вы не можете выразить, что сеттер принимает (скажем) number , но получатель всегда производит undefined . Итак, в microsoft/TypeScript#43662 есть еще одна открытая проблема, требующая наличия несвязанных типов в установщике и получателе. Если бы это было реализовано, это был бы еще один способ моделирования сеттеров без геттера; считывание свойства, как известно, всегда приводило undefined бы к результату . Опять же, однако, неясно, будет ли это реализовано.

И были некоторые предложения просто сделать так, чтобы сеттеры без геттера были ошибкой компилятора, например microsoft/TypeScript#30852, но, похоже, это будет отклонено, потому что это будет критическое изменение для существующего кода TypeScript… хотя, на мой взгляд, это приведет только к нарушению кода, который уже делает странные вещи. Но я не главный.

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

Ссылка на игровую площадку для кода

Комментарии:

1. Я думаю, что с 30852 это, вероятно, существующий код, который наверняка нарушит сделку. Но (как вы подразумевали), если вы не попытаетесь прочитать значение, то это не ошибка. Поэтому ошибка должна была возникнуть только в том случае, если вы одновременно «забыли» свой геттер И попытались его использовать. Мое любимое решение: автоматический «неявный» геттер, который просто возвращает ваше исходное «исходное» значение, не произойдет 🙁 но если бы это когда-либо произошло, это, вероятно, сделало бы многих людей очень счастливыми.

2. Я думаю, что если вы хотите увидеть такие неявные средства получения, вам следует запросить их у JavaScript, а затем TypeScript в конечном итоге получит их. TS в значительной степени выходит из игры по внедрению новых функций среды выполнения… они обожглись enum и больше не собираются этого делать.