第一个非常幼稚的方法:
interface Measurement {
date: Date
width: number
height: number
depth: number
comment: string
}
type GetByType<T, V> = {
[P in keyof T]: T[P] extends V ? P : never
}[keyof T]
/**
* Just an alias to reduce repetitive code
*/
type Get<T> = Array<GetByType<Measurement, T>>
const numericFields: Get<number> = ['width', 'height', 'depth']
const dateFields: Get<Date> = ['date']
const stringFields: Get<string> = ['comment']
缺点:
const numericFields: Get<number> = ['width', 'height', 'depth', 'depth', 'width'] // valid from TS perpective
如果你想禁止TS复制,可以试试this answer
如果你想100%确定运行时没有重复,最好的方法是使用Set's:
const array = [...new Set([1, 2, 3, 4, 4])] // [1, 2, 3, 4]
第二种方法
interface Measurement {
date: Date
width: number
height: number
depth: number
comment: string
}
// filter all properies
type GetByType<T, V> = {
[P in keyof T]: T[P] extends V ? P : never
}[keyof T]
/**
* Just an alias to reduce repetitive code
*/
type Result = GetByType<Measurement, string>
type Values<T> = T[keyof T]
/**
* because our second argument is stringified type name,
* we need some sort of vice-versa mapping
*/
type LiteralToType<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'Date' ? Date : never;
type TypeToLiteral<T> = T extends string ? 'string' : T extends number ? 'number' : T extends Date ? 'Date' : never;
const getInterfaceFieldsWithType = <
Obj extends Record<string, unknown>,
Type extends TypeToLiteral<Values<Obj>> // I need a guarantee that we have at least one property with expected type
>(obj: Obj, expectedType: Type): GetByType<Obj, LiteralToType<Type>>[] => {
const keys = (Object.keys(obj) as Array<keyof Obj>)
/**
* Here, filter is a typescript guard
* It says: if I return true, then you can sleep well, this key is what you need
*/
return keys.filter((elem): elem is GetByType<Obj, LiteralToType<Type>> => typeof obj[elem] === expectedType)
}
/**
* Tests
*/
const result = getInterfaceFieldsWithType({ date: '01-01-2021', width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'number') // ok ("width" | "height" | "numberdepth")[]
const result2 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'Date') // ok "date"[]
const result3 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'string') // ok "comment"[]
const result4 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'Promise') // error
如果你仍然想允许不存在的属性类型,比如Promise,你可以在你的函数中添加下一个重载
type EmptyArray = readonly string[];
interface Overloading {
<
Obj extends Record<string, unknown>,
Type extends TypeToLiteral<Values<Obj>> //I need a guarantee that we have at least one property with expected type
>(obj: Obj, expectedType: Type): ReadonlyArray<GetByType<Obj, LiteralToType<Type>>>
<
Obj extends Record<string, unknown>,
Type extends string // I need a guarantee that we have at least one property with expected type
>(obj: Obj, expectedType: Type): EmptyArray
}