打字稿泛型,获取数组内容的类型
Posted
技术标签:
【中文标题】打字稿泛型,获取数组内容的类型【英文标题】:Typescript generics, get type of Array contents 【发布时间】:2019-12-21 11:30:51 【问题描述】:我已经构建了一个方法,它将一个数组与传入的另一个数组连接起来。该数组位于一个名为 state
的父对象上,该对象通常在我的方法中键入。
我的方法是这样的:
export const mutateConcat = <T, K extends keyof T>(state: T, key: K, val: T[K]) =>
((state[key] as unknown) as any[]) = ((state[key] as unknown) as any[]).concat(val);
显然这很难看,我想去掉unknown
和any[]
的演员表。我还希望能够确定数组内容的类型。
基本上,我需要确保 T[K] 是一个数组,理想情况下我想将数组内容的类型分配给另一个泛型参数,例如 U
。
我尝试过<T, K extends keyof T, U extends Array<T[K]>>
,但它会将U
分配给数组本身的类型,而不是数组内容的类型。
我问的可能吗? extends keyof
是否可以只过滤为数组类型的键,这些数组内容的类型可以分配给另一个类型参数吗?
提前致谢
编辑:
所以,我设法通过关闭val
变量来获取数组的类型。所以如下:
export const mutateConcat = <T, U, K extends keyof T>(state: T, key: K, val: T[K] & U[], arrayDistinctProperty?: keyof U) =>
// U now contains the array type, and `arrayDistinctProperty` is typed to a property on that array type object
((state[key] as unknown) as any[]) = ((state[key] as unknown) as any[]).concat(val);
但是,这并不能解决强制转换的丑陋问题,因为 typescript 不能确定 T[K]
是一个数组类型。是否可以告诉它只允许我的key
参数成为T
上的数组类型?
【问题讨论】:
【参考方案1】:方法#1
/**
* Push into the existing array.
*/
export const mutateConcat1 = <
TState extends Record<TKey, unknown[]>,
TKey extends string,
>(
state: TState,
key: TKey,
newArray: TState[TKey]
) =>
state[key].push(...newArray);
;
方法#2
as
的演员表可能是不可避免的。如果没有它,我们会收到 Type 'unknown[]' 不能分配给 type 'TState[TKey]'。 这是因为我们告诉编译器该数组是 @987654325 中的 unknown[]
类型@。我尝试infer
数组类型但无法使其工作。考虑到我们确定state[key]
和newArray
属于同一类型,演员表看起来非常安全。如果我知道如何向编译器解释这一点就好了。
/**
* Overwrite the original array with concat.
*/
export const mutateConcat2 = <
TState extends Record<TKey, unknown[]>,
TKey extends string,
>(
state: TState,
key: TKey,
newArray: TState[TKey]
) =>
state[key] = state[key].concat(newArray) as TState[TKey];
;
测试
/**
* Test Cases
*/
const goodState = array1: [1, 2, 3] ;
const badState = array1: new Date() ;
// ok
mutateConcat1(goodState, 'array1', [4, 5, 6]);
mutateConcat2(goodState, 'array1', [4, 5, 6]);
// errors
mutateConcat1(goodState, 'array1', ['4', 5, 6]);
mutateConcat1(goodState, 'array2', [4, 5, 6]);
mutateConcat1(badState, 'array1', [4, 5, 6]);
mutateConcat2(goodState, 'array1', ['4', 5, 6]);
mutateConcat2(goodState, 'array2', [4, 5, 6]);
mutateConcat2(badState, 'array1', [4, 5, 6]);
Link to code.
【讨论】:
【参考方案2】:所以,在这个和 reddit 线程之间,我相信我有满足我需求的最佳解决方案。这是我想出的:
const mutateConcat = <
TState extends Record<TKey, any[]>,
TKey extends keyof TState,
TElement extends TState[TKey] extends Array<infer TEl> ? TEl : never
>(state: TState, key: TKey, val: T[K], elementProperty?: keyof TElement) =>
state[key].push(...val);
if (elementProperty)
// filter state[key] to have distinct values off of element property
我选择在TState
中使用any[]
是因为当我尝试TElement[]
时还存在其他问题,因为状态可能具有任何类型的属性,并且与调用它的方式不一致。这种方法实际上被用作高阶函数的 vuex 变异方法,所以我不关心状态参数的极端类型安全。 keyof
参数是重要的参数,因为它们将由开发人员传递,并且此解决方案强制要求两者都对传递的对象有效。
编辑:这里是所有感兴趣的人的完整功能:
const mutateConcat = <
TState extends Record<TKey, any[]>,
TKey extends keyof TState,
TElement extends TState[TKey] extends Array<infer TEl> ? TEl : never
>(
key: TKey,
sessionKey?: string,
distinctProperty?: keyof TElement,
) => (state: TState, val: TElement[]) =>
state[key].push(...val);
if (distinctProperty)
removeDuplicates(state[key], distinctProperty);
if (sessionKey)
setSessionItem(sessionKey, state);
;
【讨论】:
【参考方案3】:另一个答案是正确的,但我想补充一点,如果您将state
设为相对具体的Record<K, U[]>
类型,而不是更通用的T extends Record<K, U[]>
类型,则可以使其工作。这有助于编译器将赋值视为安全,因为T extends a: string[]
可能是a: Array<"lit">
,然后obj.a.push("oops")
将是一个错误:
const mutateConcat = <K extends keyof any, U>(
state: Record<K, U[]>,
key: K,
val: U[]
) =>
state[key] = state[key].concat(val); // okay
;
我看到的唯一问题是,如果您在调用 mutateConcat()
时将对象文字传递给 state
参数,那么您可能会在非数组属性上得到不受欢迎的 excess property checking:
mutateConcat( a: [1, 2, 3], b: 1 , "a", [4, 5, 6]); // excess property checking ?
我对@987654334@ 的签名所做的任何事情都会导致编译器在实现中过于混乱,你又回到了casting asserting。如果你仍然想这样做,你可以通过不使用新的对象文字来避免过多的属性检查,如:
const obj = a: [1, 2, 3], b: 1 ;
mutateConcat(obj, "a", [4, 5, 6]); // okay
这是否满足您的用例取决于您。好的,祝你好运!
Link to code
【讨论】:
keyof any
对我来说是新的。整洁的。在这种情况下简单地做K extends string
有什么好处?
这里的一个限制是它没有通过这个测试:const obj = a: [1, 2, 3] ; mutateConcat(obj, "a", [ 1, 'b', 'c' ]);
,我认为这应该是一个错误,因为我假设数组的目标类型来自状态而不是来自新的价值观。以上是关于打字稿泛型,获取数组内容的类型的主要内容,如果未能解决你的问题,请参考以下文章