You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NGRX选择器使用filter操作符抛出source.lift is not a function异常

NGRX选择器复用succeeded校验逻辑的问题

背景

我在使用NGRX与Angular 13的项目中,定义了如下状态:

export const initialState: SomethingState = {
  data: null,
  succeeded: false,
  message: "",
  errors: null,
};

并创建了对应的特征选择器:

export const selectSomethingFeatureSelector = createFeatureSelector<SomethingState>("something");

每次API调用完成且无错误时,状态中的succeeded值会更新为true。

在使用该特征选择器的组件中,我都会校验succeeded是否为true(有时会搭配take(1)自动取消订阅),示例代码如下:

this.store.select(selectSomethingFeatureSelector)
.pipe(
      filter((state) => state.succeeded),
      take(1),
      map((state) => {
        return state.data.items;
      })
);

由于所有状态都包含succeeded字段,这段逻辑重复出现,因此我希望将其迁移至选择器中。

最初我尝试了如下写法,但Observable仍会在succeeded为false时触发:

export const selectSomething = () =>
  createSelector(
    selectSomethingFeatureSelector,
    (state: SomethingState) => {
      if(state.succeeded) {
        return state.data.items;
      }
    }
);

之后我参考资料尝试了另一种写法,VS Code未提示错误,但控制台抛出异常:

TypeError: source.lift is not a function at filterOperatorFunction

写法如下:

export const selectSomething = () =>
  createSelector(
    selectSomethingFeatureSelector, 
    filter((state: SomethingState) => state.succeeded === true),
    (state: SomethingState) => {
      return state.data.items;    
  }
);

我的问题分为两部分:

  1. 将该逻辑迁移至选择器是否合理,还是应保留在组件中?
  2. 若选择器方案可行,我哪里出错了?

解答

问题1:逻辑迁移至选择器是否合理?

完全合理。NGRX选择器的核心作用之一就是复用状态派生逻辑,避免在多个组件中重复编写相同的状态转换、过滤逻辑。既然所有状态都有succeeded字段,把“校验succeeded为true后提取数据”的逻辑封装到选择器中,能大幅减少组件代码冗余,还能保证逻辑一致性。

不过要注意:take(1)属于Observable的订阅控制逻辑,更适合留在组件中——因为不同组件可能有不同的订阅需求(比如有的需要持续监听状态变化,而非只取一次)。

问题2:选择器写法的错误分析及正确实现

第一种写法的问题

你最初的写法中,选择器会始终返回值:当succeeded为false时,函数没有明确返回值,实际会返回undefined。而NGRX选择器只要源状态变化就会触发计算,不管返回的是undefined还是有效值,所以Observable还是会发出值,达不到过滤的效果。

第二种写法的问题

你错误地把RxJS的filter操作符直接放到了createSelector的参数里。createSelector的参数只能是其他选择器或者纯函数,不能是RxJS操作符——filter是用来处理Observable流的,不是处理状态快照的纯函数,所以才会抛出source.lift is not a function的错误。

正确实现方案

有两种可行的方式:

方式一:创建带过滤逻辑的高阶选择器

如果希望选择器只在succeeded为true时返回有效值,同时避免返回undefined,可以结合RxJS操作符封装一个高阶函数:

// 通用的过滤succeeded为true的高阶选择器
export const selectWhenSucceeded = <T>(selector: MemoizedSelector<object, T>) => {
  return (store: Store) => store.select(selector).pipe(
    filter(state => state.succeeded),
    map(state => state.data.items)
  );
};

// 针对SomethingState的具体选择器
export const selectSomethingData = selectWhenSucceeded(selectSomethingFeatureSelector);

// 组件中使用
this.selectSomethingData(this.store).pipe(take(1)).subscribe(items => {
  // 处理items
});
方式二:在选择器中返回可选值,组件中配合过滤

如果想严格使用NGRX的纯选择器,可以让选择器返回T | undefined,然后在组件中保留filter(但复用选择器已经减少了大部分重复逻辑):

export const selectSomethingData = createSelector(
  selectSomethingFeatureSelector,
  (state: SomethingState) => state.succeeded ? state.data.items : undefined
);

// 组件中使用
this.store.select(selectSomethingData).pipe(
  filter(items => items !== undefined),
  take(1)
).subscribe(items => {
  // 处理items
});

如果你的所有状态结构类似(都有succeeded和data),还可以创建一个通用的泛型选择器,进一步复用:

export const selectDataWhenSucceeded = <T, U>(
  featureSelector: MemoizedSelector<object, { succeeded: boolean; data: T }>,
  dataExtractor: (data: T) => U
) => {
  return createSelector(
    featureSelector,
    state => state.succeeded ? dataExtractor(state.data) : undefined
  );
};

// 针对SomethingState的使用
export const selectSomethingItems = selectDataWhenSucceeded(
  selectSomethingFeatureSelector,
  (data) => data.items
);

内容的提问来源于stack exchange,提问作者Ben Thomson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 02:15:59