Data transformation in Class vs Reducer (ngrx/redu

2019-07-13 08:42发布

问题:

Using ngrx/redux, and following the ngrx example app as close as possible. Now I'm doing this somewhere in the application:

get totalItems (): number {
   return sumBy(this._data.items, 'metadata.value');
}

Where the _data is the raw async data from a remote server, and is part of some data of a User.

Now I have some options:

1. Create a Class User

constructor (public _data: UserResponse) {}

And have some methods like:

get name (): string {
  return this._data.name;
}

get address (): string {
  return this._data.address;
}

And additionally things like the aforementioned totalItems, and other shortcuts to get a different result than the raw data. What I don't like is the repetition of the properties, and where to use this class;

  • Do I use the User Class in the api-service, and return the transformed data to the the @Effect module, which in turn will trigger the SUCCESS or FAILED actions?
  • Do I put it in the @Effect module, right before the action triggers?
    • Do I use extends or implements on the angular component?

Another thing is that we apply the transformed data on the payload property, which would make it hard to debug the raw remote data.

2. Another option would be to transform this data inside the reducer:

The current reducer:

case ActionTypes.FETCH_USERS_SUCCESS:
   return {
     ...state,
     users: action.payload
   };

What I could do:

case ActionTypes.FETCH_USERS_SUCCESS:
   return {
     ...state,
     users: action.payload.map((user) => {
        return({ 
          ...user, 
           totalItems: sumBy(user.items, 'metadata.value' });
     })
   };

Is this an anti-pattern or bad practice? Because I see .filter being used in examples all the time, and I don't see summing up as being impure, so what would be the arguments for/against doing it in the reducer? This way I could eliminate needing a User class, and load all the necessary data into a component.

And, keeping the reducers from filling up with a lot of logic, perhaps make a separate reducer for this, which "listens" to the same actions.

Option 3:

Put it in a selector:

export const selectUsers = createSelector(selectUsersState, (state: Userstate) => state.payload.map((a: UsersResponse) => new User(a)))

Reading this answer by @dan_abramov: How to put methods onto the objects in Redux state? implies me that the 2nd option makes more sense for this, as he states:

"In Redux, you don't really have custom models. Your state should be plain objects (or Immutable records). They are not expected to have any custom methods."