简体   繁体   English

更新 ngrx/store 中的对象

[英]Updating an object in the ngrx/store

I'm using @ngrx/store for an Angular 2 app.我正在将 @ngrx/store 用于 Angular 2 应用程序。

My store holds a list of say, Book objects.我的商店有一个清单,比如Book对象。 I want to update a field in one of those objects.我想更新这些对象之一中的字段。 I also happen to have an Observable of the Book instance I'm looking to update (say, selectedBook ).我也碰巧有一个我想要更新的 Book 实例的 Observable(比如selectedBook )。

To do the update I intend on calling the reducer with an UpdateBookAction , and a payload of the new Book.为了进行更新,我打算使用UpdateBookActionUpdateBookAction的有效负载调用减速器。 So I make a deep copy of the existing Book object by subscribing to selectedBook and then calling Object.assign().因此,我通过订阅selectedBook然后调用 Object.assign() 来制作现有 Book 对象的深层副本。

But when I try to write to one of the fields of the copy I get the following error.但是,当我尝试写入副本的其中一个字段时,出现以下错误。 (It happens to be the same error I get if I were to try to write directly to the Book object in the store.) (如果我尝试直接写入商店中的 Book 对象,这恰好与我得到的错误相同。)

Error错误

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

Code代码

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);

    }
}

Clarification after Comments评论后澄清

I was under the assumption that I could have an action with a payload that was not the entire state of the store.我假设我可以使用不是商店的整个状态的有效负载进行操作。 (In fact that seems necessary, no?) I'm confident that this is the case given the documentation. (事实上​​,这似乎是必要的,不是吗?)鉴于文档,我相信情况确实如此。

The action I'm looking to take is something like this:我希望采取的行动是这样的:

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

As mentioned, I intend on making that call like this:如前所述,我打算像这样拨打电话:

this.store.dispatch(action);

Presumably under the hood, ngrx is passing my action to the reducer along with the (immutable) current state.大概在幕后,ngrx 正在将我的动作与(不可变的)当前状态一起传递给减速器。

So from there, everything should work okay.所以从那里开始,一切都应该正常。 My logic inside the reducer doesn't mutate the existing state, it simply creates a new one out of the existing state and the payload I've passed in.我在 reducer 中的逻辑不会改变现有状态,它只是从现有状态和我传入的有效负载中创建一个新状态。

The real question here is how I can reasonably build the new "objectToUpdate" such that I can pass that in as the payload.这里真正的问题是我如何合理地构建新的“objectToUpdate”,以便我可以将其作为有效负载传入。

I could do something like this:我可以做这样的事情:

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);

}

But we're not just talking about two fields here... what if my book has several fields?但我们在这里讨论的不仅仅是两个领域……如果我的书有几个领域怎么办? Do I have to manually build from scratch a new Book each time just to update one field?我是否必须每次都从头开始手动构建一本新书以更新一个字段?

My solution was to do a deep copy using Object.assign({}, book) (and not mutate the old one!) and subsequently make the update to solely the field I was looking to touch.我的解决方案是使用Object.assign({}, book)进行深度复制(而不是改变旧的!),然后仅对我想要触及的领域进行更新。

