Прошу простить мою многословность в вопросе:
Библиотека, которую я использую (которая является абстракцией 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
Любая информация или документация, подтверждающая, почему это не работает, будут полезны!
См. эта запись часто задаваемых вопросов и эта запись часто задаваемых вопросов что-то вроде канонического обсуждения этого. Если это касается вашего вопроса, я могу написать ответ; если нет, то что мне не хватает?
Да, это похоже на очень похожую проблему. Спасибо за ссылки, я проверю их. Так что в основном это то, о чем я думал, поскольку тип не «использует» или не имеет структурной зависимости от этих типов, он не может использовать их для вывода, поскольку псевдоним не фиксирует и не «запоминает» их.
Я прочитал эти часто задаваемые вопросы и думаю, что они довольно хорошо отвечают на вопрос и наиболее прямо отвечают на вопрос, почему TS не может делать выводы, используя эти типы.
Это связано с тем, что 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
, но, видимо, вы можете. Этот связь показывает, что его можно импортировать, и закомментированный обходной путь, который я сейчас использую.
И да, я согласен, что в какой-то момент вся дополнительная работа того не стоит, хотя меня устраивает текущий обходной путь, который у меня есть, и теперь я понимаю, что, объединив псевдоним, который я создал, с псевдонимом для типа Helpers
, он завершает вывод и позволяет выводить даже такие параметры, как actions
, хотя псевдоним Helpers
применяется только к helpers
(третьему параметру).
В основном меня интересовало, почему это не работает, и приведенные здесь ответы/комментарии полезны. Также интересует грядущая функция 4.7.
Система типов TypeScript — структурный, а не номинальный. Если два типа имеют одинаковую структуру, то они являются одним и тем же типом. Такой тип, как
type Foo<T> = {}
, не имеет структурной зависимости отT
, и поэтомуT
нельзя надежно вывести из него (например, сделать вывод, какая переменная была передана функцииx => 0
из вывода.