Redux/Java: управление нормированными данными и представлениями нескольких моделей для каждого объекта

Мы создаем новое приложение, использующее React/Redux, которое отображается на стороне сервера.

Мы хотим следовать лучшей практике для Redux и нормализовать наши данные на сервере, прежде чем он перейдет в начальное состояние для хранилища.

В этом примере предположим, что у нас есть общий объект "Продукты", который может быть довольно сложным и нормализуется в корне нашего состояния хранилища и состояния страницы в другом объекте в корне хранилища. Таким образом, структура и Reducers следуют типичному шаблону редуктора среза и будут выглядеть так:

{
  page_x_state: PageReducer
  products: ProductsReducer
}

Мы используем комбинированные редукторы, чтобы объединить редукторы, прежде чем передавать их в магазин.

Теоретический прецедент: у нас есть страница продуктов, в которой показан список основных сведений о продукте. Пользователь может нажать на продукт, чтобы показать модальный, который затем загружает и показывает полные данные о продуктах.

В приведенном выше примере состояние, отправленное с сервера, будет содержать только базовые модели продуктов (3 или 4 поля), этого достаточно, чтобы отобразить таблицу и получить всю информацию о продукте в этот момент является расточительным и не очень эффективным.

Когда пользователь нажимает на продукт, мы будем делать вызов AJAX для извлечения всех данных для этого продукта. Как только у нас будут все данные для одного продукта, следует ли обновить экземпляр в магазине продуктов с полной моделью? Если это так, мы бы закончили с набором объектов, все из которых могли бы быть разными состояниями (у некоторых могли быть минимальные поля с некоторыми, которые являются полномасштабными объектами с 10 полями). Это лучший способ справиться с этим?

Кроме того, мне было бы интересно услышать мысли о том, как управлять различными представлениями одной и той же базовой модели на сервере и как сопоставить ее с хранилищем Redux (в идеале в Java).

Ответы

Ответ 1

EDIT:

Явно отвечая на ваш первый вопрос, если ваши редукторы правильно построены, ваше дерево состояний должно инициализироваться абсолютно без данных. Но должна быть правильная форма. Ваши редукторы всегда должны иметь возвращаемое значение по умолчанию - при рендеринге стороны сервера - Redux должен отображать только начальное состояние

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

Предположим, что в вашем представлении второго уровня вам нужны name, photo_url, price и brand, а в исходном представлении есть 4 продукта, ваш рендер-магазин будет выглядеть примерно так:

