我正在寻求使用泛型来强制val1
的类型应该与数组中每个元素的val2
的类型匹配。
interface SameTypeContainer<T> {
val1: T,
val2: T;
}
test([
{
val1: 'string',
val2: 'also string'
},
{
val1: 5,
val2: false // expect to throw error since type is not number
}
]);
function test(_: SameTypeContainer<any>[]) { }
这不会导致错误。我预计这会由于以下原因而引发打字错误:
在传递给测试函数的数组的第二个元素中,val1
是一个数字,val2
是一个字符串。应该使用SameTypeContainer
接口来强制val1
的类型与val2
的类型相匹配。
接下来,我尝试重新定义测试函数以使用泛型:
function test<T>(_: SameTypeContainer<T>[]) { }
现在我收到一个错误,但原因有误。编译器期望val1
的类型为string,val2
的类型为string,因为这是数组中第一个元素的定义方式。
我希望评估数组中的每个元素是否独立满足给定的泛型。
任何帮助将不胜感激!
更新:
感谢您的帮助!我很感激!我开始理解使用扩展,但是无法将其扩展到我的实际用例中:
export type Selector<S, Result> = (state: S) => Result;
export interface SelectorWithValue<S, Result> {
selector: Selector<S, Result>;
value: Result;
}
export interface Config<T, S, Result> {
initialState?: T;
selectorsWithValue?: SelectorWithValue<S, Result>[];
}
export function createStore<T = any, S = any, Result = any>(
config: Config<T, S, Result> = {}
): Store<T, S, Result> {
return new Store(config.initialState, config.selectorsWithValue);
}
export class Store<T, S, Result> {
constructor(
public initialState?: T,
public selectorsWithValue?: SelectorWithValue<S, Result>[]
) {}
}
const selectBooleanFromString: Selector<string, boolean> = (str) => str === 'true';
const selectNumberFromBoolean: Selector<boolean, number> = (bool) => bool ? 1 : 0;
createStore({
selectorsWithValue: [
{ selector: selectBooleanFromString, value: false },
{ selector: selectNumberFromBoolean, value: 'string' } // should error since isn't a number
],
});
所需:对于传递给createStore
函数的数组中的每个元素,selector
的第二种类型应与value
的类型匹配。
Ex:如果selector
属性的类型为Selector<boolean, number>
,则value
属性的类型应该为number
,而与数组其他元素的类型无关。
这是我第一次尝试为上述嵌套用例提供的Typescript游乐场@jcalz:
答案 0 :(得分:0)
Array<SameTypeContainer<any>>
不起作用的原因是,因为实际上任何值都可以分配给any
,所以无论{{1}是什么,{val1: x, val2: y}
的类型都是SameTypeContainer<any>
}}和x
是。
您要查找的类型是一个数组,其中每个元素都是 some y
类型,但没有任何 specific SameTypeContainer<T>
类型。最好用T
之类的existential type来表示,目前TypeScript(其他大多数带有泛型的语言)都不支持本机。 TypeScript(以及大多数其他具有泛型的语言)仅具有 universal 类型:想要一个Array<SameTypeContainer<exists T>>
类型的值的人可以为他们想要的X<T>
指定任何类型,并且提供程序价值必须能够遵守。存在类型则相反:有人想要提供诸如T
之类的类型的值,可以为他们想要的X<exists T>
选择任何特定类型,而该值的接收者只需遵守即可。但是,TypeScript没有存在类型,因此我们还必须做其他事情。
(嗯,它没有 native 存在类型。您可以使用泛型函数并通过回调反转控制来emulate them,但使用起来比我的解决方案还要复杂接下来会提出建议。如果您仍然对存在性感兴趣,可以阅读有关它的链接文章。
我们要做的第二件事是使用泛型类型推断,方法是让T
是一个接受泛型类型test()
并扩展了 {{ 1}},然后验证A
是否与所需约束匹配。这是我们可以做到的一种方法:
Array<SameContainer<any>>
我认为这可以按照您想要的方式工作。这有点复杂,但是我主要是在线解释的。 A
是一个mapped array,它在每个元素上使用conditional type inference将interface SameTypeContainer<T> {
val1: T;
val2: T;
}
// IsSomeSameTypeContainerArray<A> will evaluate to A if it meets your constraint
// (it is an array where each element is a SameTypeContainer<T> for *some* T)
// Otherwise, if you find an element like {val1: T1, val2: T2} for two different
// types T1, and T2, replace that element with the flipped version {val1: T2, val2: T1}
type IsSomeSameTypeContainerArray<
A extends Array<SameTypeContainer<any> >
> = {
[I in keyof A]: A[I] extends { val1: infer T1; val2: infer T2 }
? { val1: T2; val2: T1 }
: never
};
// test() is now generic in A extends Array<SameTypeContainer<any>>
// the union with [any] hints the compiler to infer a tuple type for A
// _ is of type A & IsSomeSameTypeContainerArray<A>.
// So A will be inferred as the type of the passed-in _,
// and then checked against A & IsSomeSameTypeContainerArray<A>.
// If it succeeds, that becomes A & A = A.
// If it fails on some element of type {val1: T1, val2: T2}, that element
// will be restricted to {val1: T1 & T2, val2: T1 & T2} and there will be an error
function test<A extends Array<SameTypeContainer<any>> | [any]>(
_: A & IsSomeSameTypeContainerArray<A>
) {}
test([
{
val1: "string",
val2: "also string"
},
{
val1: 5,
val2: 3
},
{
val1: 3, // error... not number & string!!
val2: "4" // error... not string & number!!
}
]);
转换为IsSomeSameTypeContainerArray<A>
。如果该转换没有改变{val1: T1, val2: T2}
的类型,那么一切都很好。否则,将至少有一个与交换类型的元素不匹配的元素,并且会出现错误。
无论如何,希望能有所帮助;祝好运!
答案 1 :(得分:0)
发生了什么事,打字稿正在尝试最好为您推断类型,因此,它只是将泛型T扩展为字符串的并集|编号布尔值,因为这是数组中的三种可能的类型。
这里应该写些什么?应该从val1推断出来吗? val2?数字还是布尔值?第一个参考?还是最后一个参考?确实没有“正确”的答案
要解决此问题,您可以执行以下操作:..虽然这不是唯一的方法。 “正确的方式”实际上取决于您的程序。
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
interface SameTypeContainer<T> {
val1: T,
val2: T;
}
test([
{
val1: 'string',
val2: 'also string'
},
{
val1: "",
val2: "false" // fine.
}
]);
type PullTypeContainer<T extends SameTypeContainer<unknown>> =T extends SameTypeContainer<infer TEE> ? TEE : never
const test = <T extends SameTypeContainer<any>>(arg: (IsUnion<PullTypeContainer<T>> extends true ? "No unions" : T)[]) => {
}
答案 2 :(得分:0)
自@jcalz提出以来,请进行一些存在性输入! I've already posted this answer,所以我将担任这一CW。其他答案可能更惯用(因此更好)。但从理论上讲它是合理的,因此应该是正确的。
您已经有了参数类型:
interface SameTypeContainer<T> {
val1: T,
val2: T;
}
存在“通用SameTypeContainer
个消费者”,它们具有以下通用量化类型(由其返回类型进行参数化设置)
type SameTypeConsumer<R> = <T>(c: SameTypeContainer<T>) => R
如果您有SameTypeContainer<T>
,但不知道T
是什么,那么唯一可以做的就是将其传递到SameTypeConsumer<R>
中,而不会{ em> care T
是什么,然后取回R
(不依赖于T
)。因此,SameTypeContainer<T>
-未知-T
等效于一个函数,该函数接受不需要关心的任何T
使用者,并自行运行:< / p>
type SameType = <R>(consumer: SameTypeConsumer<R>) => R
// = <R>(consumer: <T>(sameType: SameTypeContainer<T>) => R) => R
最终产品是能够将SameTypeContainer
的类型掩埋在匿名函数的关闭中。因此,我们在数据结构中存储了一个类型和一个值,该值取决于该类型,其类型仅描述了两者之间的关系。那是一对依赖的人。我们完成了!
function sameType<T>(c: SameTypeContainer<T>): SameType {
return <R>(consumer: SameTypeConsumer<R>) => consumer(c)
}
通过“埋入”这样的类型,您可以将所有不同类型的SameTypeContainer
注入一个大的并集类型SameType
中,在您的情况下,可以将其用作数组元素。>
let list: SameType[] = [ sameType({ val1: 'string', val2: 'also string' })
, sameType({ val1: 42, val2: 42 })
, sameType({ val1: {}, val2: {} })
// , sameType({ val1: 1, val2: false }) // error!
]
function test(l: SameType[]): void {
let doc = "<ol>"
for(let s of l) {
// notice the inversion
let match = s(same => same.val1 === same.val2)
doc += "<li>" + (match ? "Matches" : "Doesn't match") + "</li>"
}
doc += "</ol>"
document.write(doc)
}
// it may be favorable to immediately destructure the pair as it comes into scope:
function test(l: SameType[]): void {
let doc = "<ol>"
for (let s0 of l) s0(s => {
// this way, you can wrap the "backwardsness" all the way around your
// code and push it to the edge, avoiding clutter.
let match = s.val1 === s.val2 ? "Matches" : "Doesn't match"
doc += "<li>" + match + "</li>"
})
doc += "</ol>"
document.write(doc)
}
test(list)
- 不匹配
- 匹配
- 不匹配
您可能会发现进一步定义很有用
function onSameType<R>(c: SameTypeConsumer<R>): (s: SameType) => R {
return s => s(c)
}
以便您可以沿“前进”方向应用功能:
function someFunction<T>(c: SameTypeContainer<T>): R
let s: SameType
s(someFunction) // "backwards"
let someFunction2 = onSameType(someFunction)
someFunction2(s) // "forwards"