【问题标题】:How to reduce CRUD boilerplate in NGXS?如何减少 NGXS 中的 CRUD 样板?
【发布时间】:2019-06-02 16:48:31
【问题描述】:

假设我的商店中有两个子状态,每个子状态一个:1.users 2.文章

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

为了实现这一点,我必须为他们两个都做至少 7 个动作。

以文章操作列表为例:

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

将为用户以及他们的 reducer/action 处理程序创建所有 8 个操作。这会为一些非常常见和琐碎的事情产生大量的样板。

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

【问题讨论】:

    标签: ngxs


    【解决方案1】:

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

    首先,我可以立即摆脱这三个动作——LoadingStartLoadingFinishLoadingError。我不确定你是否一定会从中获利,只是因为 NGXS 会为你返回一个错误给onError 回调。

    但这也取决于您所说的“加载”,它适用于所有请求还是仅适用于“文章”?如果只是“文章” - 创建一个名为 loading 的状态属性要容易得多,例如:

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

    由于 NGXS 可以执行异步工作,因此在您返回 PromiseObservable 的情况下,最好应用 finalize 运算符,它会在 observable 完成或错误时调用函数,因此您可能不会需要LoadingFinishLoadingError

    第二个问题是你剩下的 5 个动作。这也取决于您所说的“样板”,基本上您不能将 1 个动作用于 5 个动作: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,它可以通过引入声明性来减少您的代码。

    【讨论】:

      猜你喜欢
      • 2018-09-16
      • 1970-01-01
      • 1970-01-01
      • 2013-03-23
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多