当 ngrx store state 包含 Map 时,为什么无法识别对此 Map 的更改?
When ngrx store state contains a Map, why are changes to this Map not recognized?
我根据他们的示例应用迁移到了 Ngrx Store v4.1.1 (+ Angular5)。除了一个 SubStore 之外,一切都像以前一样运行良好。该 SubStore 的状态包含一个已更改的地图。但是以某种方式无法识别对此地图的更改。
可以在这里找到工作的 Plunker:https://plnkr.co/edit/2Z77Cq?p=preview
详细代码如下
我的 NgModule 看起来像这样:
import {reducers} from './reducers';
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(reducers)
],
declarations: [ App ],
bootstrap: [ App ],
providers: [Service]
})
我的减速器是这样的:
import {
ActionReducerMap,
createSelector,
createFeatureSelector,
} from '@ngrx/store';
import * as character from './character.reducer';
export interface State {
character: character.State;
}
export const reducers: ActionReducerMap<State> = {
character: character.reducer,
};
/** Character **/
export const getCharacterState = createFeatureSelector<character.State>('character');
export const getCharacter = createSelector(
getCharacterState,
character.getCharacter
);
SubStore Reducer 包含以下代码:
import { Character, Item } from './models';
import * as character from './character';
export interface State {
character: Character;
}
export const initialState: State = {
character: null,
};
export function reducer(state = initialState, action:character.Actions): State {
switch (action.type) {
case character.INIT_CHARACTER:
const char: Character = action.payload;
state.character = char;
console.log('init char', char);
return Object.assign({}, state);
case character.EQUIP_ITEM:
const eqItem: Item = action.payload;
state.character.wardrobeItemIds.set(eqItem.part, eqItem.id);
console.log('eq ITEMMM', eqItem, state.character.wardrobeItemIds);
return Object.assign({}, state);
default:
return state;
}
}
export const getCharacter = (state: State) => state.character;
对应的动作为:
import { Action } from '@ngrx/store';
import { Character, Item } from './models';
export const INIT_CHARACTER = '[Character] Initialized Character';
export const EQUIP_ITEM = '[Character] Equipped Item';
export class InitCharacter implements Action {
readonly type = INIT_CHARACTER;
constructor(public payload: Character) {}
}
export class EqItem implements Action {
readonly type = EQUIP_ITEM;
constructor(public payload: Item) {}
}
export type Actions =
InitCharacter |
EqItem;
现在我在我的服务中用一个新角色初始化 SubStore:
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import {Character, Item} from './models';
import * as fromRoot from './reducers.ts';
import * as CharacterAction from './character.ts';
@Injectable()
export class Service {
character$: Observable<Character>;
constructor(
private store: Store<fromRoot.State>
) {
// listen to the store
this.character$ = this.store.select(fromRoot.getCharacter);
this.character$.subscribe(
(state: any) => console.log('char store triggered. State:', state)
);
// init the wardrobeItemIds Map
const wardrobeItemIds = new Map<string, string>();
wardrobeItemIds.set('part1', 'anyId');
// init the character (this is just a dummy)
let newCharacter: Character = {
baseType: 'anyString',
skinItemIds: [
'string1',
'string2'
],
wardrobeItemIds: wardrobeItemIds
}
this.store.dispatch(new CharacterAction.InitCharacter(newCharacter));
}
addItem(part: string): void {
// add rnd item of given part
const item: EquipItem = {
id: Math.random().toString(),
part: part,
}
this.store.dispatch(new CharacterAction.EqItem(item));
}
}
这导致我的订阅在同一个服务中
this.character$.subscribe(
(state: any) => console.log('char store triggered. State:', state)
);
记录字符,这很好,因为它从 null 变为字符 Object。
现在,如果我调用 addItem()
调用 this.store.dispatch(new CharacterAction.EqItem(item));
,它应该向地图 state.character.wardrobeItemIds
添加一个项目。
这应该会导致 Store Observable 再次触发并且订阅应该记录更改的字符。但不知何故什么也没发生。
已经检查减速器是否正确接收动作。
不确定这只是我的愚蠢还是某种错误?
提前致谢
托比
@ngrx/entity 似乎是商店中更复杂数据结构的预期解决方案。如果您打开它们,使用 Map 将触发新的运行时检查,因为它们既不是不可变的也不是可序列化的。
我根据他们的示例应用迁移到了 Ngrx Store v4.1.1 (+ Angular5)。除了一个 SubStore 之外,一切都像以前一样运行良好。该 SubStore 的状态包含一个已更改的地图。但是以某种方式无法识别对此地图的更改。
可以在这里找到工作的 Plunker:https://plnkr.co/edit/2Z77Cq?p=preview 详细代码如下
我的 NgModule 看起来像这样:
import {reducers} from './reducers';
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot(reducers)
],
declarations: [ App ],
bootstrap: [ App ],
providers: [Service]
})
我的减速器是这样的:
import {
ActionReducerMap,
createSelector,
createFeatureSelector,
} from '@ngrx/store';
import * as character from './character.reducer';
export interface State {
character: character.State;
}
export const reducers: ActionReducerMap<State> = {
character: character.reducer,
};
/** Character **/
export const getCharacterState = createFeatureSelector<character.State>('character');
export const getCharacter = createSelector(
getCharacterState,
character.getCharacter
);
SubStore Reducer 包含以下代码:
import { Character, Item } from './models';
import * as character from './character';
export interface State {
character: Character;
}
export const initialState: State = {
character: null,
};
export function reducer(state = initialState, action:character.Actions): State {
switch (action.type) {
case character.INIT_CHARACTER:
const char: Character = action.payload;
state.character = char;
console.log('init char', char);
return Object.assign({}, state);
case character.EQUIP_ITEM:
const eqItem: Item = action.payload;
state.character.wardrobeItemIds.set(eqItem.part, eqItem.id);
console.log('eq ITEMMM', eqItem, state.character.wardrobeItemIds);
return Object.assign({}, state);
default:
return state;
}
}
export const getCharacter = (state: State) => state.character;
对应的动作为:
import { Action } from '@ngrx/store';
import { Character, Item } from './models';
export const INIT_CHARACTER = '[Character] Initialized Character';
export const EQUIP_ITEM = '[Character] Equipped Item';
export class InitCharacter implements Action {
readonly type = INIT_CHARACTER;
constructor(public payload: Character) {}
}
export class EqItem implements Action {
readonly type = EQUIP_ITEM;
constructor(public payload: Item) {}
}
export type Actions =
InitCharacter |
EqItem;
现在我在我的服务中用一个新角色初始化 SubStore:
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import {Character, Item} from './models';
import * as fromRoot from './reducers.ts';
import * as CharacterAction from './character.ts';
@Injectable()
export class Service {
character$: Observable<Character>;
constructor(
private store: Store<fromRoot.State>
) {
// listen to the store
this.character$ = this.store.select(fromRoot.getCharacter);
this.character$.subscribe(
(state: any) => console.log('char store triggered. State:', state)
);
// init the wardrobeItemIds Map
const wardrobeItemIds = new Map<string, string>();
wardrobeItemIds.set('part1', 'anyId');
// init the character (this is just a dummy)
let newCharacter: Character = {
baseType: 'anyString',
skinItemIds: [
'string1',
'string2'
],
wardrobeItemIds: wardrobeItemIds
}
this.store.dispatch(new CharacterAction.InitCharacter(newCharacter));
}
addItem(part: string): void {
// add rnd item of given part
const item: EquipItem = {
id: Math.random().toString(),
part: part,
}
this.store.dispatch(new CharacterAction.EqItem(item));
}
}
这导致我的订阅在同一个服务中
this.character$.subscribe(
(state: any) => console.log('char store triggered. State:', state)
);
记录字符,这很好,因为它从 null 变为字符 Object。
现在,如果我调用 addItem()
调用 this.store.dispatch(new CharacterAction.EqItem(item));
,它应该向地图 state.character.wardrobeItemIds
添加一个项目。
这应该会导致 Store Observable 再次触发并且订阅应该记录更改的字符。但不知何故什么也没发生。 已经检查减速器是否正确接收动作。
不确定这只是我的愚蠢还是某种错误?
提前致谢 托比
@ngrx/entity 似乎是商店中更复杂数据结构的预期解决方案。如果您打开它们,使用 Map 将触发新的运行时检查,因为它们既不是不可变的也不是可序列化的。