【问题标题】:ngrx: how to pass parameters to selector inside createSelector methodngrx:如何在 createSelector 方法中将参数传递给选择器
【发布时间】:2019-05-01 22:24:01
【问题描述】:

我的店里有一个很简单的状态:

const state = {
 records: [1,2,3],
};

我有一个记录选择器:

export const getRecords = createSelector(getState, (state: State) => state.records));

我现在想要的是有单独的选择器来按索引获取每条记录。 为此,我想以这种方式创建一个带有道具的通用选择器:

export const getRecordByIndex = createSelector(
getRecords,
(state: State, { index }) => state.records[index]),
);

然后创建几个特定的​​选择器 e。 g.:

export const getFirstRecord = createSelector(
getRecordByIndex(/* somehow pass index = 0 to this selector */),
(firstRecord) => firstRecord),
);

但是当我们在 createSelector 方法中使用它们时,我没有发现任何提及如何将参数传递给带有 props 的选择器。有可能吗?

【问题讨论】:

    标签: angular ngrx


    【解决方案1】:

    这可以通过@timdeschryver 在他的comment regarding the deprecation of props from ngrx selectors 中描述的“工厂选择器”来完成。

    使用 OP 的请求,我将有以下实现:

    export const getRecordByIndex = (index: number) => createSelector(
       getRecords, (records) => records[index]
    );
    

    然后可以用作:

    const record$ = this.store.select(getRecordByIndex(1));
    

    现在“使用带有道具的选择器”标记为Deprecated in the ngrx documentation,这似乎是首选方式。

    【讨论】:

      【解决方案2】:

      来自这篇博文:https://timdeschryver.dev/blog/parameterized-selectors

      从 NgRx 6.1 开始,选择器也接受一个额外的 props 参数。哪一个 意味着您现在可以如下定义选择器:

      export const getCount = createSelector(
        getCounterValue, 
        (counter, props) => counter * props.multiply
      );
      
      this.counter = this.store.pipe(
        select(fromRoot.getCount, { multiply: 2 })
      );
      

      啊...但是重新阅读您的问题,您是在问如何构建另一个使用此选择器的选择器?上面链接的文章建议构建一个工厂函数。

      【讨论】:

      【解决方案3】:

      我设法为选择器提供了参数,并稍微改变了我的使用方式。示例:

      选择器(这里我不使用单独的功能)

      export const selectReferentialsState = (state: AppState) => state.referentials;
      
      export const referentialDataSelector = createSelector(
          selectReferentialsState,
          (state: ReferentialsState, props: { refType: Referential}) => state.data[props.refType]
      );
      

      用法

      this.availableRoles$ = this.store.select(state => referentialDataSelector(state, { refType: Referential.Role}));
      

      高级用法(带参数的级联选择器)

      我将提供另一个示例来涵盖更复杂的场景,即必须定义一个依赖于需要参数的选择器 (props) 的选择器。这还包括更简单的使用语法(pipe + select):

      export const selectQuestionnaireTranslationInfo = createSelector(
          selectQuestionnaireTranslationState,
          (state: QuestionnaireTranslationState, props: { formId: number}) => state.entities[props.formId]
      );
      
      export const selectQuestionnaireLanguageProgress = createSelector(
          selectQuestionnaireTranslationInfo,
          (state: QuestionnaireTemplateTranslationFullInfo, props: {formId: number, langId: number }) =>
              state?.languageInfo?.find(li => li.spTranslationLanguageId === props.langId)
      );
      
      export const selectQuestionnaireLanguageProgressCount = createSelector(
          selectQuestionnaireLanguageProgress,
          (state: QuestionnaireTemplateTranslationLanguageInfo) =>
              state?.translatedResourceCount
      );
      

      用法:

      const props = { formId: this.templateId, langId: this.languageId};
      this.progressCount$ = this.store.pipe(select(selectQuestionnaireLanguageProgressCount, props));`
      

      正如Ian Jamieson 已经指出的那样,props 被合并并在选择器链中可用(这就是为什么最后一个选择器不需要显式声明 props,它们是“继承的”)。

      【讨论】:

      • 很高兴看到您的使用部分
      • 这应该在文档中...键入的道具和信息从哪里获得state
      【解决方案4】:

      使用此库中的 mapSelectors reselect-mapper

      请看下面的例子

      import { mapSelectors } from "reselect-mapper"
      
      const getUserById = (state: State, id: string) => state.users[id]
      const getPostByIndex = (state: State, index: number) => state.posts[index]
      
      // now you can combine your selectors using mapSelectors
      const getUserAndPost = mapSelectors({
               user: getUserById,
               post: getPostByIndex
            }, map => map); // map signature is { user: User, post: Post }
      // getUserAndPost selector has the following signature
      // (state: State, params: { user: string, post: number }) => ({ user: User, post: Post })
      

      【讨论】:

        【解决方案5】:

        我正在使用"@ngrx/entity": "7.2.0",,我可以看到道具被传递给每个选择器,例如在我正在调用的组件中:

        this.isActive$ = this.store.pipe(select(fromClient.isActive, { id: 'someid' }));
        

        然后在我的减速器中,我有以下内容:

        export const getState = createFeatureSelector('state');
        
        export const getEntity = createSelector(
          getState,
          (state, props) => {
            // do something with props.id to get an entity then:
            return state;
          }
        );
        
        export const isActive: = createSelector(
          getEntity, // props are passed to here
          (state: any) => { // i don't add the props argument here, as i don't need them
            return state.isActive;
          }
        );
        

        【讨论】:

        • 刚刚验证这适用于当前稳定的@ngrx v10。
        【解决方案6】:

        您可以使用投影仪功能:

        export interface Record {
          // Some sort of record interface
        }
        
        export interface State {
          records: Record[];
        }
        
        export const getRecords = createSelector(
          getState,
          (state: State): Record[] => state.records)
        );
        
        export const getRecordByIndex = createSelector(
          getRecords,
          (records: Record[], { index }) => records[index]),
        );
        
        export const getFirstRecord = createSelector(
          getRecords,
          (records: Record[]) => getRecordByIndex.projector(records, { index: 0 })
        );
        

        【讨论】:

        • 这是实际问题的唯一答案,所有其他人都没有抓住重点。这个解决方案的缺点是getFirstRecord 必须知道getRecordByIndex 的输入选择器getRecords 才能将状态传递给投影仪功能。 getRecordByIndex 的实现正在泄漏到 getFirstRecord
        猜你喜欢
        • 2011-04-22
        • 1970-01-01
        • 1970-01-01
        • 2012-08-31
        • 2022-01-18
        • 2017-09-01
        相关资源
        最近更新 更多