如何减少NGXS中的CRUD样板?

时间:2019-06-02 16:48:31

标签: ngxs

想象一下,我的商店中有两个子状态,每个子状态一个: 1。用户 2。文章

这两个都支持CRUD(创建,读取,更新删除)。

要做到这一点,我必须对它们两个进行至少7个动作。

文章操作列表为例

1. GetAllArticle
2. GetArticleById
3. DeleteArticle
4. UpdateArticle
5. CreateArticle
6. LoadingStart
7. LoadingFinish
8. LoadingError

将为用户以及其减速器/动作处理程序创建所有8个动作。这会为大量非常平凡而琐碎的事情带来大量的样板。

我的问题是,是否有办法减少样板和代码重复?

1 个答案:

答案 0 :(得分:1)

我想答案有点晚了,但是:D

首先,我可以立即摆脱三个动作-LoadingStartLoadingFinishLoadingError。我不确定您一定会从中获利,仅仅是因为NGXS会为您向onError回调返回错误。

但这还取决于您所说的“加载”,是应用于所有请求还是仅应用于“文章”?如果仅是“文章”,那么创建一个名为loading的状态属性要容易得多,类似于:

@State({
  name: 'articles',
  defaults: {
    loading: false,
    articles: []
  }
})
export class ArticlesState {}

由于NGXS可以执行异步工作,因此在返回PromiseObservable的情况下-最好应用finalize运算符,该运算符将在可观察的完成或结束时调用一个函数。错误,因此您可能不需要LoadingFinishLoadingError

第二个问题是您剩余的5个动作。这也取决于您所谓的“样板”,基本上您不能对5个动作使用1个动作:D除非它是某种通用动作,否则看起来像这样:

// Let's assume that it's some shared
// `enum` that's used in multiple places
// by multiple states
export const enum CrudOperation {
  GetAll,
  GetById,
  Delete,
  Update,
  Create
}

export class ArticlesCrudAction {
  public static readonly type = '[Articles] Crud action';
  constructor(public operation: CrudOperation, public article?: Article) {}
}

因此,如果您要删除文章-您将分派此类操作:

public deleteArticle(article: Article): void {
  this.store.dispatch(
    new ArticlesCrudAction(CrudOperation.Delete, article)
  );
}

这只是一个伪代码,但是我不喜欢这种方法,因为我以前已经看过它。显式胜于隐式。您将需要编写一些switch-case并确定要使用的服务方法。

如果我是您-我仍将保留这些CRUD动作,因为它们可以使正在发生的事情更加清晰。最终代码如下所示:

function setLoading(loading: boolean) {
  return (state: Readonly<ArticlesStateModel>) => ({ ...state, loading });
}

function startLoading() {
  return setLoading(true);
}

function stopLoading() {
  return setLoading(false);
}

@State<ArticlesStateModel>({
  name: 'articles',
  defaults: {
    loading: false,
    articles: []
  }
})
export class ArticlesState {
  @Selector()
  public static isLoading(state: ArticlesStateModel): boolean {
    return state.loading;
  }

  @Selector()
  public static getArticles(state: ArticlesStateModel): Article[] {
    return state.articles;
  }

  constructor(private articlesService: ArticlesService) {}

  @Action(GetAllArticles)
  public getAllArticles(ctx: StateContext<ArticlesStateModel>) {
    return this.request(
      ctx,
      () => this.articlesService.getAllArticles(),
      articles => ctx.patchState({ articles })
    );
  }

  @Action(GetArticleById)
  public getArticleById(ctx: StateContext<ArticlesStateModel>, { id }: GetArticleById) {
    return this.request(ctx, () => this.articlesService.getArticleById(id));
  }

  @Action(DeleteArticle)
  public deleteArticle(ctx: StateContext<ArticlesStateModel>, { article }: DeleteArticle) {
    return this.request(ctx, () => this.articlesService.deleteArticle(article.id));
  }

  @Action(UpdateArticle)
  public updateArticle(ctx: StateContext<ArticlesStateModel>, { article }: UpdateArticle) {
    return this.request(ctx, () => this.articlesService.updateArticle(article));
  }

  @Action(CreateArticle)
  public createArticle(ctx: StateContext<ArticlesStateModel>, { article }: CreateArticle) {
    return this.request(ctx, () => this.articlesService.createArticle(article));
  }

  private request<T>(
    ctx: StateContext<ArticlesStateModel>,
    request: () => Observable<T>,
    callback?: (response: T) => void
  ) {
    ctx.setState(startLoading());

    return request().pipe(
      tap(response => callback && callback(response)),
      finalize(() => ctx.setState(stopLoading()))
    );
  }
}

您还可以创建一种其他方法,在其中可以修补状态,在发送请求之前将loading属性设置为true,并用finalize用管道传输。

这仍然是非常抽象的,并取决于您的实现。

我们还在3.4.0发行版中引入了state operators,这可能会由于声明性而减少代码。