Ionic(Angular)应用修改属性提示只读,实际非只读问题求助
Ionic+Angular中修改过滤器属性报错"Attempted to assign to readonly property."的原因及解决
问题重现
我在开发Ionic+Angular应用时,实现了一个过滤器组件:首次点击过滤器能正常切换状态并触发store dispatch,但再次点击之前操作过的过滤器时,控制台报错Attempted to assign to readonly property.,移除store dispatch代码后功能恢复正常。
组件模板代码
<ng-container *ngFor="let filter of filters; let idx = index"> <div class="filter-wrapper"> {{ filter.text | transloco | uppercase }} <div class="icon-wrapper" (click)="handleFilterClick(idx, $event)"> <app-icon-button [color]="filter.isActive ? activeIconColor : inActiveIconColor" [src]="filter.iconSrc" > </app-icon-button> </div> </div> </ng-container>
Filters数组定义
filters: Filter[]; constructor( private store: Store<AppState>, ) {} ngOnInit() { this.filters = [ { iconSrc: 'assets/icons/white-filters-icons/parking.svg', text: 'shared.attribute.parking', isActive: false, filter: Attribute.PARKING, }, { iconSrc: 'assets/icons/white-filters-icons/wc.svg', text: 'shared.attribute.wc', isActive: false, filter: Attribute.WC, }, { iconSrc: 'assets/icons/white-filters-icons/food.svg', text: 'shared.attribute.food', isActive: false, filter: Attribute.FOOD, } ] }
点击处理函数
handleFilterClick = (idx: number, e: MouseEvent): void => { this.filters[idx].isActive = !this.filters[idx].isActive; const selectedFilters = [...this.filters.filter(el => el.isActive)]; this.store.dispatch(new SetSelectedFilters({ filters: selectedFilters })); e.stopImmediatePropagation(); };
SetSelectedFilters动作及Reducer代码
// 动作定义 export enum ActionTypes { SET_SELECTED_FILTERS = '[Map] Set selected filters', } export class SetSelectedFilters implements Action { readonly type = ActionTypes.SET_SELECTED_FILTERS; constructor(public payload: { selectedFilters: Filter[] }) { } } export type Actions = SetSelectedFilters;
// Reducer定义 export interface MapState { selectedFilters: Filter[]; }; export const initialState: MapState = { selectedFilters: [], }; export const reducer = ( state = initialState, action: fromMap.Actions ): MapState => { switch (action.type) { case fromMap.ActionTypes.SET_SELECTED_FILTERS: { return { ...state, selectedFilters: action.payload.selectedFilters, }; } default: { return state; } } };
原因分析
问题核心是对象引用共享和NgRx的状态不可变性机制:
- 首次点击时,你直接修改了
this.filters[idx].isActive,然后通过[...this.filters.filter(...)]浅拷贝数组,但数组里的Filter对象还是原引用,直接传到了store。 - NgRx在开发模式下会启用状态不变性检查,将store中的对象标记为只读(冻结)。此时组件内
filters数组的对象和store里selectedFilters的对象是同一个引用。 - 第二次点击时,你尝试修改这个已经被store标记为只读的对象的
isActive属性,就触发了报错。
解决方案
修改组件内的过滤器对象时,不要直接操作原引用,而是创建新的对象副本;同时传递给store的也必须是全新的对象实例,断开和组件内对象的引用关联。
修改后的点击处理函数:
handleFilterClick = (idx: number, e: MouseEvent): void => { // 创建新的filters数组,修改对应位置的对象为新副本 this.filters = this.filters.map((filter, i) => { if (i === idx) { // 复制原对象并切换isActive状态 return { ...filter, isActive: !filter.isActive }; } return filter; }); // 筛选选中的过滤器,此时每个对象都是新副本 const selectedFilters = this.filters.filter(el => el.isActive); this.store.dispatch(new SetSelectedFilters({ selectedFilters })); e.stopImmediatePropagation(); };
额外优化建议
- 如果Filter对象有嵌套属性,需要用深拷贝(比如
JSON.parse(JSON.stringify(filter))或lodash的cloneDeep)确保完全断开引用。 - 保持NgRx状态的不可变性是最佳实践,当前Reducer已经正确返回新的state对象,只需确保组件传递给action的payload是全新对象即可。
内容的提问来源于stack exchange,提问作者hvma411
相关产品推荐
相关产品推荐

