Users sees one part of deeply-nested state, should

2019-04-11 20:32发布

问题:

I'm working on a game. Originally, the user was in a single dungeon, with properties:

// state

{
  health: 95,
  creatures: [ {}, {} ],
  bigBoss: {},
  lightIsOn: true,
  goldReward: 54,
  // .. you get the idea
}

Now there are many kingdoms, and many dungeons, and we may want to fetch this data asynchronously.

Is it better to represent that deeply-nested structure in the user's state, effectively caching all the other possible dungeons when they are loaded, and every time we want to update a property (e.g. action TURN_ON_LIGHT) we need to find exactly which dungeons we're talking about, or to update the top-level properties every time we move to a new dungeon?

The state below shows nesting. Most of the information is irrelevant to my presentational objects and actions, they only care about the one dungeon the user is currently in.

// state with nesting

{
  health: 95,
  kingdom: 0,
  dungeon: 1,
  kingdoms: [
    {
      dungeons: [
        {
          creatures: [ {}, {} ],
          bigBoss: {},
          lightIsOn: true,
          goldReward: 54
        }
        {
          creatures: [ {}, {}, {} ],
          bigBoss: {},
          lightIsOn: false,
          goldReward: 79
        }
        {
          //...
        }
      ]
    }
    {
      // ...
    }
  ]
}

One of the things that's holding me back is that all the clean reducers, which previously could just take an action like TURN_ON_LIGHT and update the top-level property lightIsOn, allowing for very straight-forward reducer composition, now have to reach into the state and update the correct property depending on the kingdom and dungeon that we are currently in. Is there a nice way of composing the reducers that would keep this clean?

回答1:

The recommended approach for dealing with nested or relational data in Redux is to normalize it, similar to how you would structure a database. Use objects with IDs as keys and the items as values to allow direct lookup by IDs, use arrays of IDs to indicate ordering, and any other part of your state that needs to reference an item should just store the ID, not the item itself. This keeps your state flatter and makes it more straightforward to update a given item.

As part of this, you can use multiple levels of connected components in your UI. One typical technique with Redux is to have a connected parent component that retrieves the IDs of multiple items, and renders <SomeConnectedChild itemID={itemID} /> for each ID. That connected child would then look up its own data using that ID, and pass the data to any presentational children below it. Actions dispatched from that subtree would reference the item's ID, and the reducers would be able to update the correct normalized item entry based on that.

The Redux FAQ has further discussion on this topic: http://redux.js.org/docs/FAQ.html#organizing-state-nested-data . Some of the articles on Redux performance at https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#redux-performance describe the "pass an ID" approach, and https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f is a good reference as well. Finally, I just gave an example of what a normalized state might look like over at https://github.com/reactjs/redux/issues/1824#issuecomment-228609501.

edit:

As a follow-up, I recently added a new section to the Redux docs, on the topic of "Structuring Reducers". In particular, this section includes chapters on "Normalizing State Shape" and "Updating Normalized Data".



标签: redux