Typescript: Как сделать тип, который принимает объект, ключи которого соответствуют generi c, но все значения являются функциями отображения аргумента значения - PullRequest
1 голос
/ 06 февраля 2020

Я хочу получить тип, который говорит: «У меня все ключи рекурсивно такие же, как у типа T , но все мои значения являются функциями, которые принимают такое значение ключа и отображают его.

I ' мы пробовали приведенный ниже код, но он не выводит типы возвращаемых заданных функций, а рекурсивность неверна

export type ObjectButAllKeyIsValueFn<T> = {
    [K in keyof T]?: ObjectButAllKeyIsValueFn<T[K]> | (<R>(value: T[K]) => R)
}


interface User {
    name: string,
    surname: string,
    address: {
        city: string,
        country: string,
        street: string
    }
}

const z: ObjectButAllKeyIsValueFn<User> = {
    name: value => `Hello ${value}`,
    address: {
        city: city => 1
    }
};

Обновление: я хочу добиться чего-то подобного, но рекурсивно, поэтому мне нужно правильно сопоставить StupidPattern с Pattern

type StupidPattern<T> = {
    [K in keyof T]: StupidPattern<T[K]> | ((value: T[K]) => unknown)
}

type Pattern<T> = {
    [K in keyof T]: (value: T[K]) => unknown
}

type Result<T, P = Pattern<T>> = {
    [K in keyof P]: K extends keyof T
        ? P[K] extends (value: T[K]) => infer R
            ? R
            : never
        : never
}

declare function map <T> (pattern: Pattern<T>): (target: T) => Result<T, Pattern<T>>
declare function map <T> (pattern: Pattern<T>, target: T): Result<T, Pattern<T>>;

1 Ответ

1 голос
/ 06 февраля 2020

Тип (<R>(value: T[K]) => R) - это не то, что вы ищете; по сути, дженерик c R является количественно неправильным способом. Это означает «функцию, чей вход имеет тип T[K], а выход - любой тип R, который указывает вызывающая сторона . Это не реально реализовать безопасно. Предположительно, вы действительно имеете в виду, что Реализатор функции должен иметь возможность указать R, но в TypeScript нет простого способа представить это с помощью обобщений ... вы могли бы поместить R в качестве другого обобщенного c параметра ObjectButAllKeyIsValueFn , но тогда вам понадобится один R на ключ T. Давайте вернемся назад и посмотрим на это по-другому:

Как насчет того, чтобы определить ObjectButAllKeyIsValueFun как этот тип:

export type ObjectButAllKeyIsValueFn<T> = {
  [K in keyof T]?: ObjectButAllKeyIsValueFn<T[K]> | ((value: T[K]) => unknown)
}

Этот тип отражает идею о том, что функционально-значимым свойствам разрешено возвращать абсолютно любой тип, если вам это нужно. Но, конечно, простое аннотирование значения типом ObjectButallKeyIsValueFn<User> отбрасывает кучу информации о типе этого значения, вы не знаете, какой тип возвращает каждая функция, или даже если какое-то конкретное свойство вообще является функцией.

Так что давайте откажемся от annot ating переменных как этот тип. Вместо этого, когда мы создаем значения, мы проверяем, что они присваиваются этому типу, не расширяя их. Это даст нам все, что вы хотите IntelliSense. Вот вспомогательная функция и ее применение к User:

const asObjType = <T>() => <U extends ObjectButAllKeyIsValueFn<T>>(u: U) => u;

const asObjUser = asObjType<User>();

А вот как вы вызываете asObjUser():

const z = asObjUser({
  name: value => `Hello ${value}`,
  address: {
    city: city => 1
  }
});

/* const z: {
    name: (value: string) => string;
    address: {
        city: (city: string) => number;
    };
} */

console.log(z.name("Fred")); // Hello Fred

, которая работает и отслеживает специфику c тип z. Он также отлавливает ошибки, подобные этой:

const oops = asObjUser({
  name: 123, // error!
  // number is not assignable to string | ((value: string) => unknown) | undefined
});

Итак, надеюсь, этого достаточно для достижения прогресса. Удачи!

Детская площадка ссылка на код

...