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 类型未“使用”的所有通用参数本身(ModelStoreModelInjections)似乎迷路了,而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。但在某些时候,你必须问问自己,所有这些工作^,是否真的值得,因为其他地方不那么冗长?我想取决于你问的是谁...