【问题标题】:Typescript inference of sibling function's generic arguments兄弟函数的通用参数的打字稿推断
【发布时间】:2020-12-19 22:23:18
【问题描述】:

我想实现以下推理:

type RequestAction<Data = any, TransformedData = Data> = {
  type: string;
  request: any | any[];
  meta?: {
    getData?: (data: Data, currentData: TransformedData) => TransformedData;
    [extraProperty: string]: any;
  };
};

function fetchBooks(
  x: number,
  y: string,
): RequestAction<{ raw: boolean }, { parsed: boolean }> {
  return {
    type: 'FETCH_BOOKS',
    request: {
      url: '/books',
      x,
      y,
    },
    meta: {
      getData: data => ({ parsed: data.raw }),
    },
  };
}

const query = useQuery({
  action: fetchBooks,
  variables: [1, '2'], // inferred from fetchBooks argument types
});
query.data // inferred from `fetchBooks` generic

我只能同时实现一个,而不能同时实现两者。以下是我的尝试:

1)

interface QueryState<QueryStateData> {
  data: QueryStateData;
  error: any;
  loading: boolean;
}

type Arr = readonly any[];

export function useQuery<
  QueryStateData = any,
  Variables extends Arr = any
>(props: {
  action?: (...args: Variables) => RequestAction<any, QueryStateData>;
  variables?: Variables;
}): QueryState<QueryStateData>;

这几乎可以工作,data 是从传递给 fetchBooks: RequestAction 的 QueryStateData 自动推断出来的。变量也部分起作用,但只是部分起作用。它似乎工作正常,例如如果fetchBooks 参数是(string, number),那么variables 的类型将确实是[string, number] 元组。但是由于某种原因,如果我设置了variables: [1, '1'],那么它仍然可以工作,因为突然从 TS 输入的类型将是(string | number)[],将其转换为联合数组,原因我不知道,这是不可接受的,我希望变量准确action 参数,顺序敏感。

2)

interface RequestCreator<QueryStateData> {
  (...args: any[]): RequestAction<any, QueryStateData>;
}

export function useQuery<
  QueryStateData = any,
  R extends RequestCreator<QueryStateData> = RequestCreator<QueryStateData>
>(props: {
  action?: R;
  variables?: Parameters<R>;
}): QueryState<QueryStateData>;

虽然我可以通过这个实现完美的variables 推理。不幸的是,query.data 将是 any 的类型,QueryStateData 在这种情况下由于某种原因推理不起作用,它不是从 action 回调泛型中选择的。

请告诉我哪种方法更正确,以及如何调整它以同时实现variablesdata 推理。或者也许还有另一种我没有考虑过的方法。提前致谢!

【问题讨论】:

  • RequestAction 是什么,fetchBooks 的类型是什么?请提供minimal reproducible example
  • 我编辑了我的帖子以包含那些的简化版本。请注意,RequestAction 是类型而不是接口,因为这是 redux 操作,它也支持带有有效负载的 FSA 操作,因此我在那里使用了 union,但在这里我简化了示例

标签: typescript typescript-typings


【解决方案1】:

尝试对操作使用 single 类型参数,这与您的第二次尝试类似。

interface RequestCreator<QueryStateData = any> {
  (...args: any[]): RequestAction<any, QueryStateData>;
}

// Gets the `QueryStateData` type from a `RequestCreator`
// (e.g. GetQueryStateData<typeof fetchBooks> is { parsed: boolean }).
type GetQueryStateData<T extends RequestCreator> = T extends RequestCreator<infer QueryStateData>
  ? QueryStateData
  : never;

// I'm just using declare here so TS is happy
export declare function useQuery<R extends RequestCreator>(props: {
  action?: R;
  // The parameter types of R
  // (e.g. Parameters<typeof fetchBooks> is [x: number, y: string])
  variables?: Parameters<R>;
}): QueryState<GetQueryStateData<R>>;

const query = useQuery({
  action: fetchBooks,
  variables: [1, '2'],
});
// inferred correctly as { parsed: boolean }
query.data;

useQuery({
  action: fetchBooks,
  // Type 'number' is not assignable to type 'string'. (expected)
  variables: [1, 1],
});

我认为(基于轶事经验;请不要引用我的话)当 TypeScript 直接对应于参数类型(或直接“包含”在其中的类型,例如 action 的类型时)更好地推断类型参数)。这就是为什么编译器在您的第二次尝试中无法推断 QueryStateData 的类型。

Playground link

【讨论】:

  • 这简直太棒了,谢谢!我还有一个问题要解决,我想在 useQuery 中保留 QueryStateData 泛型,因为实际上情况更复杂,这一切都是针对使用 Redux 动作创建器库的人而无需使用常量的。然后有时他们不会通过行动。这是useQuery 的实际接口 - export function useQuery&lt;R extends RequestCreator = any&gt;(props: { type: string | R; action?: R; variables?: Parameters&lt;R&gt;; }): QueryState&lt;GetQueryStateData&lt;R&gt;&gt;; 注意type 也可以是字符串
  • 因此,如果有人使用动作创建器库,type 就足够了,这将起作用,推理将适用于其中的数据。但是,如果有人不这样做,则需要通过type: string。然后,在某些情况下可以将action 传递给此处(实际上它是可选的),然后它又会起作用。但通常一个人只能通过type: string,然后这样做会很酷,例如const query = useQuery&lt;number&gt;({ type: 'FETCH_NUMBER' }),然后data 将是number 的类型,而不是any。我试图更新您的示例但没有成功。还不流利使用infer 和条件;)
  • 我可能用一个额外的条件类型解决了这个问题,我会尽快发布你的解决方案和这个小调整!
【解决方案2】:

这是最终类型,我稍微更新了樱花响应以仍然允许将自定义泛型传递给 useQuery:

interface RequestCreator<QueryStateData = any> {
  (...args: any[]): RequestAction<any, QueryStateData>;
}

type GetQueryStateData<T extends RequestCreator> = T extends RequestCreator<
  infer QueryStateData
>
  ? QueryStateData
  : never;

export function useQuery<
  Data = undefined,
  QueryCreator extends RequestCreator = any
>(props: {
  type: string | QueryCreator;
  action?: QueryCreator;
  requestKey?: string;
  multiple?: boolean;
  defaultData?: any;
  dispatch?: boolean;
  variables?: Parameters<QueryCreator>;
}): QueryState<
  Data extends undefined ? GetQueryStateData<QueryCreator> : Data
> & {
  load: () => Promise<{
    data?: QueryState<
      Data extends undefined ? GetQueryStateData<QueryCreator> : Data
    >;
    error?: null;
    isAborted?: true;
    action: any;
  }>;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-30
    • 2018-11-01
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-01
    • 2020-03-22
    相关资源
    最近更新 更多