@addapptables/ngrx-actions 中文文档教程

发布于 4年前 浏览 4 项目主页 更新于 3年前

NGRX Actions

注意:这个库是对 ngrx-actions 库的改进。

NGRX 的动作/减速器实用程序。 它提供了一些功能来使 NGRX/Redux 更具 Angular-tastic。

  • @Store(MyInitialState): Decorator for default state of a store.
  • @Action(...MyActionClass: Action[]): Decorator for a action function.
  • @Effect(...MyActionClass: Action[]): Decorator for a effect function.
  • ofAction(MyActionClass): Lettable operator for NGRX Effects
  • createReducer(MyStoreClass): Reducer bootstrap function
  • @Select('my.prop'): Select decorator

查看变更日志了解最新变更。

Whats this for?

这是在使用 Redux 模式时帮助减少样板代码的。 也就是说,这是它提供的高级功能:非常

  • Reducers become classes so its more logical organization
  • Automatically creates new instances so you don't have to handle spreads everywhere
  • Enables better type checking inside your actions
  • Reduces having to pass type constants by using type checking

简单 (<100LOC),您可以选择要在何处使用它。

Getting Started

首先,让我们通过 npm 安装包:

npm i @addapptables/ngrx-actions --S

Reducers

接下来,创建一个操作,就像您今天使用 NGRX 所做的那样:

export class MyAction {
  readonly type = 'My Action';
  constructor(public payload: MyObj) {}
}

然后您创建一个类并使用包含的 Store 装饰器装饰它 reducer 的初始状态。 在那个类中你定义方法 用带有动作类参数的 Action 装饰器装饰 你想匹配它。

import { Store, Action } from 'ngrx-actions';

@Store({
    collection: [],
    selections: [],
    loading: false
})
export class MyStore {
    @Action(Load, Refresh)
    load(state: MyState, action: Load) {
        state.loading = true;
    }

    @Action(LoadSuccess)
    loadSuccess(state: MyState, action: LoadSuccess) {
        state.collection = [...action.payload];
    }

    @Action(Selection)
    selection(state: MyState, action: Selection) {
        state.selections = [...action.payload];
    }

    @Action(DeleteSuccess)
    deleteSuccess(state: MyState, action: DeleteSuccess) {
        const idx = state.collection.findIndex(r => r.myId === action.payload);
        if (idx === -1) {
          return state;
        }
        const collection = [...state.collection];
        collection.splice(idx, 1);
        return { ...state, collection };
    }
}

你可能会注意到,我不返回状态。 那是因为如果它看不到 从动作中返回的状态,它检查该状态是否是 对象或数组并自动为您创建一个新实例。 如果你是 改变深层嵌套的属性,你仍然需要自己处理这些。

你仍然可以自己返回状态,它不会弄乱它。 这很有帮助 因为如果状态没有改变或者你有一些复杂的逻辑在进行。 这可以是 在 deleteSuccess 操作中看到。

在上面你可能会注意到,第一个动作有多个动作类。 那是因为 @Action 装饰器可以接受单个或多个动作。

要将其连接到 NGRX,您所要做的就是调用 createReducer 函数传递 你的商店。 现在传递 myReducer 就像传递内部带有 switch 语句的函数一样。

import { createReducer } from 'ngrx-actions';
export function myReducer(state, action) { return createReducer(MyStore)(state, action); }

在上面的示例中,我返回了一个返回我的 createReducer 的函数。 这是因为 AOT 抱怨说 装饰器不支持函数表达式 如果我们只是赋值 createReducer 方法直接。 这是一个已知问题,其他 NGRX 问题也受到影响。

接下来,像往常一样将其传递给您的 NGRX 模块:

@NgModule({
   imports: [
      StoreModule.forRoot(reducers, { metaReducers }),
      ReduxRegisterModule.forRoot({
         pizza: PizzaStore
      })
   ]
})
export class AppModule {}

您也可以选择将您的商店直接提供给 ReduxRegisterModule ,它将处理 为您创建减速器,还可以在您的商店中使用 DI。 所以而不是 在forRootforFeature 中用StoreModule 描述,我们在ReduxRegisterModule 上调用它们。

@NgModule({
   imports: [
      ReduxRegisterModule.forFeature('menu', {
          sidebar: MenuStore
      })
   ]
})
export class ChildModule {}

Effects

如果你想使用 NGRX 效果,我创建了一个可让你使用的操作符 将操作类作为参数传递,如下所示:

import { ofAction } from 'ngrx-actions';

@Injectable()
export class MyEffects {
    constructor(
        private update$: Actions,
        private myService: MyService
    ) {}

    @Effect()
    Load$ = this.update$.pipe(
        ofAction(Load),
        switchMap(() => this.myService.getAll()),
        map(res => new LoadSuccess(res))
    );
}

@Effect 您可以在商店中定义它以执行异步操作。

@Store({ delievered: false })
export class PizzaStore {
    constructor(private pizzaService: PizzaService) {}

    @Action(DeliverPizza)
    deliverPizza(state) {
        state.delivered = false;
    }

    @Effect(DeliverPizza)
    deliverPizzaToCustomer(state, { payload }: DeliverPizza) {
        this.pizzaService.deliver(payload);
    }
}

效果总是在动作之后运行。

Selects

我们没有遗漏选择器,有一个 Select 装饰器接受(深)路径字符串。 这看起来像:

@Component({ ... })
export class MyComponent {
    // Functions
    @Select((state) => state.color) color$: Observable<string>;

    // Array of props
    @Select(['my', 'prop', 'color']) color$: Observable<strinv>;

    // Deeply nested properties
    @Select('my.prop.color') color$: Observable<string>;

    // Implied by the name of the member
    @Select() color: Observable<string>;

