У меня проблемы с пониманием взаимодействия универсальных типов TypeScript со значениями по умолчанию и того, как максимально ограничить служебные функции. (Этот пример будет надуманным, но он позволяет легко показать проблему в небольшом количестве кода.)
При моделировании простого случая, когда у вас есть «редактор» данного «значения», вы можете выразить это так:
type Editor<V extends object = object> = {
value: V
set: (value: V) => void
}
Редактор является универсальным, так что вы можете дополнительно уточнить тип значения. И я добавил значение по умолчанию к универсальному аргументу, чтобы вы могли легко опустить его в случаях, когда вам не нужно делать его более строгим.
Затем у вас есть внешняя вспомогательная функция, написанная как общая для работы с любым редактором (упрощенно):
const get = <E extends Editor>(editor: E): E['value'] => {
return editor.value
}
Но затем, пытаясь написать фабрику для этих редакторов, я столкнулся с ошибкой TypeScript:
const create = <V extends object = object>(value: V): Editor<V> => {
const editor: Editor<V> = {
value,
set: (value: V) => {
const prev = get(editor)
// ...
}
}
return editor
}
Ошибка:
Argument of type 'Editor<V>' is not assignable to parameter of type 'Editor<object>'.
Types of property 'set' are incompatible.
Type '(value: V) => void' is not assignable to type '(value: object) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'object' is not assignable to type 'V'.
'object' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint 'object'.(2345)
Почему так происходит? Это сбивает с толку, потому что get кажется максимально мягким, поэтому я предполагаю, что он охватывает все подтипы, но все же TypeScript жалуется, что типы могут иметь несоответствие.






Проблема исходной игровой площадки решена в эта площадка, но я не знаю, удалил ли я функцию, которую вы изучали.
Этот код компилируется, и я думаю, он станет основой для чего-то ...
type Editor<V extends object> = {
value: V;
set: (value: V) => void;
};
const get = <V extends object>(editor: Editor<V>): V => {
return editor.value;
};
const create = <V extends object>(value: V): Editor<V> => {
const editor: Editor<V> = {
value,
set: (value: V) => {
const prev = get(editor);
// ...
},
};
return editor;
};
Чтобы проиллюстрировать проблему, вы можете воссоздать исходную ошибку из моего рабочего примера, следуя этим пошаговым изменениям на игровой площадке.
Сначала измените сигнатуру get на эту, которая вводит потенциально сужающийся тип E ...
const get = <V extends object, E extends Editor<V>>(editor: E): V => {
return editor.value;
};
Затем исправьте это, убедившись, что тип сужения фактически совпадает с типом экземпляра редактора ...
const prev = get<V,typeof editor>(editor);
... что поясняет, что существовал еще один свободный тип, который нужно было закрепить, по сравнению с моим рабочим кодом, в котором его не было.
Относительно открытых вопросов о взаимодействии между Generics и Defaults, и вы не единственный. У меня есть кое-какие эвристики и уроки ...
A) Если тип является частью композиции аргумента, всегда включайте тип в определения функции, которые включают этот аргумент, устраняйте значения по умолчанию везде и никогда не используйте по умолчанию any.
Б) На практике я обнаружил, что Typescript почти всегда в любом случае выводит типы из аргумента, но только если они были сохранены, следуя пункту A), так что они присутствуют в пути кода для проверки Typescript.
Это, вероятно, объясняет, почему мое первое вмешательство в ваш код было удалением значений по умолчанию, второе - обеспечение того, чтобы любая ссылка на Editor всегда сопровождалась Generic V, который можно было бы вывести из него.
О и C) если тип не нуждается в псевдониме (и потенциально сужен), не добавляйте сужающий «псевдоним», так как вы можете создать псевдоним, который будет конфликтовать позже.
Так, например, если V - это все, что необходимо для определения сигнатуры функции, проходящей через редактор, тогда не создавайте псевдоним E extends Editor<V> или, что еще хуже, E extends Editor, чтобы более поздние подписи могли неожиданно ввести то, что составляет псевдоним E2 extends Editor<V2>, который может или может не то же самое.
Спасибо за возможность проверить код и даже больше за рекомендации, о которых следует подумать при использовании общих / стандартных значений. Это было невероятно полезно!