Типы возвращаемых функций не разрешаются правильно при вызове в цикле

Я создал простую карту с двумя функциями, которые принимают один аргумент «foo: string» и реализуют различные функции, которые возвращаются напрямую:

import { mapValues } from 'lodash';

const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};

interface Actions {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

const actions: Actions = { fun1, fun2 };

const mappedActions = mapValues(actions, action => action('bar'));

mappedActions.fun1(1);

С помощью mapValues я перебираю эти функции и вызываю их с некоторым общим параметром. Я бы ожидал, что это карта функций полуназываемый, назначенных в mappedActions карте, но проблема с типами все еще остается.

fun1 не разрешается как функция, которая принимает (name: string, surname: string) аргументов, но все же является функцией верхнего уровня.

Как мне это составить, чтобы TypeScript правильно разрешал типы?

Типы возвращаемых функций не разрешаются правильно при вызове в цикле

Использует ли mapValues сопоставленный тип?

Jonas Wilms 27.04.2019 17:38

@JonasWilms Да, action('bar') разрешается правильно, поэтому требуется один строковый параметр. Передача чего-либо еще дает ошибку.

hsz 27.04.2019 17:44
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
2
181
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я скопировал ваш фрагмент в CodeSandbox и проанализировал функцию Lodash mapValues(), и я думаю, что типы для mapValues() не соответствуют вашей цели.

mapValues<T extends object, TResult>(obj: T | null | undefined, callback: ObjectIterator<T, TResult>): { [P in keyof T]: TResult };

Как видите, функция mapValues ​​принимает объект (T extends object) и для каждого ключа этого объекта присваивает TResult, что делает это:

mapValues<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>(obj: Actions, callback: ObjectIterator<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>): {

Я пытался использовать lodash (особенно Лодаш/fp) с TypeScript и могу сказать, что не все работает идеально, также очень сложно создать типизацию универсальный для всех возможностей.

Хорошо, это имеет смысл. Так можно ли добиться этого в TS с помощью обычного цикла Object.values?

hsz 27.04.2019 20:56
Ответ принят как подходящий

Я думаю, что нашел способ правильно разрешить типы:

import { mapValues } from 'lodash';

const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};

type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };

interface Actions extends FunctionDictionary {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

const actions: Actions = { fun1, fun2 };

type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
  [P in keyof T]: ReturnType<T[P]>;
} : never;

const map: (actions: Actions) =>  ReturnTypeDict<Actions> = actions => {
  return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}

const mappedActions = map(actions);

mappedActions.fun1(1);

Ключевым здесь является условный тип ReturnType. Чтобы использовать его, вы должны создать свой собственный условный тип:

type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
  [P in keyof T]: ReturnType<T[P]>;
} : never;

И теперь вы должны явно сказать, что ваш интерфейс расширяет нужный тип словаря:

type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };

interface Actions extends FunctionDictionary {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

Затем вы создаете функцию, которая правильно отображает данные:

const map: (actions: Actions) =>  ReturnTypeDict<Actions> = actions => {
  return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}

Другие вопросы по теме