Невозможно назначить свойство только для чтения для глубоко скопированного объекта - PullRequest
2 голосов
/ 29 мая 2020

Я использую angular 9 и ngrx 9

У меня есть такой селектор:

    this.store$
      .pipe(select(SettingsStoreSelectors.selectNavigationSettings), take(1))
      .subscribe((settings: SettingsStoreModels.INavigationSettings) => {
        let copy = JSON.parse(JSON.stringify(settings));
        this.settings = copy.vrMapSettings;
      });

Это единственное место, settings назначенное в этом компоненте

В этом компоненте у меня есть флажок, который редактирует свойство настройки

      <mat-checkbox
        class="setting-input"
        *ngIf="viewer"
        (click)="$event.stopPropagation()"
        (change)="onSettingChange(settings, true)"
        [(ngModel)]="settings.depthTestAgainstTerrain"
      >
        {{ "MAP.SETTINGS.DEPTH_TEST_AGAINST_TERRAIN" | translate }}
      </mat-checkbox>

Это продолжает запускать

core. js: 4117 ERROR TypeError: Невозможно назначить только для чтения свойство 'depthTestAgainstTerrain' объекта '[object Object]'

Но я больше не понимаю, что делать. Я глубоко клонировал свойство своего магазина, я хочу отредактировать, чтобы повторно отправить действие, но это невозможно. почему ...

EDIT: на самом деле я выяснил причину, но я не понимаю, как ее правильно использовать.

Я глубоко клонирую объект, как указано выше из моего селектора

При изменении настройки я отправляю действие для изменения объекта настройки в хранилище: этот объект настройки действия ссылается на тот же объект, который используется компонентом.

this.store$.dispatch(new SettingsStoreAction.SetNavigationSettings({ settings: { vrMapSettings: settings } }));

Который вызывает этот редуктор:

const SET_NAVIGATION_SETTINGS = (state: State, action: featureAction.SetNavigationSettings) => {
  return {
    ...state,
    navigation: {
      ...state.navigation,
      ...action.payload.settings,
    },
  };
};

Но тогда, если я снова отредактирую, поскольку хранилище ссылается непосредственно на объект настройки компонента, это вызовет ошибку ....

Если Я тоже здесь глубоко клонирую

this.store$.dispatch(new SettingsStoreAction.SetNavigationSettings({ settings: { vrMapSettings: JSON.parse(JSON.stringify(settings))} }));

Это работает ...

Но если мне нужно создать небольшое действие для КАЖДОГО свойства настроек или глубокое клонирование для КАЖДОЙ отправки / выбора store, будет ОГРОМНЫЙ недостаток в производительности ...

правильно ли я его использую?

Если вам нужна дополнительная информация, прокомментируйте, я дам.

Ответы [ 3 ]

2 голосов
/ 31 мая 2020

Вы правы, состояние нужно мутировать только через действия. В вашем случае, если объект настроек имеет много свойств, может быть неудобно создавать разные действия для каждого из свойств. Копирование настроек выглядит как решение, но сложность глубокого копирования объектов зависит от размера объекта.

Я могу предложить вам использовать immer для создания копий настроек. Вместо копирования всего объекта он повторно использует все его неизменные части, что работает намного быстрее .

...
<mat-checkbox
  class="setting-input"
  *ngIf="viewer"
  (click)="$event.stopPropagation()"
  (change)="onDepthTestAgainstTerrainChange($event)"
  [checked]="settings.depthTestAgainstTerrain">
  {{ "MAP.SETTINGS.DEPTH_TEST_AGAINST_TERRAIN" | translate }}
</mat-checkbox>
...
@Component(...)
export class MyComponent {
  ...
  onDepthTestAgainstTerrainChange(change: MatCheckboxChange) {
    const patchedSettings = produce(settings, draft => {
      // draft remembers all the changes you've made and produces
      // new immutable state based on these mutations, but takes all
      // unchanged parts of the original object
      draft.depthTestAgainstTerrain = change.checked;
    });

    this.store$.dispatch(new SettingsStoreAction.SetNavigationSettings({
      settings: { vrMapSettings: patchedSettings}
    }));
  }
}

Или вы можете создать действие, которое принимает функцию патча, например:

  onDepthTestAgainstTerrainChange(change: MatCheckboxChange) {
    this.store$.dispatch(new SettingsStoreAction.PatchNavigationSettings(sttingsDraft => {
      sttingsDraft.depthTestAgainstTerrain = change.checked;
    }));
  }
1 голос
/ 01 июня 2020

Эти мутации состояния в редукторе фактически увеличивают производительность. Это быстрее, чем (ненужный) повторный рендеринг компонента, вы можете использовать канал OnPush, потому что ссылка на состояние изменится, что вызовет повторный рендеринг только тогда, когда это действительно необходимо.

Документы Redux имеет страницу производительности , которая также может быть применена к NgRx.

Если вам не нравится обновлять состояние таким образом, есть immer. На самом деле я написал иммерную оболочку для редуктора NgRx. См. ngrx-et c. NgRx также предоставляет пакет ngrx-entity, в котором есть несколько помощников для обновления состояния.

1 голос
/ 29 мая 2020

Вам нужно поделиться объявлением settings, похоже, у него есть флаг только для чтения.

чтобы исправить это, вы можете сделать эту подпись доступной для записи:

export declare type Writable<T> = {
  -readonly [P in keyof T]: T[P];
};

class Test {
  public settings: Writable<TypeYouNeed>;
}
...