Написание пользовательского оператора ngrx и возвращение наблюдаемого типа источника - PullRequest
8 голосов
/ 21 июня 2020

У меня есть собственный оператор waitFor, который я использую в своих эффектах:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor<ReturnType<typeof myAction>>([anotherAction]),
      ...etc
    );
  });

Он в основном смотрит на correlationId, чтобы не продолжать выполнение до тех пор, пока не будет отправлен массив действий. Но это не относится к делу.

Как и ожидалось, ofType принимает наблюдаемый источник и использует его как возвращаемый тип, однако я изо всех сил пытаюсь достичь того же эффекта. Как вы можете видеть выше, я использую ReturnType<typeof myAction>> и следующее в моем методе waitFor:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

Итак, если я назову waitFor вот так:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor([anotherAction]),
      ...etc
    );
  });

Тогда его тип определяется как Action, но я хочу, чтобы это было ReturnType<typeof theSourceObservable> по умолчанию. Поэтому я предполагаю, что мне понадобится что-то вроде этого в моем методе waitFor:

export function waitFor<A extends ReturnType<typeof sourceObservable?!>>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

waitFor выглядит так:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      switchMap((action: A & { correlationId: string}) => {
        // use zip() to wait for all actions 
        // and when omitting map((action) => action)
        // so the original action is always returned
      })
    );
  };
}

Из ofType источник , похоже, мне нужно использовать Extract

Обновление

Пример StackBlitz показан здесь

Ответы [ 2 ]

5 голосов
/ 25 июня 2020

Это, по крайней мере, компилирует; Не знаю, делает ли он то, что вам нужно.

public effect3$: Observable<Action> = createEffect(() => {
  const a:Action[]= []

  return this.actions$.pipe(
    ofType(doSomething),
    this.someCustomOperatorReturningStaticTypes(),
    this.thisWontWork(a),
    tap(({aCustomProperty}) => {
      // The type is inferred
      console.log(aCustomProperty);
    }),
  )
});

private thisWontWork<A extends Action>(actionsToWaitFor$: Action[]): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      tap(() => {
        console.log('Should work')
      })
    )
  }
}

Мне не удалось запустить его в StackBlitz, есть подсказка?

Надеюсь, это поможет

2 голосов
/ 07 июля 2020

Столкнулся с этим вопросом, и я подумал, что добавлю пару объяснений на случай, если вам все еще интересно, и если кто-нибудь столкнется с этим в будущем!

Во-первых - в вашем примере вы слишком много обдумываете в терминах возвращаемого типа.

Если вы хотите вывести что-то в качестве типа, в котором оно передается (например, здесь вы хотите, чтобы ваше действие возвращалось в том виде, в каком оно есть), вы просто возвращаете тот же generi c, который вы переходите - как в ответе сказано:

function foo<A>(val: A): A {
  return val;
}

Если я вызываю foo () с номером, я получаю его обратно. Если я вызываю его с определенным типом действия c, то происходит то же самое. Вот почему вам просто нужен return type OperatorFunction<A, A> - то же самое, то же самое.

Второй - как работает ofType?

Ключевым моментом здесь является то, что вам нужен некоторый набор действий который вы сужаете.

Использование createAction позаботится об этом за вас, но, например, я создам это вручную.

type FooAction = { type: 'FOO_ACTION' };
type BarAction = { type: 'BAR_ACTION' };

// This type gets built by you using createAction - this is your Action type
type MyActions = FooAction | BarAction;

Далее нам нужен тип, который будет возьмите наш набор действий (MyActions) и сузите его до определенного c действия в зависимости от типа.

type NarrowActionType<T extends { type: any }, K extends string> = T extends { type: K }
  ? T
  : never;

Мы можем проверить, что это работает:

type NarrowedActionTest = NarrowActionType<MyActions, 'FOO_ACTION'>;

Verifying NarrowedActionTest works in TS playground

ofType is a higher order function which first accepts a string, and then the observable stream to operate on. I'm not going to use observables to keep it simpler, but it's the same principle.

In essence we want something along the lines of (type: string) => (action: Action) => action is NarrowActionType for the type signature.

  • NarrowActionType needs the overall action type as it's first type parameter, which we will call A
  • NarrowActionType needs the string literal type which we are using to narrow with, which we will call T
  • T is going to be a string, so T extends string
  • We only know the type of T to begin with, and then the type of A once the object stream is applied, so let's use one generic parameter at each function application
  • The function is going to ultimately check if A.type is the same as type. So we should enforce this exists, with A extends { type: any }
function isOfType<T extends string>(type: T) {
    return <A extends { type: any }>(action: A): action is NarrowActionType<A, T> => {
        return action.type === type
    }
}

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


const myAction: MyActions = { type: 'FOO_ACTION' };
const isFooAction = isOfType('FOO_ACTION')(myAction);

if (isOfType('FOO_ACTION')(myAction)) {
    type myNarrowedAction = typeof myAction; // is FooAction
}

Полная ссылка на игровую площадку здесь: https://www.typescriptlang.org/play/#code / C4TwDgpgBAcghgJwQewO4EEDGwCWyB2AKuBADyFQQAewE + AJgM5QDeUokAXFHPiFAF8ANFADSlGnSZRGwBDnwBzAHxQAvFArVaDZmw4Ru4gQCgoUAPyazUbvggA3CAgDcJkwagAxZMiy4CdVZ2Em4Aci8AeUiAfXQAYUIASUiYMME3TwAhRH88fCD9UKgwrPQAJTjElLSM9xMAegbNAAscZk9FCGBmACMAVxwAG2AoXv4QZH6ofsYFRShMBAg4WjzAgFp2NuZ2qEn + hCh1goMPEigAWRAT5g0fP2 x8qAAfKByEE7dzyFhEFFQEHoJ0IEFkQXgSDQIJIpGutxEEWiVWSqTCym + ADN + vgnoF2pFMcRIOQJDppLJ5EplAAKAzcQgASlYNnMy2AhwKpHQZKkehCXB4fEEtLgePw3HQjO4YoCBT2kIBMJJ6BEhFUalULFZ5l17M5PHFADpPGozQKIDrTKYTJgCOCALY3cXceHiu7BeklKKxBKo2oCNx2-Dg9oPE5BAlEkg0pG + 6poxk0p0nRnfHCYqA0qPEiCxn0omphJMp8WM5na3UWqBOxVoIERjQGZCZ0tylxQJpQPbh8UmUxAA

...