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'),
)
