我希望能够写actions$.ofType<MyAction>().map(a => ...)
代替actions$.filter(a => a.type === ActionType.MY_ACTION).map((a: MyAction) => ...)
。
我按照本文中列出的模式https://spin.atomicobject.com/2017/07/24/redux-action-pattern-typescript/尝试在Angular中使用Redux时保存一些样板文件。 TL; DR是您可以在操作的类型字段中使用字符串枚举来简化reducer代码而不会牺牲类型安全性。它涉及制作如下所示的动作界面:
export enum ActionType {
ADD = "Add",
NEGATE = "Negate"
}
export interface AddAction extends Action {
type: ActionType.ADD;
amount: number;
}
export interface NegateAction extends Action {
type: ActionType.NEGATE;
}
export type EveryAction = AddAction | NegateAction | DummyAction;
在Reducer中,您现在可以编写如下代码:
switch (action.type) {
case TypeKeys.ADD: {
// The type of action is narrowed to AddAction here.
}
case TypeKeys.NEGATE: {
// ...and to NegateAction here.
}
}
我想在写剧本时使用类似的模式&#34; epics&#34;使用redux-observable。我想要一个函数ofType<T>()
,它同时按操作对象上的类型值进行过滤,并缩小为正确的派生操作类型。注意,redux-observable已经有一个非泛型的ofType(),它将类型值作为参数,但是:1)我宁愿将Action接口类型作为泛型参数而不是类型值传递,并且2)调用内置的ofType()之后的动作类型仍然只是Action。
这就是我想要的代码:
export class Epics {
epics: Array<Epic<EveryAction, RootState>>;
constructor() {
this.epics = [
this.afterAdd,
this.afterNegate
];
}
public afterAdd = (actions$: Observable<EveryAction>) => actions$
.ofType<AddAction>()
.do(a => console.log(`Added ${a.amount} to value`)); // a is AddAction here
public afterNegate = (actions$: Observable<EveryAction>) => actions$
.ofType<NegateAction>()
.do(a => console.log("Negated value"));
}
但是,我无法弄清楚如何写ofType<T>()
。我一直在使用不同的TypeScript功能,如索引类型和keyof,但我无法让它工作。有可能吗?
我希望函数为ofType<T>()
,但如果它必须是ofType(ActionType.INCREMENT)
那么那也很好,只要它自动缩小动作类型而不必指定泛型类型和类型键。
答案 0 :(得分:3)
如果您愿意将Action
作为课程进行调整,那么您可以利用缩小班级类型和a definition of filter
来接受类型警卫。另请注意,这会忽略使用ActionTypes
枚举进行模式匹配的模式。
最重要的是,filter
中的箭头功能在没有明确的类型后卫的情况下不会缩小,即使它包裹了一个类型后卫,因此工厂方法使我们的生活更加轻松:
function ofType<S extends EveryAction>(b: new () => S): (a: EveryAction) => a is S) {
return (a: EveryAction): a is S => a instanceof b;
}
将Action
作为具有空构造函数的类,例如:
export class AddAction implements Action { // ...
然后使用就像你想要的那样好而短:
public afterAdd = (actions$: Observable<EveryAction>) => actions$
.filter(ofType(AddAction))
.do(a => a.amount);
您想要的一种模式匹配方法不起作用:
function ofType<S extends EveryAction>(t: ActionType): (a: EveryAction) => a is S) {
return (a: EveryAction): a is S => a.type === t;
}
// used as:
(a as Observable<EveryAction>).filter(ofType(ActionType.ADD)) // S is still EveryAction :/
不幸的是,TypeScript还不是那么聪明。
答案 1 :(得分:1)
这样的方法怎么样?你可以使它成为扩展方法。我使用redux-typescript-actions
生成actionCreators,该名称已将名称更改为typescript-fsa
现在
import { combineEpics, ActionsObservable } from 'redux-observable';
import { Observable } from 'rxjs/Observable';
import { isType, Action, ActionCreator } from 'redux-typescript-actions';
export function ofType<T>(action$: ActionsObservable<Action<T>>, actionCreator: ActionCreator<T>): Observable<Action<T>> {
return action$.filter((action: ActionCreator<T>) => isType(action, actionCreator));
}
用法:
export const todoEpic = action$ => ofType(action$, actions.loadTodoStarted)
如果您不想使用typescript-fsa
,则应使用is
运算符编写方法,明显将Action和ActionCreator更改为您的类型。
export function isType<P>(action: ReduxAction, actionCreator: ActionCreator<P>): action is Action<P>{
return action.type === actionCreator.type;
}
答案 2 :(得分:1)
如果您使用类作为动作创建者,则可以执行您想要的操作。
动作将作为类实例创建,但该机制不依赖于它们作为类实例保留并且不使用instanceof
。序列化的动作,发送到Redux DevTools然后使用时间旅行调试重放仍然可以使用该机制。仅使用类,以便存储类型的静态副本。
无论如何,这就是它的样子:
export enum ActionType {
ADD = "Add",
NEGATE = "Negate"
}
export class AddAction implements Action {
static readonly type = "ADD";
readonly type = "ADD";
constructor (public amount: number) {}
}
请注意,静态和实例type
属性均为readonly
。它们需要被推断为字符串文字类型 - 你的reducer代码表明你正在使用一个有区别的联合来缩小行动。
对于ofType
运算符,需要使用类型保护。因为该类包含静态type
,所以您可以像这样实现运算符:
import { Observable } from "rxjs/Observable";
import { filter } from "rxjs/operator/filter";
interface ActionCtor<T extends string, A extends Action> {
type: string;
new (...args: any[]): A;
}
function ofType<T extends string, A extends Action>(
this: Observable<Action>, creator: ActionCtor<T, A>
): Observable<A> {
return filter.call(this, (a: Action): a is A => a.type === creator.type);
}
Observable.prototype.ofType = ofType;
declare module "rxjs/Observable" {
interface Observable<T> {
ofType: typeof ofType;
}
}
您可以将操作符与此类操作创建者一起使用,它将与普通的JSON操作一起使用:
const actions = Observable
.of<Action>({ type: "ADD", amount: 42 }, { type: "OTHER", amount: "forty-two" })
.ofType(AddAction)
.subscribe(a => console.log(a.amount)); // 42
最近,我也厌倦了输入这么多样板文件,所以我写了一个基于这个答案机制的库:ts-action
。我还写了一篇文章,解释了与Redux行为有关的两个缩小机制:How to Reduce Action Boilerplate。并且,和你一样,我希望能够使用带有redux-observable的mechansim; ts-action
及其同伴ts-action-operators
会这样做。