Как написать общие вспомогательные функции для универсального типа в TypeScript?

У меня проблемы с пониманием взаимодействия универсальных типов 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 жалуется, что типы могут иметь несоответствие.

Вот Ссылка на TypeScript Playground.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
0
27
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Проблема исходной игровой площадки решена в эта площадка, но я не знаю, удалил ли я функцию, которую вы изучали.

Этот код компилируется, и я думаю, он станет основой для чего-то ...

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>, который может или может не то же самое.

Спасибо за возможность проверить код и даже больше за рекомендации, о которых следует подумать при использовании общих / стандартных значений. Это было невероятно полезно!

Ian Storm Taylor 05.04.2021 05:20

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