The idea of the ngrx store is to have one and only one single place of truth, which means all the objects are immutable, and the only way to change anything is to recreate everything as a whole. ngrx 存储的想法是拥有一个且只有一个真实的地方,这意味着所有对象都是不可变的,改变任何事物的唯一方法是将所有事物作为一个整体重新创建。 Also, you are probably using the ngrx freeze ( https://github.com/codewareio/ngrx-store-freeze ) which means that all of the objects will be created read-only so you wont be able to change any (This is good for development if you want to completely follow the redux pattern).此外,您可能正在使用 ngrx 冻结( https://github.com/codewareio/ngrx-store-freeze ),这意味着所有对象都将以只读方式创建,因此您将无法更改任何对象(这是如果您想完全遵循 redux 模式,则适合开发)。 If you remove the part where the store freezes the object, you will be able to change it, but thats not best practice.如果您删除商店冻结对象的部分,您将能够更改它,但这不是最佳实践。

What I would suggest you is the following: Use the ngrx observable with async pipe to put the data (in your case books) in a dumb component which can only get input and output some event.我建议您如下:使用带有异步管道的 ngrx observable 将数据(在您的案例书中)放入一个只能获取输入和输出某些事件的哑组件中。 Than, inside of the dumb component you can "edit" that object by making a copy of it, and after you are done, you can emit back the changes to the smart component which is subscribed to the store and allow it to change the state via the store (commit).然后,在哑组件内部,您可以通过复制该对象来“编辑”该对象,完成后,您可以将更改发送回订阅商店的智能组件并允许它更改状态通过商店(提交)。 This way is best because it is not very common to change the whole state for a really small change (like two way binding, when user types..).这种方式是最好的,因为对于非常小的更改(例如双向绑定,当用户键入..)时更改整个状态并不常见。

If you follow the redux pattern, than you will be able to add history, which means the store will keep a copies of the last X state recreations, so you can get UNDO functionality, easier to debug, timeline etc如果您遵循 redux 模式,那么您将能够添加历史记录,这意味着商店将保留最后 X 状态重新创建的副本,因此您可以获得 UNDO 功能、更易于调试、时间轴等

Your problem is that you are directly editing the property instead of recreating the whole state.您的问题是您正在直接编辑属性而不是重新创建整个状态。

I'll have to make an assumption about the actual scenario the OP is experiencing.我必须对 OP 正在经历的实际场景做出假设。

The problem问题

It's not possible to modify a member of a frozen object.无法修改冻结对象的成员。 Its the error being thrown.它的错误被抛出。

The cause原因

ngrx-store-freeze is used as a meta-reducer to freeze any object that enters the store. ngrx-store-freeze用作元还原器来冻结进入商店的任何对象。 On another place, when an object needs to be changed, a shallow copy is being made.在另一个地方,当一个对象需要改变时,正在制作一个浅拷贝。 Object.assign() doesn't do deep copy . Object.assign()不做深拷贝 A member of another object reached from the original object is being modified.从原始对象到达的另一个对象的成员正在被修改。 This secondary object is also frozen, by it is not duplicated .这个次要对象也被冻结,因为它不是重复的

Solution解决方案

Use a deep copy like cloneDeep() from lodash.使用像cloneDeep()这样的深层副本。 Or sent a bag of properties to be changed with a proper action.或者发送一包属性以通过适当的操作进行更改。 Process the changes on the reducer.处理减速器上的更改。

As already mentioned - the reason you are getting如前所述 - 你得到的原因

Cannot assign to read only property 'name' of object无法分配给对象的只读属性“名称”

is because 'ngrx-store-freeze' freezes the state and prevents mutating it.是因为 'ngrx-store-freeze' 会冻结状态并防止对其进行变异。

Object.assign will provide a new object as you expect, but it will copy the state's properties along with each property's own definition - such as the 'writable' definition (which 'ngrx-store-freeze' likely sets to false). Object.assign 将按照您的预期提供一个新对象,它会复制状态的属性以及每个属性自己的定义 - 例如 'writable' 定义('ngrx-store-freeze' 可能设置为 false)。

A different approach is described in this answer and explains how cloning objects with JSON.parse(JSON.stringify(yourObject)) as fastest, but this approach has flaws if you keep dates or methods etc' in your state.此答案中描述一种不同的方法并解释了如何使用 JSON.parse(JSON.stringify(yourObject)) 以最快的速度克隆对象,但是如果您在状态中保留日期或方法等,则此方法存在缺陷。

using lodash's 'cloneDeep' is probably your best bet for deep cloning the state.使用 lodash 的 'cloneDeep' 可能是深度克隆状态的最佳选择。

One way to accomplish this is a utility/helper method to make a new book from.实现此目的的一种方法是使用实​​用程序/助手方法来制作新书。 You could give it an existing book and the subset of properties you want to add to a new book (using Partial in typeScript if you want type safety).你可以给它一本现有的书和你想要添加到新书的属性子集(如果你想要类型安全,请在typeScript使用Partial )。

createNewBook(oldBook: Book, newProps: Partial<Book>): Book {
    const newBook = new Book(); 
    for(const prop in oldBook) {
        if(newProps[prop]) {
            newBook[prop]=newProps[prop];
        } else {
            newBook[prop]=oldBook[prop];
        }
    }
    return newBook 
}

You could call it via newBook = createNewBook(new Book(), {title: 'first foo, then bar'});你可以通过newBook = createNewBook(new Book(), {title: 'first foo, then bar'}); and use this newBook to update your store.并使用这本新书来更新您的商店。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM