Типы скриптов Generics, keyof используется, но TS также вычисляется с символом | номер | строка также - PullRequest
2 голосов
/ 16 января 2020

Позволяет непосредственно перейти к примеру.

В приведенном ниже коде вы можете увидеть, как используется keyof, но машинописный текст по-прежнему вычисляет тип EVT как string|number|string, даже когда EVT расширяется keyof ((typeof Events)[OBJ])

КОД: (В ПОСЛЕДНЕЙ ЛИНИИ АВТОКОМПЛЕКТА И ТИПА ПРОВЕРКИ РАБОТАЕТ ШТРАФ)

export const Events = {
    // [event: string]: (...args: any) => void;
    account: {
        available: (status?:  'online'|'offline',foo?:'test',bar?:'test2') => {
            console.error('1');
        },
    }
}

export  class AMQP {
    static event<
        OBJ extends keyof typeof Events,
        EVT extends keyof ((typeof Events)[OBJ]),
        FUNC extends ((typeof Events)[OBJ][EVT])
        >(
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}

AMQP.event('account','available','online','test','test2');

Playground Link

ВЫПУСК (КРАСНЫЙ ФЛАГ НА ПЕРЕКЛЮЧАТЕЛЕ 'FUN')

Type 'FUNC' does not satisfy the constraint '(...args: any) => any'.
  Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][EVT]' is not assignable to type '(...args: any) => any'.
    Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][keyof { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ]]' is not assignable to type '(...args: any) => any'.
      Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string] | { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][number] | { ...; }[OBJ][symbol]' is not assignable to type '(...args: any) => any'.
        Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string]' is not assignable to type '(...args: any) => any'.(2344)

ОБНОВЛЕНИЕ :::

на данный момент. проблема, предложенная @Maciej Sikora, заставляя объявить FUN как исправленную функцию.

, но вот еще один пример. там, где он работает нормально без объявления силы, разница только в том, что События не глубокие, поэтому он работает там, но не тогда, когда объект немного глубже, мне все еще интересно узнать причины.

Playground Link

1 Ответ

2 голосов
/ 16 января 2020

Узкий тип по условному типу

Мы можем исправить это, явно сказав TS, что нам нужны только функции, хотя в настоящее время есть только то, что TS не в состоянии сузить его. Рассмотрим:

type E = typeof Events // for readability

export  class AMQP {
    static event<
        OBJ extends keyof E,
        EVT extends keyof E[OBJ],
        FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ?  E[OBJ][EVT] : never
        >(
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}

Суть - FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT] : never. Это означает, что мы разрешаем выбирать только функции на втором уровне структуры объекта. Похоже, теперь TS больше не жалуется.

Дополнительным плюсом такого решения является то, что если мы предоставим поле, которое не будет функцией, то не будет возможности вызвать функцию с таким выбором.

Почему TS не может сузить тип

Есть несколько причин, которые могут объяснить это частично. Рассмотрим аналогичный пример:

const a = {
    a: 2
}
const f = <A extends typeof a, K extends keyof A>(a: A, k: K) => {
    return a[k] + 1; // error we cannot use +
}
f(a, 'a');

Как, если мы не можем использовать +, если a имеет только числовое значение внутри. Похоже, это неправильно? Нет, это правильно, так как наша функция говорит, что мы разрешаем тип extends typeof a, поэтому он не совпадает с a, у нас может быть объект со свойством a, но также другое свойство b, которое будет, например, логическим , Учтите, что приведенный ниже код компилируется:

const b = {
    a: 2,
    b: true,
}
f(b, 'b');

Таким образом, я могу использовать объект, который структурно имеет те же поля - a, но также имеет другой с другими типами. По той же причине ваш код не может сузить тип, поскольку мы можем ввести другой объект, который будет структурно таким же, но будет иметь другие нефункциональные свойства.

В нашем примере мы имеем немного другой случай, так как мы ссылаемся на один и тот же объект. Тогда не совсем понятно, почему на каком-то вложенном уровне вывод не выполняется. ТС нуждается в дополнительной помощи для сужения, чтобы функционировать. Почему для меня неизвестно, похоже на ограничения логического вывода.

Ввести тип stati c для событий

Второе решение, которое вы должны рассмотреть, - это stati c, типизирующий интерфейс Events. Обратите внимание:

type Events = Record<string, Record<string, (...a: any[]) => any>>
export const events = {
    account: {
        available: (status?: 'online' | 'offline', foo: 'test', bar: 'test2') => {
            console.error('1');
        },
    }
};

export  class AMQP {
    static event<
        INSTANCE extends Events,
        OBJ extends keyof INSTANCE,
        EVT extends keyof INSTANCE[OBJ],
        FUNC extends INSTANCE[OBJ][EVT]
        >(
        inst: INSTANCE,
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}
// pay attention below I pass events object explicitly as first argument
AMQP.event(events, 'account', 'available', 'online', 'test', 'test2' );

Обратите внимание, что я представил здесь несколько вещей:

  • Тип события
  • дополнительный первый аргумент, который является объектом экземпляра интерфейса Events

Такое решение является более гибким, поскольку реализация не привязана к одному объекту, вы можете передать любой объект, который является структурно корректным с интерфейсом Events, также нет необходимости в условных типах.

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