简体   繁体   中英

TypeScript mapped types inference not working as expected

Given this function:

export const combineValidators = <Input extends { [P in keyof Input]: (val: string) => Err }, Err>(
  validators: Input
) => (values: { [P in keyof Input]?: unknown }): { [P in keyof Input]: Err } => {
  // Ignore implementation.
  return {} as { [P in keyof Input]: Err };
};

And this usage:

const validator = combineValidators({
  name: (val) => val ? undefined : 'error',
  email: (val) => val ? undefined : 'error'
});

const errors = validator({
  name: 'Lewis',
  email: 'lewis@mercedes.com'
});

I would expect TypeScript to be able to infer the return type as:

// Expected: `errors` to be inferred as:
interface Ret {
  name: string | undefined;
  email: string | undefined;
}

However it's inferred as:

// Actual: `errors` inferred as:
interface Ret {
  name: {};
  email: {};
}

I've created a live example in the TypeScript playground demonstrating the issue.

Can anybody help?

Err will not be inferred in the way you expect it. It might be simpler to use the ReturnType conditional type to extract the return types from Input :

type ReturnTypes<T extends Record<keyof T, (...a: any[]) => any>> = {
  [P in keyof T]: ReturnType<T[P]>
}

export const combineValidators = <Input extends Record<keyof Input, (val: unknown) => any>>(
  validators: Input
) => (values: Record<keyof Input, unknown>): ReturnTypes<Input> => {
  return {} as ReturnTypes<Input>;
};

const validator = combineValidators({
  name: (val) => val ? undefined : 'error',
  email: (val) => val ? undefined : 'error'
});

const errors = validator({
  name: 'Lewis',
  email: 'lewis@mercedes.com'
});

We can even go a bit further and if you specify parameters types in the validator function, you can get type checking for the fields of the object passed to validator :

type ParamTypes<T extends Record<keyof T, (a: any) => any>> = {
  [P in keyof T]: Parameters<T[P]>[0]
}

type ReturnTypes<T extends Record<keyof T, (...a: any[]) => any>> = {
  [P in keyof T]: ReturnType<T[P]>
}

export const combineValidators = <Input extends Record<keyof Input, (val: unknown) => any>>(
  validators: Input
) => (values: ParamTypes<Input>): ReturnTypes<Input> => {
  return {} as ReturnTypes<Input>;
};

const validator = combineValidators({
  name: (val: string) => val ? undefined : 'error',
  email: (val) => val ? undefined : 'error', // if we leave it out, we still get unknown
  age: (val: number) => val ? undefined : 'error'
});

const errors = validator({
  name: 'Lewis',
  email: 'lewis@mercedes.com',
  age: 0
});

const errors2 = validator({
  name: 'Lewis',
  email: 'lewis@mercedes.com',
  age: "0" // type error
});

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