额外类型条件的 Typescript key
Typescript keyof extra type condition
我想传递数组类型字段名称的两个通用条件,但不接受第二个条件。
这是我的方法声明,没问题。
firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;
上面的方法声明有效,但我只想在 recurviseItem 字段中传递 Array 类型。
我正在尝试此方法声明但不起作用。
firstOrDefault<K extends keyof T & T[]>(predicate?: (item: T) => boolean, recursiveItem?: K): T
如何解决这个问题?
示例代码
let departments : IDepartment[] = [
{
name: 'manager',
subDepartments: [
{
name: 'accountant'
}
]
}
]
// This my method declaration and this code worked but you can pass name and subDepartments field pass for recursiveItem parameter but i want only T[] type field pass so only subDepartments.
let department = departments.firstOrDefault(d => d.name == 'accountant', 'subDepartments')
console.log(department)
interface Array<T> {
firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;
}
Array.prototype.firstOrDefault = function(this, predicate, recursiveItem) {
if (!predicate)
return this.length ? this[0] : null;
for (var i = 0; i < this.length; i++) {
let item = this[i]
if (predicate(item))
return item
if (recursiveItem) {
let subItems = item[recursiveItem]
if (Array.isArray(subItems)) {
var res = subItems.firstOrDefault(predicate, recursiveItem)
if (res)
return res
}
}
}
return null;
}
interface IDepartment {
name?: string,
subDepartments?: IDepartment[]
}
我认为没有很好的答案。您正在寻找的是一个类型函数,它标识类型 T
的属性,其值是 Array<T>
类型(或 Array<T> | undefined
因为可选属性就是这样)。这最自然地用 mapped conditional types 表示,它还不是 TypeScript 的一部分。在那种情况下,你可以做类似
的东西
type ValueOf<T> = T[keyof T];
type RestrictedKeys<T> = ValueOf<{
[K in keyof T]: If<Matches<T[K],Array<T>|undefined>, K, never>
}>
并将 recursiveItem
参数注释为类型 RestrictedKeys<T>
并完成。但你不能那样做。
我唯一实际可行的解决方案是放弃扩展 Array
原型。 (不管怎么说,这都是不好的做法,不是吗?)如果您可以使用第一个参数是 Array<T>
的独立函数,那么您可以这样做:
function firstOrDefault<K extends string, T extends Partial<Record<K, T[]>>>(arr: Array<T>, pred?: (item: T) => boolean, rec?: K): T | null {
if (!pred)
return this.length ? this[0] : null;
for (var i = 0; i < this.length; i++) {
let item = this[i]
if (pred(item))
return item
if (rec) {
let subItems = item[rec]
if (Array.isArray(subItems)) {
var res = firstOrDefault(subItems, pred, rec)
if (res)
return res
}
}
}
return null;
}
在上面,你可以限制类型 T
为 Partial<Record<K,T[]>>
,这意味着 T[K]
是类型 Array<T>
的可选 属性 .通过将此表示为对 T
的限制,类型检查器将按照您的意愿运行:
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'subDepartments') // okay
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'name') // error
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'random') // error
正如我所说,没有很好的方法来采用上述解决方案并使其适用于扩展 Array<T>
接口,因为它通过限制 T
来工作。理论上,您可以用 T
来表达 K
,例如 keyof (T & Partial<Record<K,T[]>>
,但 TypeScript 不会积极评估交集以消除不可能的类型,因此它仍然接受 name
,即使尽管 name
属性 的推断类型类似于 string & IDepartment[]
不应该存在。
无论如何,希望以上解决方案对您有用。祝你好运!
编辑:我看到您通过放宽不同的要求解决了您自己的问题:recursiveItem
参数不再是键名。我仍然认为您应该考虑独立函数解决方案,因为它按您最初的预期工作并且不会污染 Array
的原型。当然,这是你的选择。再次祝你好运!
试试这个类型定义
type ArrayProperties<T, I> = { [K in keyof T]: T[K] extends Array<I> ? K : never }[keyof T]
class A {
arr1: number[];
arr2: string[];
str: string;
num: number;
func() {}
}
let allArr: ArrayProperties<A, any>; // "arr1" | "arr2"
let numArr: ArrayProperties<A, number>; // "arr1"
所以 firstOrDefault
看起来像那样(我把 ArrayProperties<T, T>
限制为递归类型的 recursiveItem,即只能使用 IDepartment[]
类型的属性,但是你可以把 ArrayProperties<T, any>
如果你想接受任何数组)
function firstOrDefault<T>(predicate?: (item: T) => boolean, recursiveItem?: ArrayProperties<T, T>): T { }
以你为例
interface IDepartment {
name: string;
subDepartments?: IDepartment[];
}
let departments : IDepartment[] = [
{
name: 'manager',
subDepartments: [
{
name: 'accountant'
}
]
}
]
let a = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartments'); // OK
let b = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartment'); // error: [ts] Argument of type '"subDepartment"' is not assignable to parameter of type '"subDepartments"'.
我想传递数组类型字段名称的两个通用条件,但不接受第二个条件。
这是我的方法声明,没问题。
firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;
上面的方法声明有效,但我只想在 recurviseItem 字段中传递 Array 类型。
我正在尝试此方法声明但不起作用。
firstOrDefault<K extends keyof T & T[]>(predicate?: (item: T) => boolean, recursiveItem?: K): T
如何解决这个问题?
示例代码
let departments : IDepartment[] = [
{
name: 'manager',
subDepartments: [
{
name: 'accountant'
}
]
}
]
// This my method declaration and this code worked but you can pass name and subDepartments field pass for recursiveItem parameter but i want only T[] type field pass so only subDepartments.
let department = departments.firstOrDefault(d => d.name == 'accountant', 'subDepartments')
console.log(department)
interface Array<T> {
firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;
}
Array.prototype.firstOrDefault = function(this, predicate, recursiveItem) {
if (!predicate)
return this.length ? this[0] : null;
for (var i = 0; i < this.length; i++) {
let item = this[i]
if (predicate(item))
return item
if (recursiveItem) {
let subItems = item[recursiveItem]
if (Array.isArray(subItems)) {
var res = subItems.firstOrDefault(predicate, recursiveItem)
if (res)
return res
}
}
}
return null;
}
interface IDepartment {
name?: string,
subDepartments?: IDepartment[]
}
我认为没有很好的答案。您正在寻找的是一个类型函数,它标识类型 T
的属性,其值是 Array<T>
类型(或 Array<T> | undefined
因为可选属性就是这样)。这最自然地用 mapped conditional types 表示,它还不是 TypeScript 的一部分。在那种情况下,你可以做类似
type ValueOf<T> = T[keyof T];
type RestrictedKeys<T> = ValueOf<{
[K in keyof T]: If<Matches<T[K],Array<T>|undefined>, K, never>
}>
并将 recursiveItem
参数注释为类型 RestrictedKeys<T>
并完成。但你不能那样做。
我唯一实际可行的解决方案是放弃扩展 Array
原型。 (不管怎么说,这都是不好的做法,不是吗?)如果您可以使用第一个参数是 Array<T>
的独立函数,那么您可以这样做:
function firstOrDefault<K extends string, T extends Partial<Record<K, T[]>>>(arr: Array<T>, pred?: (item: T) => boolean, rec?: K): T | null {
if (!pred)
return this.length ? this[0] : null;
for (var i = 0; i < this.length; i++) {
let item = this[i]
if (pred(item))
return item
if (rec) {
let subItems = item[rec]
if (Array.isArray(subItems)) {
var res = firstOrDefault(subItems, pred, rec)
if (res)
return res
}
}
}
return null;
}
在上面,你可以限制类型 T
为 Partial<Record<K,T[]>>
,这意味着 T[K]
是类型 Array<T>
的可选 属性 .通过将此表示为对 T
的限制,类型检查器将按照您的意愿运行:
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'subDepartments') // okay
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'name') // error
firstOrDefault(departments, (d:IDepartment)=>d.name=='accountant', 'random') // error
正如我所说,没有很好的方法来采用上述解决方案并使其适用于扩展 Array<T>
接口,因为它通过限制 T
来工作。理论上,您可以用 T
来表达 K
,例如 keyof (T & Partial<Record<K,T[]>>
,但 TypeScript 不会积极评估交集以消除不可能的类型,因此它仍然接受 name
,即使尽管 name
属性 的推断类型类似于 string & IDepartment[]
不应该存在。
无论如何,希望以上解决方案对您有用。祝你好运!
编辑:我看到您通过放宽不同的要求解决了您自己的问题:recursiveItem
参数不再是键名。我仍然认为您应该考虑独立函数解决方案,因为它按您最初的预期工作并且不会污染 Array
的原型。当然,这是你的选择。再次祝你好运!
试试这个类型定义
type ArrayProperties<T, I> = { [K in keyof T]: T[K] extends Array<I> ? K : never }[keyof T]
class A {
arr1: number[];
arr2: string[];
str: string;
num: number;
func() {}
}
let allArr: ArrayProperties<A, any>; // "arr1" | "arr2"
let numArr: ArrayProperties<A, number>; // "arr1"
所以 firstOrDefault
看起来像那样(我把 ArrayProperties<T, T>
限制为递归类型的 recursiveItem,即只能使用 IDepartment[]
类型的属性,但是你可以把 ArrayProperties<T, any>
如果你想接受任何数组)
function firstOrDefault<T>(predicate?: (item: T) => boolean, recursiveItem?: ArrayProperties<T, T>): T { }
以你为例
interface IDepartment {
name: string;
subDepartments?: IDepartment[];
}
let departments : IDepartment[] = [
{
name: 'manager',
subDepartments: [
{
name: 'accountant'
}
]
}
]
let a = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartments'); // OK
let b = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartment'); // error: [ts] Argument of type '"subDepartment"' is not assignable to parameter of type '"subDepartments"'.