Псевдоним TypeScript не «запоминает» неиспользуемые параметры универсального типа или тип с псевдонимом

Прошу простить мою многословность в вопросе:

Библиотека, которую я использую (которая является абстракцией redux), называемая очень просто, предоставляет следующие типы для использования в сочетании друг с другом. Тип Thunk имеет несколько неиспользуемых общих параметров, которые, кажется, существуют только для того, чтобы обеспечить вывод для жанровых параметров функции thunk (которые точно соответствуют параметрам типа Thunk):

export type Thunk<
  Model extends object, // not used
  Payload = undefined,
  Injections = any, // not used
  StoreModel extends object = {}, // not used
  Result = any
> = {
  type: 'thunk';
  payload: Payload;
  result: Result;
};

export function thunk<
  Model extends object = {},
  Payload = undefined,
  Injections = any,
  StoreModel extends object = {},
  Result = any
>(
  thunk: (
    actions: Actions<Model>,
    payload: Payload,
    helpers: Helpers<Model, StoreModel, Injections>,
  ) => Result,
): Thunk<Model, Payload, Injections, StoreModel, Result>;

Рекомендуемое использование будет примерно таким:

type Injections = {
  someFunc: () => "foo";
}

const someThunk: Thunk<LocalModel, string, Injections, StoreModel, Promise<void>> = thunk(
  (actions, payload, { injections, getState, getStoreState }) => {
     // do something
     const state = getState(); // returns local state
     const storeState = getStoreState(); // returns global / store state
     const foo = injections.someFunc(); // foo
  }
);

Однако, если вы попытаетесь создать псевдоним типа Thunk, чтобы иметь менее подробное определение, все общие параметры, которые не «используются» самим типом Thunk (Model, StoreModel и Injections), кажется, получают потеряны, а действия, инъекции, getState (зависит от Model типа) и getStoreState (зависит от StoreModel типа) больше не печатаются (они становятся any).

type LocalThunk<TPayload, TResult> = Thunk<
  LocalModel,
  TPayload,
  Injections,
  StoreModel,
  TResult
>;

const someThunk: LocalThunk<string, Promise<void>> = thunk(
  (actions, payload, { injections, getState, getStoreState }) => {
     // do something
     const state = getState(); // any
     const storeState = getStoreState(); // any
     const foo = injections.someFunc(); // any
  }
);

Лучшее, что я могу понять, это то, что псевдоним не «запоминает» типы, которые на самом деле не используются в типе Thunk.

У меня есть несколько обходных путей, поэтому я не ищу их здесь. Что меня интересует, так это то, может ли кто-нибудь предоставить более обоснованную причину того, почему это так, и следует ли это считать ошибкой, и, возможно, github TypeScript является лучшим местом для поднятия этой проблемы.

Вот небольшая репродукция, чтобы вы могли увидеть это в действии: https://codesandbox.io/s/shy-worker-qpi5lr?file=/src/store.tsx

Любая информация или документация, подтверждающая, почему это не работает, будут полезны!

