简体   繁体   中英

Typescript Generics: Use output argument type to build input argument type

I want to use generics to decrease maintenance, and possible chance of errors while maintaining, Typescript types.

I have a function called loadValues that takes in an object with the following example interface:

interface DashboardPreload: {
    studentNames: Observable<string[]>,
    seatNumbers: Observable<number[]>
}

And returns an object with the following, very similar, interface. Note it has the same keys, and keys have the same values, except in DashboardPreload, it's wrapped in an Observable:

interface DashboardState: {
    studentNames: string[],
    seatNumbers: number[] 
}

So specifying the types look like this:

function loadValues(preload: DashboardPreload): DashboardState {
    // ...
}

To reuse the loadValue function across components, I've changed the signature to allow for generics:

loadValues<A, B>(preload: A): B {
    // ...
}
state = loadValues<DashboardState, DashboardPreload>(toPreload);
// another component would then do
interface UserDetailPreload: {
    name: Observable<string>;
    address: Observable<string>;
}
interface UserDetailState: {
    name: string;
    address: string;
}
otherState = loadValues<UserDetailState, UserDetailPreload>(otherValuesToPreload);

But there is a massive win I still want to take. My input and output objects

  1. always have the same keys
  2. and corresponding key value types are the same, except on the input object, the values are wrapped in an Observable.

To avoid having to maintain very similar types, it would be great if generics could specify the above relationship between the input and output types. I want to simply provide a single generic parameter (eg the output) and the signature is written in a way that will type the input and output:

// How should this signature look?
function loadValues<P>(preload: ?): P {
    // ...
}
state = loadValues<DashboardState>(toPreload);

Is this possible? How should the function signature look (please note I'm not asking for the logic in the function, but asking how to do the generics). I've looked at eg

  1. Typescript signatures for things like Object.entries(),
  2. Structures like "K extends keyof P"
  3. Examples such as: https://medium.com/iqoqo-engineering/two-advanced-techniques-to-make-you-a-typescript-wizard-df42e00b1cf8

but haven't found a solution.

you can add a Preload type like this:

type Preload<T> = {
  [P in keyof T]: Observable<T[P]>;
};
function loadValues<P>(preload: Preload<P>): P {
  // ...
}
state = loadValues<DashboardState>(toPreload);

I'm not exactly certain I understand what you're trying to achieve, so this might not pose a complete answer, but perhaps this might push you in the right direction.

If you make the type of your preload argument generic as well, typescript should be able to infer the generic return type. You'd need to introduce a new generic interface for this.

So your function signature would look like this:

function loadValues<P>(preload: PreloadState<P>): P {
    // ...
}

interface PreloadState<P> { }

and when you use it like this

var preload: PreloadState<DashboardState> = { .. };
var result = loadValues(preload);

the type of result will correctly be inferred to be of type DashboardState .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM