Generics TypeScript: вывести тип из типа аргументов функции?

У меня есть метод с двумя аргументами, и я хочу, чтобы он выводил тип из первого аргумента.

Например, в следующем коде я хочу, чтобы тип T функции create_C<T> выводился из firstArgument, чтобы возвращаемый тип create_C функции был C<type inferred from firstArgument>

interface C<T> { 
    firstArgument: A<T>;
    secondArgument: (obj: any) => T
}

export interface A<T> {
    type: T;
}

function create_C<T>(
    firstArgument: A<T>,
    secondArgument: (obj: any) => T
): C<T> {
    return {
        firstArgument,
        secondArgument
    }
}

Однако в следующей реализации тип const c определяется как C<{ prop2: number }>. Но я ожидаю, что он будет выведен как C<B>, и я ожидаю, что компилятор выдаст ошибку, говоря, что тип возвращаемого значения secondArgument не относится к типу B

interface B { 
    prop1: string;
    prop2: number
}

export class B_Component implements A<B> {
    type: B = {
        prop1: "",
        prop2: 1
    };
}

const c = create_C(
    new B_Component(),
    () => ({ prop2: 2 })
)

Как я могу убедиться, что компилятор выдает ошибку, говоря, что тип возвращаемого значения secondArgument не относится к типу B?

Вот ссылка на редактор Stackblitz: https://stackblitz.com/edit/qqddsn


person prudhvi    schedule 26.11.2019    source источник
comment
Не совсем следую, но разве вы не должны называть create_C вот так ?: create_C<B_Component>(). Вы вводите функцию с помощью общего, и все выглядит хорошо, просто нужно передать правильный тип T, когда вы его вызываете ...   -  person MrRobboto    schedule 26.11.2019
comment
Так вы хотите, чтобы ваш create_C вызов был ошибкой? Обратный вызов, переданный как второй параметр, не возвращает B, он возвращает {prop2: number}, поэтому c - это C<{prop2: number}>. Почему бы не вернуть фактический B в обратном вызове?   -  person jcalz    schedule 26.11.2019
comment
@MrRobboto я не хочу вызывать функцию с общим. Я хочу, чтобы общий вывод выводился на основе типа переданного первого аргумента   -  person prudhvi    schedule 26.11.2019
comment
Совершенно уверен, что вы не должны были набирать create_C как общий, тогда   -  person MrRobboto    schedule 26.11.2019
comment
И следует прочитать в комментарии @jcalz - () => ({ prop2: 2 }) не соответствует тому, что вы вводите с помощью secondArgument: (obj: any) => T   -  person MrRobboto    schedule 26.11.2019
comment
@jcalz Да, я хочу, чтобы вызов create_C отображал ошибку, говорящую о том, что обратный вызов, переданный как второй параметр, не возвращает B. Я хочу, чтобы пользователь, который передает второй аргумент, возвращал B. И если это не так, Я хочу, чтобы типограф на него кричал с ошибкой.   -  person prudhvi    schedule 26.11.2019
comment
@PrudhviChandraSimhadri Я понимаю, о чем вы сейчас говорите, в OP не совсем ясно, что второй пример должен выдать ошибку.   -  person MrRobboto    schedule 26.11.2019
comment
typescriptlang.org/play/#code/   -  person MrRobboto    schedule 26.11.2019
comment
@prudhvi только что создал игровую площадку выше - я вижу несколько проблем. B_Component не реализует A ‹B› - не уверен, что вы там пытаетесь делать ...   -  person MrRobboto    schedule 26.11.2019
comment
@MrRobboto Я только что исправил проблему. B_Component теперь изменено на свойство с именем type, которое является частью интерфейса A<B>   -  person prudhvi    schedule 26.11.2019


Ответы (2)


В вашей сигнатуре функции

declare function create_C<T>(a1: A<T>, a2: (obj: any) => T): C<T>;

есть два сайта вывода для T («сайт вывода» означает «место, где компилятор может попытаться вывести тип для параметра типа»). Один сайт взят из свойства type первого аргумента a1, а другой сайт является типом возвращаемого значения второго аргумента a2. Компилятор смотрит на вызов как

create_C(new B_Component(), () => ({ prop2: 2 });

и пытается вывести T с обоих сайтов. В этом случае есть совпадение: и (new B_Component()).type, и {prop2: 2} могут быть присвоены {prop2: number}. Так что ошибки нет, и вы получаете C<{prop2: number>. В другой ситуации это может быть именно то поведение, которое вы хотите от компилятора.


Вместо этого вы хотите, чтобы компилятор использовал только a1 для вывода T и просто проверял, соответствует ли a2 ему. То есть вы хотите, чтобы T в (obj: any) => T был параметром невыводимого типа (см. Microsoft / TypeScript # 14829). К сожалению, "официальной" поддержки для этого нет. Но, к счастью, есть обходные приемы, которые часто можно использовать, чтобы добиться такого поведения.

Вот один из таких приемов: если вы измените параметр типа на сайте вывода с T на T & {}, это снижает приоритет сайта. Таким образом, компилятор будет склонен сначала делать T выводы из других сайтов вывода и возвращаться к T & {} только в том случае, если он не может сделать вывод из других мест. И тип T & {} очень похож на T (если T является типом объекта, то он в основном такой же), поэтому он не сильно меняет семантику. Давай попробуем:

declare function create_C_better<T>(a: A<T>, b: (obj: any) => T & {}): C<T>;

Поехали:

const c2 = create_C_better(
    new B_Component(),
    () => ({ prop2: 2 }) // error!
    //    ~~~~~~~~~~~~~~ <-- prop1 is missing
)

const c3 = create_C_better(
    new B_Component(),
    () => ({ prop1: "all right", prop2: 2 })
); // C<B>

Там вы получаете желаемую ошибку, когда отсутствует prop1, а когда вы ее исправляете, вы получаете результат типа C<B> по желанию.


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

Ссылка на код

person jcalz    schedule 26.11.2019
comment
Отличное объяснение. На него следует ссылаться в коде, потому что никто никогда не узнает, для чего нужен &{}. - person Juan Mendes; 26.11.2019
comment
Спасибо, что нашли время помочь! Действительно отличное объяснение. - person prudhvi; 26.11.2019

Это связано с secondArgument: (obj: any) => T. Если вы примените вышеупомянутое определение к () => ({ prop2: 2 }) Тип T - { prop2: number }. Вы можете изменить его на что-нибудь другое, чтобы получить желаемый результат. Например.

    interface C<T> {
      firstArgument: A<T>;
      secondArgument: (obj: any) => any;
    }

    export interface A<T> {
      type: T;
    }

    declare function create_C<T>(
      firstArgument: A<T>,
      secondArgument: (obj: any) => any
    ): C<T>;

    interface B {
      prop1: string;
      prop2: number;
    }

    export class B_Component implements A<B> {
      type: B;
      configuration: B = {
        prop1: "",
        prop2: 1
      };
    }
    const b = new B_Component();
    export const c = create_C(b, () => ({ prop2: 2 }));
person Nidin Vinayakan    schedule 26.11.2019
comment
Эй, та вещь, которую вы опубликовали, все еще не работает. Тип T - ВСЕ ЕЩЕ {prop2: number} - person prudhvi; 26.11.2019
comment
Это странно. Пожалуйста, проверьте эту ссылку на игровую площадку. typescriptlang.org/play/ Наведите указатель мыши на c - это ожидаемый тип. - person Nidin Vinayakan; 26.11.2019
comment
Но он не требует, чтобы возвращаемый тип 2-го аргумента был B - person prudhvi; 26.11.2019
comment
Японял твою точку зрения. как и первый ответ `(obj: any) =› T & {} `должен исправить это. - person Nidin Vinayakan; 27.11.2019