Вывод сопоставленных типов TypeScript работает не так, как ожидалось

#javascript #typescript

#javascript #typescript

Вопрос:

Учитывая эту функцию:

 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 };
};
  

И это использование:

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

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

Я ожидал бы, что TypeScript сможет вывести возвращаемый тип как:

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

Однако это выводится как:

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

Я создал <a rel="noreferrer noopener nofollow" href="https://www.typescriptlang.org/play/#src=export const combineValidators = Err }, Err>(
validators: Input
) => (values: { [P in keyof Input]?: unknown }): { [P in keyof Input]: Err } => {
return {} as { [P in keyof Input]: Err };
};

const validator = combineValidators({
name: () => ‘error’,
email: () => ‘error’
});

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

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

// Actual: `errors` to be inferred as:
// interface Ret {
// name: {};
// email: {}
// }» rel=»nofollow noreferrer»>живой пример в TypeScript playground, демонстрирующий проблему.

Кто-нибудь может помочь?

Ответ №1:

Err выводится не так, как вы ожидаете. Возможно, было бы проще использовать ReturnType условный тип для извлечения возвращаемых типов из 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'
});
  

Мы даже можем пойти немного дальше, и если вы укажете типы параметров в функции проверки, вы можете получить проверку типов для полей объекта, переданного 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
});
  

Комментарии:

1. Удивительно! Мне нужно улучшить TypeScript. Единственная проблема ReturnType , похоже, игнорируется undefined ; Я ожидал ReturnType<() => string | undefined> , что это приведет к string | undefined ?

2. @riscarrott вы уверены, что у вас есть строгие проверки null?

3. Удивительно, но я этого не делаю, отлично работает со строгими нулевыми проверками, спасибо вам!