Как получить поле "один ко многим" в виде массива в redux-orm

У меня есть следующие модели для приложения чата, использующего redux-orm, каждый Conversation содержит много Messages, но одно сообщение может принадлежать только одному Conversation:

export class Message extends Model {
  static modelName = 'Message';
  static fields = {
    id: attr(),
    text: attr(),
    created: attr(),
    from: fk('User'),
    conversation: fk('Conversation', 'messages')
  };
}

export class Conversation extends Model {
  static modelName = 'Conversation';
  static fields =  {
    id: attr(),
    created: attr(),
  };
}

Я использую следующий селектор, чтобы получить список разговоров с соответствующими сообщениями.

export const getConversations = createSelector(
  getOrm,
  createOrmSelector(orm, session =>  {
    return session.Conversation
      .all()
      .toModelArray()
  })
);

Эта проблема? messages собственность каждого Conversation Экземпляр QuerySetне Array, что затрудняет работу при передаче компонентов.

Вот решения, которые я попробовал:

  1. Картирование messages собственность каждого Conversation модель вернулась в массив Messages с messages.all().toModelArray(), Это дало мне ошибку Can't mutate a reverse many-to-one relationдаже когда я пытался клонировать объект.

  2. Создание совершенно нового простого старого объекта JavaScript и копирование всех свойств, а затем установка правильного значения для messages, Это сработало, но создание всех этих новых объектов кажется огромным скачком производительности приложения с частыми изменениями состояния.

Как мне достичь своей цели здесь?

1 ответ

Решение

Вы должны быть в состоянии сделать что-то похожее на:

return session.Conversation.all().toModelArray()
  .map(c => ({
    ...c.ref,
    messages: c.messages.toRefArray()
  }))

в вашем селекторе. Если это не работает, вам, возможно, придется включить больше деталей. Вы не получаете отношения бесплатно с начальным toModelArrayвам нужно "запросить" их, чтобы использовать их в селекторе.

Обычно я бы не просто выкидывал все содержимое хранилища для этих сущностей, как это, я бы уточнил, что в действительности требует компонент:

import { pick } from 'lodash/fp'

// ...

const refineConversation = c => ({
  ...pick([ 'id', 'updatedAt' ], c),
  messages: c.messages.toRefArray().map(refineMessages)
})

const refineMessages = m => ({
  ...pick([ 'id', 'author', 'text', 'updatedAt' ], m)
})

// ...

return session.Conversation
  .all()
  .toModelArray()
  .map(refineConversation)

Хотя может показаться заманчивым просто сгенерировать ссылку на объект из хранилища в вашем компоненте, в этом объекте есть куча вещей, которые ваш компонент, вероятно, не должен отображать. Если что-то из этого изменится, компонент должен выполнить повторную визуализацию, и ваш селектор не сможет использовать свои записанные данные.

Помните, что когда вы используете объект распространения (или Object.assignВы создаете мелкие копии. Да, это цена, но все, что выходит за рамки первого уровня вложенности, использует ссылку, поэтому вы не клонируете все это. Использование селекторов должно защитить вас от необходимости делать слишком много работы в mapStateToProps (после первого рендера с этими данными).

Важно сделать правильный выбор с помощью управления состоянием, но реальные потери производительности, вероятно, будут приходить из других источников (ненужные рендеры, ожидание взаимодействия API и т. Д.).

Другие вопросы по тегам