Система типов TypeScript — структурный, а не номинальный. Если два типа имеют одинаковую структуру, то они являются одним и тем же типом. Такой тип, как type Foo<T> = {}, не имеет структурной зависимости от T, и поэтому T нельзя надежно вывести из него (например, сделать вывод, какая переменная была передана функции x => 0 из вывода.

jcalz 05.04.2022 22:56

См. эта запись часто задаваемых вопросов и эта запись часто задаваемых вопросов что-то вроде канонического обсуждения этого. Если это касается вашего вопроса, я могу написать ответ; если нет, то что мне не хватает?

jcalz 05.04.2022 22:57

Да, это похоже на очень похожую проблему. Спасибо за ссылки, я проверю их. Так что в основном это то, о чем я думал, поскольку тип не «использует» или не имеет структурной зависимости от этих типов, он не может использовать их для вывода, поскольку псевдоним не фиксирует и не «запоминает» их.

no_stack_dub_sack 06.04.2022 00:45

Я прочитал эти часто задаваемые вопросы и думаю, что они довольно хорошо отвечают на вопрос и наиболее прямо отвечают на вопрос, почему TS не может делать выводы, используя эти типы.

no_stack_dub_sack 06.04.2022 01:13
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
4
38
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Это связано с тем, что TS может вывести дженерики из возвращаемого значения следующим образом:

export function thunk<
  Model extends object = {},
  Payload = undefined,
  Injections = any,
  StoreModel extends object = {},
  Result = any
>(
  thunk: (
    actions: Actions<Model>, //Applies generic to here
    payload: Payload,
    helpers: Helpers<Model, StoreModel, Injections>, //and here
  ) => Result,
): Thunk<Model, Payload, Injections, StoreModel, Result>; //Infers the generic from the return value

Но не может этого сделать, когда вы его инкапсулируете (потому что, как указал jcalz, TS не основан номинально).

export function thunk<...>(
  thunk: (
    actions: Actions<Model>, //How do I infer this?
    payload: Payload,
    helpers: Helpers<Model, StoreModel, Injections>, //Kaboom!
  ) => Result,
): ThunkAlias<...>; //Uh oh, there is no Model value!

Вам придется создать функцию-оболочку в качестве обходного пути. К сожалению, поскольку easy-peasy не экспортирует все типы утилит для создания преобразователь, это невозможно.

В частности, эта строка: helpers: Helpers<Model, StoreModel, Injections> — это то, что вызовет большую часть головной боли при попытке сделать это. Потому что они не экспортируют тип Helpers.

Для TS 4.7 выпущена функция, позволяющая получать параметры типа из типизированных функций. https://github.com/microsoft/TypeScript/pull/47607. Мы можем использовать этот шаблон, чтобы получить параметры thunk. Таким образом, мы можем сделать это, как только он будет выпущен, или если вы согласны с запуском разрабатываемой версии TS в качестве зависимости. Установить с помощью npm/yarn install/add typescript@next

type ThunkAlias<TPayload = never, TReturn = any> = Thunk<
  StoreModel,
  TPayload,
  Deps,
  never,
  TReturn
>

export function thunkAlias<
  Model extends object = StoreModel,
  Payload = undefined,
  Injections = Deps,
  SModel extends object = {},
  Result = any
>(
  args: Parameters<typeof thunk<Model, Payload, Injections, SModel, Result>>[0],
): ThunkAlias<Payload, Result> {
  return thunk<Model, Payload, Injections, SModel, Result>(args)
}

Вы можете просмотреть демо-версию, которая эмулирует это здесь: ТС игровая площадка

Обходной путь — просто скопировать тип Helpers и вместо этого переопределить функцию-оболочку на основе этого. (и импортировать любые другие типы, которые ему нужны)

// Yoinked from easy-peasy 
type Helpers<Model extends object, StoreModel extends object, Injections> = {
  dispatch: Dispatch<StoreModel>;
  fail: AnyFunction;
  getState: () => State<Model>;
  getStoreActions: () => Actions<StoreModel>;
  getStoreState: () => State<StoreModel>;
  injections: Injections;
  meta: Meta;
};

export function thunkAlias<
  Model extends object = StoreModel,
  Payload = undefined,
  Injections = Deps,
  SModel extends object = {},
  Result = any
>(
  _thunk: (
    actions: Actions<Model>,
    payload: Payload,
    helpers: Helpers<Model, SModel, Injections>,
  ) => Result,
): ThunkAlias<Payload, Result> {
  return thunk<Model, Payload, Injections, SModel, Result>(args)
}

P.S. но в какой-то момент вы должны спросить себя, действительно ли вся эта работа^ стоит того, чтобы поменьше многословия в другом месте? Зависит от того, кого вы спросите, я полагаю...

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

no_stack_dub_sack 06.04.2022 00:46

И да, я согласен, что в какой-то момент вся дополнительная работа того не стоит, хотя меня устраивает текущий обходной путь, который у меня есть, и теперь я понимаю, что, объединив псевдоним, который я создал, с псевдонимом для типа Helpers , он завершает вывод и позволяет выводить даже такие параметры, как actions, хотя псевдоним Helpers применяется только к helpers (третьему параметру).

no_stack_dub_sack 06.04.2022 00:48

В основном меня интересовало, почему это не работает, и приведенные здесь ответы/комментарии полезны. Также интересует грядущая функция 4.7.

no_stack_dub_sack 06.04.2022 00:49

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