Могу ли я написать интерфейс TypeScript, описывающий объект, который не наследуется от Object?

Это всего лишь гипотетический вопрос, т.е. он не представляет собой реальной проблемы. Это возникло в результате написания статьи о том, как классы работают в JavaScript, в которой были описаны некоторые странные последствия их внутреннего устройства, что заставило меня задуматься: «Интересно, можно ли это странное использование JavaScript выразить в TypeScript?»

Когда я создаю класс в JavaScript, по умолчанию он «наследуется» Object. Т.е. прототипом его прототипа будет прототип Object.

Таким образом, TypeScript предполагает, что все они присутствуют в объекте.

class Foo {}

function x(y: Foo) {
  // Valid. `toString` doesn't exist on Foo, it exist on `Object`
  return y.toString();
}

Object сам по себе ни от чего не наследуется, поэтому прототипом является null.

> Object.getPrototypeOf(Object.prototype)
null

Но цепочка прототипов изменяема, и именно так мы имитировали наследование до того, как было введено ключевое слово class, хотя это было для вставки базового класса; не удаляя ни одного. Но я мог бы порвать с Object, установив для прототипа прототипа значение null.

> const f = new Foo()
undefined
> f.toString()
'[object Object]'
> Object.setPrototypeOf(Foo.prototype, null)
[Object: null prototype] {}
> f.toString()
Uncaught TypeError: f.toString is not a function

Теперь у Foo нет базового класса. Однако интерфейс TypeScript включает в себя все свойства Object.

Могу ли я правильно смоделировать это поведение?

п.с. Если вы читаете это и думаете: «Эй, я мог бы сделать это за…», не делайте этого. Просто не надо.

Это ms/TS#1108. TS никогда не намеревался моделировать такое поведение (по-видимому, оно достаточно редко встречается в реальном коде, чтобы не стать большой проблемой для разработчиков TS). Есть неуклюжие обходные пути, но по сути это недостающая функция. Это полностью решает вопрос? Если да, то напишу ответ или найду подходящий источник дублирования. Если нет, то чего мне не хватает?

jcalz 20.07.2024 13:52

@jcalz Сценарий немного другой, но проблема по сути та же, поэтому я бы сказал: да, это отвечает на мой вопрос; этот тип поведения не поддерживается ;) Чего я тоже ожидал.

Pete 21.07.2024 20:27
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
2
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

См. microsoft/TypeScript#1108.

TypeScript не может моделировать такие типы. TypeScript действительно предполагает, что все типы объектов (включая автоматически упакованные примитивы ) наследуются от Object. Это предположение, хотя и неверно с технической точки зрения, на практике работает достаточно хорошо, поэтому TypeScript не получает много сообщений об ошибках. В microsoft/TypeScript#1108 есть запрос на поддержку null объектов-прототипов, но он был открыт уже давно и получил всего несколько десятков голосов, так что, похоже, на него нет большого спроса.

Как упоминалось в обсуждении, самое близкое к удалению методов Object.prototype из типа объекта в TypeScript — это сузить их до никогда:

type NotObject = Record<keyof Object, never>

function makeNotObject<T extends object>(obj: T) {
    const ret = Object.create(null!);
    Object.assign(ret, obj);
    return ret as T & NotObject;
}

const o = makeNotObject({ a: 1, b: 2, c: 3 });
console.info(o.a.toFixed(2)) // "1.00"
o.toString(); // error!
//~~~~~~~~ <-- This expression is not callable. 

Но TypeScript по-прежнему рассматривает это как подтип Object, а это противоположно тому, о чем вы говорите.


Обратите внимание: даже если бы типы объектов null-прототипа поддерживались, конкретный пример, который вы показываете, мог бы не поддерживаться, поскольку TypeScript также не моделирует мутации типа. Вы можете сузить тип существующего объекта, но не изменять его произвольно. Итак, поведение Object.setPrototypeOf() на входе невозможно представить.

В microsoft/TypeScript#22865 есть запрос на функцию, позволяющую разрешить такие вещи, но эта функция, похоже, пользуется еще меньшей поддержкой сообщества. Для прототипов гораздо лучше просто Object.create() объект с желаемым прототипом.

Детская площадка, ссылка на код

Другие вопросы по теме