TypeScript 别名不 "remember" 未使用的泛型类型参数或别名类型
TypeScript alias does not "remember" unused generic type params or aliased type
请原谅我问这个问题的冗长:
我正在使用的一个库(它是 redux 的抽象),称为 easy-peasy,公开了以下类型以相互结合使用。 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
)似乎迷路了,而actions、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
类型中实际未使用的类型。
我有一些解决方法,所以我并不是真的要在这里寻找。我感兴趣的是,是否有人可以提供更充分的理由来说明为什么会这样,是否应将其视为错误,以及 TypeScript github 是否是提出此问题的更好地方。
这是一个小重现,您可以看到它的实际效果:https://codesandbox.io/s/shy-worker-qpi5lr?file=/src/store.tsx
任何支持为什么这不起作用的信息或文档都会有所帮助!
这是因为 TS 可以像这样从 return 值推断泛型,
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
不会导出所有实用程序类型来创建 thunk,因此这是不可能的
具体来说,这一行:helpers: Helpers<Model, StoreModel, Injections>
is what will cause most of the headache in trying to do this. Because they don't export the 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)
}
您可以在此处查看模拟此功能的演示版本:
TS Playground
解决方法是简单地复制 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。但在某些时候,你必须问问自己,所有这些工作^,是否真的值得,因为其他地方不那么冗长?我想取决于你问的是谁...
请原谅我问这个问题的冗长:
我正在使用的一个库(它是 redux 的抽象),称为 easy-peasy,公开了以下类型以相互结合使用。 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
)似乎迷路了,而actions、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
类型中实际未使用的类型。
我有一些解决方法,所以我并不是真的要在这里寻找。我感兴趣的是,是否有人可以提供更充分的理由来说明为什么会这样,是否应将其视为错误,以及 TypeScript github 是否是提出此问题的更好地方。
这是一个小重现,您可以看到它的实际效果:https://codesandbox.io/s/shy-worker-qpi5lr?file=/src/store.tsx
任何支持为什么这不起作用的信息或文档都会有所帮助!
这是因为 TS 可以像这样从 return 值推断泛型,
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
不会导出所有实用程序类型来创建 thunk,因此这是不可能的
具体来说,这一行:helpers: Helpers<Model, StoreModel, Injections>
is what will cause most of the headache in trying to do this. Because they don't export the 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)
}
您可以在此处查看模拟此功能的演示版本: TS Playground
解决方法是简单地复制 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。但在某些时候,你必须问问自己,所有这些工作^,是否真的值得,因为其他地方不那么冗长?我想取决于你问的是谁...