Как ввести настраиваемую фабричную функцию в TypeScript? - PullRequest
1 голос
/ 13 октября 2019

Я пытаюсь получить базовый пример работы для фабричной функции, которая возвращает различные типы возвращаемых данных в зависимости от строки key, которую она передала ему…

const factory = <
    T extends Record<string, () => any>,
    K extends keyof T
>(options: T) => {
    return (key: K): ReturnType<T[K]> => {
        return options[key]()
    }
}

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
})

const s = create('string')
const n = create('number')
const b = create('boolean')

Но в этом случае s, n и b имеют тип string | number | boolean. Вместо каждого, имеющего самый узкий из возможных типов. ( Ссылка на TypeScript Playground )

Как можно заставить работать сужение?

1 Ответ

1 голос
/ 13 октября 2019

Когда вы вызываете обобщенную функцию, параметры ее типа указываются в это время. Это либо делается вызывающей стороной вручную с использованием угловых скобок (например, factory<MyType, MyKey>(someValueOfMyType)), либо компилятор выводит их из параметров, переданных функции и контексту, в котором вызывается функция. По вашему определению, вызов

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
});

должен выводить T и K. T легко вывести, потому что параметр, переданный в create, имеет тип T. Так что T это {string: ()=>string, number: ()=>number, boolean: ()=>boolean}. Но нигде в этом призыве нет места, где можно сделать вывод K. Нет параметра типа K, и контекст просто сохраняет возвращаемое значение в переменную с именем create. Таким образом, вывод не выполняется. В таких случаях компилятор выбирает самый широкий тип, который работает, а именно его ограничение . Это означает, что K выводится как keyof T, что становится "string" | "number" | "boolean". И поэтому create имеет тип

// const create: (key: "string" | "number" | "boolean") => string | number | boolean

, который вам не нужен.


Вы действительно хотите, чтобы T был указан только при вызове factory(),и вы не хотите указывать K, пока не наберете create(). Например, когда вы звоните create("string"), вы знаете, что K должно быть "string". А поскольку параметры универсального типа функции указываются при вызове функции, вы хотите, чтобы сама create() была универсальной функцией, где K является ее параметром типа.

Таким образом, решение здесь состоит в том, чтобы оставить Tгде это, но переместите универсальный K из внешней функции в функцию, которую она возвращает:

const factory = <
    T extends Record<string, () => any>>(options: T) => {
    return <K extends keyof T>(key: K): ReturnType<T[K]> => {
        return options[key]()
    }
}

Теперь, когда вы вызываете factory(), он возвращает универсальную функцию:

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
})

/* const create: <K extends "string" | "number" | "boolean">(key: K) => ReturnType<{
    string: () => string;
    number: () => number;
    boolean: () => boolean;
}[K]> */

И тогда он ведет себя так, как вы намереваетесь:

const s = create('string') // string
const n = create('number') // number
const b = create('boolean') // boolean

Хорошо, надеюсь, это поможет. Удачи!

Ссылка на код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...