{
  products: {
    by_id: {
      "1": {
         id: "1",
         name: "Cool Product",
         tags: [],
         brand: "Nike",
         price: 1.99,
         photo_url: "http://url.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "2": {
         id: "2",
         name: "Another Cool Product",
         tags: [],
         brand: "Adidas",
         price: 3.99,
         photo_url: "http://url2.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "3": {
         id: "3",
         name: "Crappy Product",
         tags: [],
         brand: "Badidas",
         price: 0.99,
         photo_url: "http://urlbad.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "4": {
         id: "4",
         name: "Expensive product",
         tags: [],
         brand: "Rolex",
         price: 199.99,
         photo_url: "http://url4.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       }
     },
    all_ids: ["1", "2", "3", "4"]
  }
}

В приведенных выше данных вы можете видеть, что некоторые ключи - это просто пустые строки или пустой массив. Но у нас есть наши данные, необходимые для фактического первоначального рендеринга страницы.

Затем мы могли бы сделать асинхронные вызовы на клиенте в фоновом режиме сразу после рендеринга сервера, и документ готов, вероятность того, что сервер вернет эти начальные вызовы, прежде чем пользователь попытается получить данные в любом случае. Затем мы можем загружать последующие продукты по запросу пользователя. Я не думаю, что подход лучший, но это тот, который имеет для меня наибольший смысл. У некоторых других людей могут быть другие идеи. Это полностью зависит от вашего приложения и прецедента.

Я бы оставил только один объект продукта в состоянии и сохранял ВСЕ данные, относящиеся к продуктам.


Недавно я развернул приложение в производство, и я поделюсь некоторыми своими понимание. Приложение, не будучи слишком большим по размеру, было сложным структуры данных и прошел весь процесс как новичок к Редуксу в производстве (и имея руководство от моего архитектора) - Эти некоторые из наших выводов. Там нет правильного пути с точки зрения архитектуры, но, безусловно, есть некоторые вещи, которые следует избегать или делать.

1. Перед тем, как начать записывать редукторы, создайте "статическое" состояние

Если вы не знаете, куда идете, вы не можете добраться туда. Написание всей структуры вашего состояния вне квартиры поможет вам понять, как ваше состояние со временем изменится. Мы обнаружили, что это спасло нам время, потому что нам не нужно было переписывать большие разделы.

2. Проектирование состояния

держите его просто. Весь смысл Redux - упростить управление государством. Мы использовали множество советов из egghead.io tutorials о Redux, которые были созданы Дэн Абрамовым. Они ясны, действительно помогли решить множество проблем, с которыми мы столкнулись. я уверен, что вы читаете документы о нормализации состояния, но простые примеры, которые они дали, фактически переносились в большинстве моделей данных, которые мы реализовали.

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

{
  products: {
    by_id: {
     "1": {
        id: "1",
        name: "Cool Product",
        tags: ["tag1", "tag2"],
        product_state: 0,
        is_fetching: 0,
        etc: "etc"
      }
    },
    all_ids: ["1"]
  }
}

В приведенном выше примере тегами может быть другой фрагмент данных с аналогичной структурой данных с использованием by_id и all_ids. Всюду по документам и тету, Абрамов продолжает ссылаться на реляционные данные и реляционные базы данных, это действительно было для нас ключом. Сначала мы продолжали смотреть на пользовательский интерфейс и конструировать наше государство вокруг того, как мы думали, что мы его покажем. Когда это было нажато, и мы начали группировать данные на основе этого отношения к другим частям данных, все начало защелкнуться.


Быстро перевернувшись на ваш вопрос, я бы избегал дублирования любых данных, как уже упоминалось в другом комментарии, лично я просто создаю ключ в объекте состояния с именем product_modal. пусть модальные позаботятся о своем собственном состоянии...

{
  products: {
    ...
  },
  product_modal: {
    current_product_id: "1",
    is_fetching: true,
    is_open: true
  }
}

Мы обнаружили, что следующий шаблон с состоянием страницы тоже очень хорошо работает... мы просто рассматривали его как любую другую часть данных с id/name и т.д.


3. Редукционная логика

убедитесь, что редукторы отслеживают свое состояние, многие из наших редукторов выглядели довольно похожими, сначала это чувствовало себя как СУХОЙ ад, но потом мы быстро поняли силу более редукторов... скажем, что действие отправлено, и вы хотите обновить целый кусок состояния. в вашем редукторе для действия и вернуть новое состояние. Если вы хотите обновить одно или два поля в одном состоянии... тогда вы просто сделаете то же самое, но только в полях, которые хотите изменить. большинство наших редукторов были просто оператором switch с случайным вложенным оператором if.

Объединение редукторов

Мы не использовали combReducers, мы писали наши собственные. Это было не сложно, это помогло нам понять, что происходило в Редуксе, и это позволило нам немного поучиться нашему государству. Это было бесценно

Действия

Middleware - ваш друг... мы использовали API-интерфейс fetch с redux-thunk, чтобы сделать запросы RESTful. Мы разделили требуемые запросы данных на отдельные действия, которые назывались store.dispatch() для каждого фрагмента данных, для которого требуется обновление для вызова. Каждая отправка отправила другое действие для обновления состояния. Это помогло обновить наше состояние модульно и позволило нам обновлять большие разделы или по мере необходимости подробно.

Работа с API

Хорошо, так что здесь слишком много, чтобы иметь дело с этим. Я не говорю, что наш путь - лучший... но он сработал для нас. Сокращение... у нас есть внутренний API в Java с публично открытыми конечными точками. Вызовы из этого API не всегда легко отображались на лицевой стороне. Мы этого не реализовали, но в идеале исходная конечная точка init могла быть написана на их конце, чтобы получить кучу исходных данных, которые необходимы для того, чтобы ускорить перемещение по переднему концу.

Мы создали публичный API на том же сервере, что и приложение, написанное на PHP. Этот API абстрагировал внутренние конечные точки API (а в некоторых случаях и данные) от внешнего интерфейса и браузера.

Когда приложение выполнит запрос GET на /api/projects/all, PHP API затем вызовет наш внутренний API, получит необходимые данные (иногда по нескольким запросам) и вернет эти данные в удобном для использования формате, который может использовать сокращение.

Это может быть не идеальный подход для приложения javascript, но у нас не было возможности создать новую внутреннюю структуру API, нам нужно было использовать тот, который существует уже несколько лет, мы нашли приемлемую производительность.

Ответ 2

следует обновить экземпляр в магазине товаров с полной моделью

Следует отметить, что Java и ReactJs + Redux не имеют много концептуального перекрытия. Все это объект Javascript, а не объект с классом.

Как правило, сохранение всех данных, которые вы получаете в состоянии хранилища Redux, - это путь. Чтобы обойти тот факт, что некоторые из данных будут минимальными, а некоторые из них будут полностью загружены, вы должны сделать условный вызов ajax в методе onComponentWillMount для отдельного контейнера отображения продукта.

class MyGreatProduct extends React.Component {
  onComponentWillMount() {
    if(!this.props.thisProduct.prototype.hasProperty( 'somethingOnlyPresentInFullData' )) {
      doAjaxCall(this.props.thisProduct.id).then((result) => {
        this.props.storeNewResult(result.data);
      }).catch(error=>{ ... })
    }
  }

  // the rest of the component container code
}

const mapStateToProps = (state, ownProps) => {
  return {
    thisProduct: state.products.productInfo[ownProps.selectedId] || {id: ownProps.selectedId}
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    storeNewResult: (data) => { dispatch(productDataActions.fullProductData(data)) }
}

export default connect(mapStateToProps, mapDispatchToProps)(MyGreatProduct);

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


Изменить. Что касается управления различными представлениями одной и той же базовой модели на сервере и как сопоставить ее с хранилищем Redux, я бы попытался использовать ту же относительную слабость, с которой вы имеете дело, когда у вас есть JSON. Это должно устранить некоторую связь.

Что я имею в виду, просто добавьте данные, которые у вас есть к JSObject, которые будут использоваться React + Redux, не беспокоясь о том, какие значения могут быть потенциально сохранены в состоянии Redux во время выполнения приложения.

Ответ 3

Вероятно, нет правильного ответа, какая стратегия вы предпочитаете:

  • Простейшая стратегия заключается в добавлении еще одной части в ваш редуктор под названием selectedProduct и всегда перезаписывать его полным объектом выбранного в данный момент продукта. В вашем модале всегда будут отображаться детали selectedProduct. Спад этой стратегии заключается в том, что вы не кэшируете данные в том случае, когда пользователь выбирает один и тот же продукт во второй раз, а ваши минимальные поля не нормализуются.

  • Или вы можете обновить экземпляр в своем магазине продуктов, как вы сказали, вам просто понадобится логика для его обработки. Когда вы выбираете продукт, если он полностью загружен, визуализируйте его. Если нет, сделайте вызов ajax и покажите счетчик до полной загрузки.

Ответ 4

Если у вас нет проблем с хранением дополнительных данных в магазине redux, это не сильно повлияет на вашу производительность, если вы используете нормализованное состояние. Поэтому на этом фронте я бы рекомендовал кэшировать как можно больше, не рискуя своей безопасностью.

Я думаю, что лучшим решением для вас было бы использовать промежуточное ПО промежуточного уровня, так что вашему интерфейсу все равно, как он получает данные. Он отправит действие в хранилище redux, а промежуточное программное обеспечение может определить, нужен ли ему вызов AJAX для получения новых данных. Если это необходимо для сбора данных, тогда промежуточное программное обеспечение может обновить состояние, когда AJAX будет разрешен, если он этого не сделает, он может просто отказаться от действия, потому что у вас уже есть данные. Таким образом, вы можете изолировать проблему наличия двух разных представлений данных для промежуточного программного обеспечения и реализовать там разрешение, чтобы ваш внешний интерфейс просто запрашивал данные и не заботился о том, как он его получает.

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

Если вы хотите больше узнать о промежуточном программном обеспечении, документация Redux довольно хороша.

https://redux.js.org/docs/advanced/Middleware.html

Ответ 5

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

const receiveProducts = (payload = [], representation = 'summary') => ({
  type: 'PRODUCTS_RECEIVED',
  payload, representation
});

const productReducer = (state = {}, action) => {
  case 'PRODUCTS_RECEIVED': {
    const { payload, representation } = action;

    return {
      ...state,
      ...payload.reduce((next, entity) => {
        next[entity.id] = {
          ...next[entity.id],
          [representation]: entity
        };
        return next
      }, {})
    }
  }
};

Это означает, что любой, кто звонит receiveProducts(), должен знать, какое представление возвращается.