web-dev-qa-db-ja.com

ngrx / store内のオブジェクトを更新しています

Angular 2アプリに@ngrx/storeを使用しています。

私のストアには、たとえばBookオブジェクトのリストがあります。それらのオブジェクトの1つでフィールドを更新したいと思います。また、更新しようとしているBookインスタンスのObservableがあります(たとえば、selectedBook)。

更新を行うために、UpdateBookActionと新しいブックのペイロードを使用してレデューサーを呼び出すつもりです。そこで、selectedBookにサブスクライブしてから、Object.assign()を呼び出すことにより、既存のBookオブジェクトのディープコピーを作成します。

しかし、コピーのフィールドの1つに書き込もうとすると、次のエラーが発生します。 (ストア内のBookオブジェクトに直接書き込もうとした場合と同じエラーが発生します。)

エラー

_Cannot assign to read only property 'name' of object '#<Object>' at ViewWrappedError.BaseError [as constructor]
_

コード

_ngOnInit() {
    this.book$ = this.store.let(fromRoot.getSelectedBook);
    //...
}

someFunction() {
    //...
    this.book$.subscribe(book => {

        let updatedBook = Object.assign({}, book);
        updatedBook.name = 'something else';          // <--- THIS IS WHAT THROWS

        let action = new BookUpdateAction(updatedBook);
        this.store.dispatch(action);

    }
}
_

コメント後の説明

私は、ストアの状態全体ではないペイロードを使用してアクションを実行できると想定していました。 (実際、それは必要だと思われますね?)私はこれがドキュメントを与えられた場合であると確信しています。

私が実行しようとしているアクションは次のようなものです。

_Action = UPDATE, payload = {'id': 1234, 'name': 'something new'}
_

述べたように、私はこのようにその呼び出しを行うつもりです:

_this.store.dispatch(action);
_

おそらく内部では、ngrxは(不変の)現在の状態とともに私のアクションをレデューサーに渡しています。

したがって、そこから、すべてが正常に機能するはずです。レデューサー内の私のロジックは、既存の状態を変更しません。既存の状態と渡したペイロードから新しいロジックを作成するだけです。

ここでの本当の問題は、ペイロードとして渡すことができるように、新しい「objectToUpdate」を合理的に構築する方法です。

私はこのようなことをすることができます:

_this.book$.subscribe(book => {

    let updatedBook = new Book();
    updatedBook.id = book.id;
    //set all other fields manually...
    updatedBook.name = 'something else';

    let action = new BookUpdateAction(updatedBook);
    this.store.dispatch(action);

}
_

しかし、ここでは2つのフィールドについて話しているだけではありません...私の本に複数のフィールドがある場合はどうなりますか? 1つのフィールドを更新するためだけに、毎回新しいブックを最初から手動で作成する必要がありますか?

私の解決策は、Object.assign({}, book)を使用してディープコピーを実行し(古いものを変更しないでください!)、その後、タッチしようとしていたフィールドのみを更新することでした。

8
Daniel Patrick

Ngrxストアの考え方は、真実の場所を1つだけにすることです。つまり、すべてのオブジェクトは不変であり、何かを変更する唯一の方法は、すべてを全体として再作成することです。また、おそらくngrxフリーズ( https://github.com/codewareio/ngrx-store-freeze )を使用しています。これは、すべてのオブジェクトが読み取り専用で作成されるため、いずれかを変更できます(これは、reduxパターンに完全に従う場合の開発に適しています)。ストアがオブジェクトをフリーズする部分を削除すると、それを変更できますが、それはベストプラクティスではありません。

私が提案するのは次のとおりです。非同期パイプで監視可能なngrxを使用して、データ(ケースブックの場合)を、何らかのイベントの入力と出力のみを取得できるダムコンポーネントに配置します。ダムコンポーネント内では、オブジェクトのコピーを作成してそのオブジェクトを「編集」できます。完了したら、ストアにサブスクライブしているスマートコンポーネントに変更を送信して、状態を変更できるようにすることができます。ストア経由(コミット)。非常に小さな変更で状態全体を変更することはあまり一般的ではないため、この方法が最適です(ユーザーが入力するときの双方向バインディングなど)。

Reduxパターンに従うと、履歴を追加できるようになります。つまり、ストアは最後のX状態のレクリエーションのコピーを保持するため、UNDO機能、デバッグの容易さ、タイムラインなどを取得できます。

問題は、状態全体を再作成するのではなく、プロパティを直接編集していることです。

7
Denko Mancheski

OPが経験している実際のシナリオについて推測する必要があります。

問題

フリーズされたオブジェクトのメンバーを変更することはできません。そのエラーがスローされます。

原因

_ngrx-store-freeze_は、ストアに入るオブジェクトをフリーズするためのメタリデューサーとして使用されます。別の場所では、オブジェクトを変更する必要がある場合、浅いコピーが作成されています。 Object.assign()はディープコピーを行いません。元のオブジェクトから到達した別のオブジェクトのメンバーが変更されています。このセカンダリオブジェクトも、複製されないため凍結されます

解決策

LodashのcloneDeep()のようなディープコピーを使用します。または、適切なアクションで変更するプロパティのバッグを送信しました。レデューサーで変更を処理します。

5
André Werlang

すでに述べたように-あなたが得ている理由

オブジェクトの読み取り専用プロパティ 'name'に割り当てることはできません

'ngrx-store-freeze'が状態をフリーズし、変更を防ぐためです。

Object.assignは、期待どおりに新しいオブジェクトを提供しますが、butは、状態のプロパティを各プロパティの独自の定義( 'writable'など)とともにコピーします。定義(「ngrx-store-freeze」はおそらくfalseに設定されます)。

別のアプローチが説明されています この回答では そしてJSON.parse(JSON.stringify(yourObject))を使用してオブジェクトのクローンを作成する方法を説明していますが、日付やメソッドなどを保持している場合、このアプローチには欠陥があります。状態。

lodashの「cloneDeep」を使用することは、おそらく状態をディープクローンするための最善の策です。

1
Yinon