Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
219 views
in Technique[技术] by (71.8m points)

Generic with indexed argument of type in typescript

I would like to have a generic that I can index by name while preserving typing. It would be useful to be able to write:

candidates.sort(compareByClosestTo("recieved", date))

I feel pretty close, but can't seem to complete this

// generic comparison fn with index
type Dateable<T> = {
  [P in keyof T]: DateTime
};
const compareByClosestTo = <T extends Dateable<T>>(key: keyof T, date: DateTime) => 
  (l: T, r: T) => Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds)

Playground link

however this gives an error because it requires all parameters to be of type DateTime. Is there any way to simply constrain the type of T[key] to type?

EDIT

5 minutes after posting this I came across a hint here that led me to:

function sortByClosest<K extends string>(data: Record<K, DateTime>[], key: K, date: DateTime): void {
  data.sort((l, r) => 
    Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds))
}

However, I'd like to leave this open because I would really really like to know how to figure out typed generic indexing on callback factories (ie, is the top forms possible?)

question from:https://stackoverflow.com/questions/65643280/generic-with-indexed-argument-of-type-in-typescript

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I think the main problem in your original compareByClosestTo() function is that there is no good inference site for T. The key parameter is of type keyof T. If you pass in, say, "foo" for key, you'd need the compiler to infer a type T for which "foo" is one of its keys. The compiler might infer {foo: DateTime}, depending on how you use it, or it might give up and infer something like {foo: any} or even {[k: string]: any}. The only thing you can expect the compiler to reliably infer from a value of type "foo", is the string "foo" itself.

To that end, let's dispense with T extends Dateable<T> and instead look at K extends PropertyKey. Then we can express T in terms of K: in this case, { [P in K]: DateTime }, which is equivalent to Record<K, DateTime> (using the Record utility type):

const compareByClosestTo = <K extends PropertyKey>(key: K, date: DateTime) =>
  (l: { [P in K]: DateTime }, r: { [P in K]: DateTime }) =>
    Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds);

Let's see what happens when we call it:

const compareFn = compareByClosestTo("recieved", DateTime.local());
/* const compareFn: (l: {
    recieved: DateTime;
}, r: {
    recieved: DateTime;
}) => number */

Here, K is inferred to be "received", and the resulting compare function is the {received: DateTime} type we want. And so both of the following sort() calls work:

const works = dates.map(dt => ({ recieved: dt }))
  .sort(compareByClosestTo("recieved", DateTime.local()))

const stillWorks = dates.map((dt, i) => ({ recieved: dt, index: i }))
  .sort(compareByClosestTo("recieved", DateTime.local()))

because each of the arrays you're sorting have elements assignable to {received: DateTime}.

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...