    // Remap the slice to a new object
    @Select(state => state.map(f => 'blue')) color$: Observable<string>;
}

您可以开始在任何组件中使用它。 它也适用于功能商店。 注意:由于 TypeScript#4881,Select 装饰器存在缺乏类型检查的限制。

NGRX Actions

Note: This library is an improvement on the ngrx-actions library.

Actions/reducer utility for NGRX. It provides a handful of functions to make NGRX/Redux more Angular-tastic.

  • @Store(MyInitialState): Decorator for default state of a store.
  • @Action(...MyActionClass: Action[]): Decorator for a action function.
  • @Effect(...MyActionClass: Action[]): Decorator for a effect function.
  • ofAction(MyActionClass): Lettable operator for NGRX Effects
  • createReducer(MyStoreClass): Reducer bootstrap function
  • @Select('my.prop'): Select decorator

See changelog for latest changes.

Whats this for?

This is sugar to help reduce boilerplate when using Redux patterns. That said, here's the high level of what it provides:

  • Reducers become classes so its more logical organization
  • Automatically creates new instances so you don't have to handle spreads everywhere
  • Enables better type checking inside your actions
  • Reduces having to pass type constants by using type checking

Its dead simple (<100LOC) and you can pick and choose where you want to use it.

Getting Started

To get started, lets install the package thru npm:

npm i @addapptables/ngrx-actions --S

Reducers

Next, create an action just like you do with NGRX today:

export class MyAction {
  readonly type = 'My Action';
  constructor(public payload: MyObj) {}
}

then you create a class and decorate it with a Store decorator that contains the initial state for your reducer. Within that class you define methods decorated with the Action decorator with an argument of the action class you want to match it on.

import { Store, Action } from 'ngrx-actions';

@Store({
    collection: [],
    selections: [],
    loading: false
})
export class MyStore {
    @Action(Load, Refresh)
    load(state: MyState, action: Load) {
        state.loading = true;
    }

    @Action(LoadSuccess)
    loadSuccess(state: MyState, action: LoadSuccess) {
        state.collection = [...action.payload];
    }

    @Action(Selection)
    selection(state: MyState, action: Selection) {
        state.selections = [...action.payload];
    }

    @Action(DeleteSuccess)
    deleteSuccess(state: MyState, action: DeleteSuccess) {
        const idx = state.collection.findIndex(r => r.myId === action.payload);
        if (idx === -1) {
          return state;
        }
        const collection = [...state.collection];
        collection.splice(idx, 1);
        return { ...state, collection };
    }
}

You may notice, I don't return the state. Thats because if it doesn't see a state returned from the action it inspects whether the state was an object or array and automatically creates a new instance for you. If you are mutating deeply nested properties, you still need to deal with those yourself.

You can still return the state yourself and it won't mess with it. This is helpful for if the state didn't change or you have some complex logic going on. This can be seen in the deleteSuccess action.

Above you may notice, the first action has multiple action classes. Thats because the @Action decorator can accept single or multiple actions.

To hook it up to NGRX, all you have to do is call the createReducer function passing your store. Now pass the myReducer just like you would a function with a switch statement inside.

import { createReducer } from 'ngrx-actions';
export function myReducer(state, action) { return createReducer(MyStore)(state, action); }

In the above example, I return a function that returns my createReducer. This is because AoT complains stating Function expressions are not supported in decorators if we just assign the createReducer method directly. This is a known issue and other NGRX things suffer from it too.

Next, pass that to your NGRX module just like normal:

@NgModule({
   imports: [
      StoreModule.forRoot(reducers, { metaReducers }),
      ReduxRegisterModule.forRoot({
         pizza: PizzaStore
      })
   ]
})
export class AppModule {}

Optionally you can also provide your store directly to the ReduxRegisterModule and it will handle creating the reducer for you and also enables the ability to use DI with your stores. So rather than describing in forRoot or forFeature with StoreModule, we call them on ReduxRegisterModule.

@NgModule({
   imports: [
      ReduxRegisterModule.forFeature('menu', {
          sidebar: MenuStore
      })
   ]
})
export class ChildModule {}

Effects

If you want to use NGRX effects, I've created a lettable operator that will allow you to pass the action class as the argument like this:

import { ofAction } from 'ngrx-actions';

@Injectable()
export class MyEffects {
    constructor(
        private update$: Actions,
        private myService: MyService
    ) {}

    @Effect()
    Load$ = this.update$.pipe(
        ofAction(Load),
        switchMap(() => this.myService.getAll()),
        map(res => new LoadSuccess(res))
    );
}

@Effect that you can define in your store to perform async operations.

@Store({ delievered: false })
export class PizzaStore {
    constructor(private pizzaService: PizzaService) {}

    @Action(DeliverPizza)
    deliverPizza(state) {
        state.delivered = false;
    }

    @Effect(DeliverPizza)
    deliverPizzaToCustomer(state, { payload }: DeliverPizza) {
        this.pizzaService.deliver(payload);
    }
}

Effects are always run after actions.

Selects

We didn't leave out selectors, there is a Select decorator that accepts a (deep) path string. This looks like:

@Component({ ... })
export class MyComponent {
    // Functions
    @Select((state) => state.color) color$: Observable<string>;

    // Array of props
    @Select(['my', 'prop', 'color']) color$: Observable<strinv>;

    // Deeply nested properties
    @Select('my.prop.color') color$: Observable<string>;

    // Implied by the name of the member
    @Select() color: Observable<string>;

    // Remap the slice to a new object
    @Select(state => state.map(f => 'blue')) color$: Observable<string>;
}

And you can start using it in any component. It also works with feature stores too. Note: The Select decorator has a limitation of lack of type checking due to TypeScript#4881.

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文