typescript - 通过 TypeScript 中的另一个泛型类型保留泛型类型
问题描述
我正在考虑在@testing-library/react
和/或中提交类型声明的补丁(或补丁) @testing-library/dom
。目前,所有关于RenderResult
返回变体的查询HTMLElement
,这让我写了很多代码,比如
const myDiv = wrapper.getByText('some text') as HTMLDivElement;
最简单的解决方案是使查询通用。
export type GetByText = <T extends HTMLElement = HTMLElement>(/* ... */) => T
export const getByText: GetByText
然后我可以这样做:
const myDiv = wrapper.getByText<HTMLDivElement>('some text');
虽然这在TS Playground中有效,但其中的某些@testing-library/react
内容正在阻止通用形式传递给RenderResult
's 方法。
@testing-library/react
这些是和的当前版本的相关片段@testing-library/dom
。
@testing-library/dom
类型/查询.d.ts
export type GetByText = (
container: HTMLElement,
id: Matcher,
options?: SelectorMatcherOptions,
) => HTMLElement
export const getByText: GetByText
类型/get-queries-for-element.d.ts
import * as queries from './queries';
export type BoundFunction<T> = T extends (
attribute: string,
element: HTMLElement,
text: infer P,
options: infer Q,
) => infer R
? (text: P, options?: Q) => R
: T extends (a1: any, text: infer P, options: infer Q, waitForElementOptions: infer W) => infer R
? (text: P, options?: Q, waitForElementOptions?: W) => R
: T extends (a1: any, text: infer P, options: infer Q) => infer R
? (text: P, options?: Q) => R
: never;
export type BoundFunctions<T> = { [P in keyof T]: BoundFunction<T[P]> };
export type Query = (
container: HTMLElement,
...args: any[]
) => Error | Promise<HTMLElement[]> | Promise<HTMLElement> | HTMLElement[] | HTMLElement | null;
export interface Queries {
[T: string]: Query;
}
export function getQueriesForElement<T extends Queries = typeof queries>(
element: HTMLElement,
queriesToBind?: T,
): BoundFunctions<T>;
@testing-library/react
(可能相关也可能不相关)
类型/index.d.ts
export type RenderResult<
Q extends Queries = typeof queries,
Container extends Element | DocumentFragment = HTMLElement
> = {
// ...
} & {[P in keyof Q]: BoundFunction<Q[P]>}
除了我在上面所做的更改(使每个查询的类型通用)之外,我还更改Query
了get-queries-for-element.d.ts
:
export type Query = <T extends HTMLElement>(
container: HTMLElement,
...args: any[]
) => Error | Promise<T[]> | Promise<T> | T[] | T | null;
我几乎可以肯定问题出在BoundFunction
orgetQueriesForElement()
中,因为当我进行这些更改时(我有 37% 的信心是正确的),我收到一个错误:
Type 'typeof import(".../node_modules/@testing-library/dom/types/queries")' does not satisfy the constraint 'Queries'. Property 'getByLabelText' is incompatible with index signature. Type 'GetByText' is not assignable to type 'Query'. Type 'HTMLElement' is not assignable to type 'T | Error | T[] | Promise<T[]> | Promise<T>'. Type 'HTMLElement' is not assignable to type 'T'. 'HTMLElement' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'HTMLElement'. ts(2344)
(因此,其他测试库包)中可能存在另一个问题@testing-library/react
,但修复这是第一步。
我的部分问题是我实际上并不了解BoundFunction
在做什么。我需要做什么才能使 maketypeof queries
满足Queries
约束?
解决方案
我很确定这是 TypeScript 的设计限制,尽管我在 GitHub 中没有找到适用范围狭窄的问题。一般的问题是,TypeScript 用来检查两种类型之间可分配性的启发式方法没有执行真正的统一。有关在 TypeScript 中实现这种统一的可能性的讨论,请参阅microsoft/TypeScript#30134 。
关于您面临的问题,我能想到的最短示例是:
declare const genFn: <T extends 0>() => T;
const widerGenFn: <T extends 0>() => T | 1 = genFn; // error!
// Type '<T extends 0>() => T' is not assignable to type '<T extends 0>() => T | 1'.
// Type '0' is not assignable to type 'T | 1'
该函数genFn()
有一个类型参数T
,该参数被限制为数字文字 type 0
,不接受函数参数,并返回 type 的值T
。该函数widerGenFn()
完全相同,只是它返回一个 type 的值T | 1
。
如果有人在你背后替换,你应该不会注意到widerGenFn
;genFn
如果您调用widerGenFn<ABC>()
,您将期望返回一个类型的值,并且返回一个类型的值ABC | 1
这一事实不会打扰您,因为每个类型的值也是一个类型的值。这意味着 of 的类型应该可以分配给的类型。genFn
ABC
ABC
ABC | 1
genFn
widerGenFn
但事实并非如此。TypeScript 抱怨说Type '<T extends 0>() => T' is not assignable to type '<T extends 0>() => T | 1'
,因为Type '0' is not assignable to type 'T | 1'
. 是什么赋予了?
好吧,当比较像这样的泛型函数时,编译器不使用microsoft/TypeScript#1213中要求的所谓的“高级类型” 。它似乎立即用特定类型替换T
目标类型。因此,对于genFn
,编译器会更改<T extends 0>() => T
并仅替换T
为其约束, 0
。SogenFn
被视为类型() => 0
,不可分配给<T extends 0>() => T | 1
.
并且有一个错误。
这意味着您在尝试使用通用参数默认值时也会出错,因为type X<T extends U = V>
需要V extends U
:
type GenFn = typeof genFn;
type WiderGenFn = typeof widerGenFn
type Foo<T extends WiderGenFn = GenFn> = void; // error!
// ---------------------------> ~~~~~
那么解决方法是什么?我不确定......它可能在很大程度上取决于用例。一种方法是将约束“扩大”到所涉及的两种类型的并集:
type Bar<T extends WiderGenFn | GenFn = GenFn> = void; // okay
(在你的情况下,这看起来像Q extends Queries | typeof queries = typeof queries
)
可能还有其他方法可以避免 TypeScript 缺乏统一性,但看到奇怪的副作用和极端情况出现我不会感到惊讶。
推荐阅读
- javascript - 如何在特定 laravel 刀片视图上扩展 vuejs 应用程序实例
- javascript - jQuery从包含的脚本中最近的按钮数据标签中获取
- rxjs - 当内部的任何一个发出时,发出 combine observable
- iot - 解码 lorawan 数据 gps 追踪器 Moko LW001-BG Thethings network
- php - PHP - 如何获取数组/链表中的所有可能性序列
- json - 如何从 JMeter 的 json 响应中的长 html 内容中提取特定的 *token* 值
- microsoft-graph-api - 无法从 Microsoft graph 获取批量管理器数据
- javascript - 如何更改javascript中每个按键的字体粗细
- java - 从Java中具有不同数据类型的单个方法返回多个变量
- java - java - 用分隔符理解字节数组