ค้นหาเว็บไซต์

javascript - NgRx เลือกข้อผิดพลาดเมื่อพยายามเข้าถึงคุณสมบัติที่ซ้อนกัน


ฉันกำลังได้รับ TypeErrors เมื่อใช้ฟังก์ชัน select ของ NgRx ตอนเข้าถึงคุณสมบัติภายใน

ฉันได้ตั้งค่าร้านค้ารากของฉันใน app.module.ts ดังนี้:

StoreModule.forRoot({ app: appReducer }),

ที่ตัวลดของแอปเป็นเพียงตัวลดมาตรฐาน มันตั้งค่ารัฐอย่างถูกต้อง; ฉันสามารถเห็นสิ่งนั้นในเครื่องมือพัฒนา redux ตัวเลือกสำหรับคุณสมบัติซ้อนบางอย่างที่เกิดข้อผิดพลาดคือ:

const getAppFeatureState = createFeatureSelector<IAppState>('app');

export const getAppConfig = createSelector(getAppFeatureState, state => {
  return state.appConfig.data;
});

export const getConfigControls = createSelector(getAppConfig, state => {
  console.log({ state }) // logs values from initial state
  return state.controls;
});

export const getConfigDropdowns = createSelector(
  getConfigControls,
  state => state.dropdowns,
);

เมื่อฉันสมัครรับตัวเลือกเหล่านี้ใน app.compontent.ts แบบนี้

ngOnInit() {
  this.store.dispatch(new appActions.LoadAppConfig());
  this.store
    .pipe(select(appSelectors.getConfigDropdowns))
    .subscribe(data => {
      console.log('OnInit Dropdowns Data: ', data);
    });
}

app.component.ts:31 ข้อผิดพลาด TypeError: ไม่สามารถอ่านคุณสมบัติ 'dropdowns' ของค่า null ได้ ที่ app.selectors.ts:18

เมื่อฉันเพิ่มการบันทึกลงในตัวเลือกที่อยู่สูงขึ้นในลำดับ ฉันสามารถเห็นได้ว่าสิ่งที่บันทึกเพียงอย่างเดียวคือค่าของ initialState ซึ่งถูกตั้งค่าเป็น null ฉันไม่คิดว่าฟังก์ชันตัวเลือกนี้ควรถูกเรียกจนกว่าค่าจะเปลี่ยนจากค่าตั้งต้น แต่เนื่องจากมันไม่ถูกเรียก จึงไม่น่าแปลกใจที่ฉันได้รับข้อผิดพลาดนี้ เพราะมันพยายามเข้าถึงคุณสมบัติบน null จำเป็นหรือไม่ที่ initialState จะต้องมีโครงสร้างทั้งหมดของคุณสมบัติที่ซ้อนกันในอนาคตทั้งหมดเพื่อไม่ให้ตัวเลือกของฉันเกิดข้อผิดพลาด?

ฉันจะป้องกันไม่ให้ตัวเลือกนี้ทำงานเมื่อค่าของมันไม่เปลี่ยนแปลงได้อย่างไร?

นอกจากนี้ การกำหนดค่า StoreModule.forRoot ถูกต้องหรือไม่? มันค่อนข้างสับสนสำหรับฉันที่การสร้าง store "root" กลับสร้างคีย์ app ใน redux store ของฉันขนานกับ store ของโมดูลของฉัน กล่าวคือ store ของโมดูลไม่ได้อยู่ใต้ app

แก้ไข :

การเพิ่มโครงสร้างทั่วไปของ app.reducer.ts ผมใช้ immer เพื่อลดโค้ดซ้ำที่จำเป็นสำหรับการอัปเดตคุณสมบัติที่ซ้อนกัน อย่างไรก็ตามผมก็ได้ลองใช้ reducer แบบดั้งเดิมที่ใช้ spread operator ทั่วทั้งโค้ดแล้ว และมันทำงานเหมือนกัน

import produce from 'immer';

export const appReducer = produce(
  (
    draftState: rootStateModels.IAppState = initialState,
    action: AppActions,
  ) => {
    switch (action.type) {
      case AppActionTypes.LoadAppConfig: {
        draftState.appConfig.meta.isLoading = true;
        break;
      }
      /* more cases updating the properties accessed in problematic selectors */
      default: {
        return draftState; // I think this default block is unnecessary based on immer documentation
      }
    }
}

แก้ไข: เพิ่ม initialState:

const initialState: rootStateModels.IAppState = {
  user: null,
  appConfig: {
    meta: {isError: false, isLoading: false, isSuccess: false},
    data: {
      controls: {
        dropdowns: null,
      }
    },
  },
};

คำตอบ #1

เนื่องจากคุณได้ปรับปรุงคำถามของคุณ คำตอบคือ https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged

มันอนุญาตให้ส่งค่าก็ต่อเมื่อค่าถูกเปลี่ยนแปลงแล้วเท่านั้น

store.pipe(
  map(state => state.feature.something),
  distinctUntilChanged(),
)

ต้องมีการเปลี่ยนแปลง state.feautre.something แล้ว

วิธีที่ถูกต้องคือการใช้ฟังก์ชัน createSelector ที่ส่งคืนตัวเลือกที่จดจำค่าได้ซึ่งทำงานในลักษณะเดียวกับ distinctUntilChanged

คำตอบ #2

คุณสามารถใช้ตัวดำเนินการ filter เพื่อให้แน่ใจว่ามันจะปล่อยค่าตามเฉพาะค่าที่ถูกต้อง และหลังจากนั้นคุณสามารถใช้ตัวดำเนินการ pluck เพื่อปล่อยค่าของคุณสมบัติย่อยที่เกี่ยวข้อง

store.pipe(
  filter(value => state.feature.something),
  pluck('feature', 'something'),
)