Redux — нормализованная организация вложенных данных внутри createSlice

#redux #normalizr #redux-toolkit

#redux #normalizr #redux-инструментарий

Вопрос:

У меня есть глубоко вложенный объект данных, который возвращается из моего API, который выглядит как приведенный ниже JSON.

Я использую инструментарий Redux createSlice для создания фрагмента trip

Итак, в настоящее createSlice время я хочу сохранить массив поездок.

Я также хочу иметь возможность обновлять одну поездку или часть поездки

  • например, допустим, я хочу обновить дату начала элемента поездки
  • или, допустим, я хочу обновить имя участника элемента trip

Мои вопросы и проблемы:

  • В настоящее время все эти объекты возвращаются в trip createSlice, но я не уверен, следует ли после нормализации объектов разделять их на отдельные createSlice s? если да, то как это делается или это анти-шаблон?
  • как следует определять вложенные объекты в initialState ?
  • должен ли я определить все мои нормализованные объекты в моем initalState ? если я это сделаю, как будут выглядеть мои редукторы, когда я захочу обновить trip_item или trip_item_member ?
  • мои нормализованные данные даже выглядят «правильными»? Я опустил использование mergeStrategy between trips_items_members и trip_members что, как я знаю, я должен сделать, но еще не понял, как это работает, или если это необходимо здесь?

Примечание:

В документах RTK здесь есть пример, который показывает createSlice , что он используется с 3 отдельными объектами, которые изначально были получены из 1 вызова API. Это выглядит как 3 отдельных файла, однако неясно, как данные распределяются между ними.

Вот как createSlice выглядит моя поездка

 /**
 * Get trip by ID action
 */
export const getTripByID = createAsyncThunk(
  'trips/getTripByID',
  async ({ uid }) => {
    const response = await findOne(uid)
    const normalized = normalize(response, trip)
    return normalized.entities
  },
)

const tripsAdapter = createEntityAdapter({
  selectId: entity => entity.trip_id,
  sortComparer: (a, b) => b.start_date.localeCompare(a.start_date),
  loading: '',
  error: '',
  data: [],
})

export const {
  selectById: selectTripById,
  selectIds: selectTripIds,
  selectEntities: selectTripEntities,
  selectAll: selectAllTrips,
  selectTotal: selectTotalTrips,
} = tripsAdapter.getSelectors(state => state.trip)


const initialState = tripsAdapter.getInitialState()

const tripSlice = createSlice({
  name: 'trips',
  initialState,
  extraReducers: builder => {
    builder.addCase(getAllTrips.fulfilled, (state, { payload }) => {
      tripsAdapter.upsertMany(state, payload)
      state.loading = false
    })
    builder.addCase(getTripByID.fulfilled, (state, { payload }) => {
      console.log('payload', payload)
      tripsAdapter.upsertMany(state, payload)
      state.loading = false
    })
  },
})

export default tripSlice.reducer
  

Ответ API, который возвращается из await findOne(uid)

 {
    created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e",
    deleted_by: null,
    destination: "Valencia",
    end_date: "2020-10-04",
    start_date: "2020-09-27",
    trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a",
    uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b",
    updated_by: null,
    trip_items: [
        {
            destination: "Mezzanine Level Shivaji Stadium Metro Station, Baba Kharak Singh Rd, Hanuman Road Area, Connaught Place, New Delhi, Delhi 110001, India",
            end_date: "2020-09-28",
            end_time: "2020-09-28T01:20:15.906Z",
            note: null,
            start_date: "2020-09-28",
            start_time: "2020-09-28T01:20:15.906Z",
            trip_item_id: "bd775be7-2129-42c0-a231-5a568b0f565d",
            trips_items_members: [
                {
                    trip_item_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc", 
                    uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02", 
                }
            ],
            uid: "e5f81a6d-1a0d-4456-9d4e-579e80bc27d8",
        }
    ],
    trips_members: [
        {
            trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc", 
            uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02", 
            role: "ADMIN"
        }
    ]
}
  

Это моя схема normalizr

 
const tripItemMember = new schema.Entity(
  'trips_items_members',
  {},
  { idAttribute: 'trip_item_member_id' },
)

const tripItem = new schema.Entity(
  'trips_items',
  {
    trips_items_members: [tripItemMember],
  },
  {
    idAttribute: 'trip_item_id',
  },
)

const tripMember = new schema.Entity(
  'trips_members',
  {},
  {
    idAttribute: 'trip_member_id',
  },
)

export const trip = new schema.Entity(
  'trip',
  {
    trips_items: [tripItem],
    trips_members: [tripMember],
  },
  {
    idAttribute: 'trip_id',
  },
)
  

Это вывод из normalizr

 trip: {
  "34a620e8-51ff-4572-b466-a950a8ce1c8a": {
    created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e"
    deleted_by: null
    destination: "Valencia"
    end_date: "2020-10-04"
    start_date: "2020-09-27"
    trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a"
    trips_items: ["bd775be7-2129-42c0-a231-5a568b0f565d"]
    trips_members: ["76b54a80-4d09-4768-bc5a-4d7e153e66dc"]
    uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b"
    updated_by: null
  }
}

trips_items:{
  "0a56da0f-f13b-4c3d-896d-30bccbe48a5a": {
    destination: "Mezzanine Level Shivaji Stadium Metro Station"
    end_date: "2020-09-28"
    end_time: "2020-09-28T01:20:15.906Z"
    note: null
    start_date: "2020-09-28"
    start_time: "2020-09-28T01:20:15.906Z"
    trip_item_id: "0a56da0f-f13b-4c3d-896d-30bccbe48a5a"
    trips_items_members: []
    uid: "25d20a9d-1eb9-4226-926d-4d743aa9d5dc"
  }
}

trips_members: {
  "76b54a80-4d09-4768-bc5a-4d7e153e66dc": {
    role: "ADMIN"
    trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc"
    uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02"
  }
}
  

Ответ №1:

Ваша настройка очень похожа на этот подробный пример из документации redux-toolkit. Они извлекаются articles , но каждая статья поставляется со встроенными users и comments . Они определяют отдельные фрагменты для каждого из трех объектов.

У comments среза нет собственных действий или редукторов, но он использует extraReducers свойство для ответа на действие, полученное в статье, и сохранения встроенных комментариев.

 const commentsAdapter = createEntityAdapter();

export const slice = createSlice({
  name: "comments",
  initialState: commentsAdapter.getInitialState(),
  reducers: {},
  extraReducers: {
    [fetchArticle.fulfilled]: (state, action) => {
      commentsAdapter.upsertMany(state, action.payload.comments);
    }
  }
});
  

fetchArticle Действие «принадлежит» article срезу, но полезная нагрузка действия содержит объекты всех трех типов. Все срезы получают все действия, поэтому comments и users могут реагировать на это действие со своей собственной логикой. Каждый фрагмент не оказывает никакого влияния на то, что могут или не могут делать другие.

В вашем случае вы хотите создать срезы для items и members . Вместо вызова upsertMany(state, payload) вы хотите, чтобы полезная нагрузка была привязана к типу объекта, чтобы вы могли вызывать upsertMany(state, payload.members) .