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

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

[
  {type: list, elements: []}
  {type: map, elements: []}
  {type: wheelList, elements: []}
  {type: chart, elements: []}
  {type: dyna, elements: []}
  ...
]

В одном компоненте я получаю его из хранилища и получаю доступ к нему как:

this.props.allElements

Затем для каждого из них я визуализирую другой компонент (псевдокод):

foreach (el in this.props.allElements) {
   if (el == list) {
      return <ListComponent elements={el.elements}>
   }
   if (el == map) {
      return <MapComponent elements={el.elements}>
   }
   ...
}

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

Является ли мой подход хорошим?

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

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


UPDATE

Но для обновления элементов map мне нужно получить целые значения этого элемента object из store в action update only map, а затем передать весь обновленный объект на reducer.
Так что изменилась только карта, другие нет.
Является ли реакция/сокращение достаточно умным, чтобы обновлять только компонент, данные которого изменены (карта в моем случае).
Действие:

export function getScreenElements() : Action {
  return function(dispatch) {
    // get data from server here
    // and do some calculations here
    dispatch({
                type: 'ELEMENTS_RECEIVED',
                allElements: data
            });

Разбавление:

export default function (state:State = initialState, action:Action): State {
  if (action.type === 'ELEMENTS_RECEIVED') {
    return {
      ...state,
      allElements: action.allElements,
    };

В большой части:

const mapStateToProps = state => ({
  allElements: state.main.allElements
...
class Main extends Component {
...
componentDidMount() {
    // here I actually make a call to get data
}
...
render() {
    return (
       // Here is the big code
       // Loop over props.allElements
       // and render it in different way based on data inside each element
    )
}


Пример данных:

[
  {
    "type": "list",
    "count": 99,
    "elements": [
      {
        "id": 1,
        "name": "test"
      },
      {
        "id": 2,
        "name": "test1"
      }
    ]
  },
  {
    "type": "map",
    "count": 99,
    "elements": [
      {
        "id": 3,
        "name": "test"
      },
      {
        "id": 4,
        "name": "test1"
      }
    ]
  },
  {
    "type": "list",
    "count": 99,
    "elements": [
      {
        "id": 5,
        "name": "test"
      },
      {
        "id": 6,
        "name": "test1"
      }
    ]
  },
  {
    "type": "list",
    "count": 99,
    "elements": [
      {
        "id": 7,
        "name": "test"
      },
      {
        "id": 8,
        "name": "test1"
      }
    ]
  }
]

Ответ 1

Вы можете сделать цикл внутри вашего thunk action и разделить данные на отдельный reducers.

Действие может выглядеть примерно так:

// action thunk
export function getScreenElements() {
  return function (dispatch) {
    // get data from server here
    const dataFromServer = fetch('url');
    // do your looping here and dispatch each data acording to its type
    dataFromServer.map(obj => {
      switch (obj.type) {
        case 'list':
          dispatch({
            type: 'LIST_RECEIVED',
            listElements: obj
          });

        case 'map':
          dispatch({
            type: 'MAP_RECEIVED',
            mapElements: obj
          });

        // ....
      }
    });
  }
}

Ваши отдельные редукторы могут выглядеть примерно так:

// map reducer
export default function map(state = initialState, action) {
  if (action.type === 'MAP_RECEIVED') {
    return {
      ...state,
      mapElements: action.mapElements,
    }
  }
}


// list reducer
export default function list(state = initialState, action) {
  if (action.type === 'LIST_RECEIVED') {
    return {
      ...state,
      listElements: action.listElements,
    }
  }
}

И затем вы можете сделать это следующим образом:

// inside your component render mehtod
render(){
  const { list, map } = this.props; // redux data passed as props from the seperate reducers
  return (
    <div>
      <ListComponent elements={list}>
        <MapComponent elements={map}>
    </div>
  );
}

Edit
В качестве комментария к вашему комментарию:

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

Ну, нет ничего плохого в том, что Parent повторно отображается. когда выполняется render метод component, это не означает, что он будет повторно отображаться на физическом DOM, он обновит или переопределит объект виртуального DOM, а затем проверит различия между физическим и виртуальный DOM через Согласование и Дифференциальный алгоритм, чтобы определить, когда, как и где должен обновляться физический DOM.
Он может обновлять только определенные атрибуты элементов DOM без их повторной рендеринга, Реагировать только на обновления Что необходимо.

Существует также опция React.PureComponent, но я настоятельно советю против этого в вашем случае.

Ответ 2

В вашем примере данных есть несколько элементов с одним и тем же типом (пример: список).

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

Один из способов оптимизации - сгладить ваши данные, чтобы выглядеть так:

{
  isFetching: false,
  didInvalidate: false,
  data: [
    { type: 'list', id: 1, name: 'test' },
    { type: 'list', id: 2, name: 'test1' },
    { type: 'map', id: 3, name: 'test' },
    { type: 'map', id: 4, name: 'test1' },
    { type: 'list', id: 5, name: 'test' },
    { type: 'list', id: 6, name: 'test1' },
    { type: 'list', id: 7, name: 'test' },
    { type: 'list', id: 8, name: 'test1' },
  ]
}

app.js

class App extends React.Component {
  render() {
    return (
      <div>
        <ListComponent />
        <MapComponent />
      </div>
    );
  }
}

selectors.js

Используйте библиотеку reselect, чтобы избежать ненужного переименования.

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

import { createSelector } from 'reselect';

export const typeElementsSelector = createSelector(
  state => state.allElements.data,
  (allElements, props) => allElements.find(element => element.type === props.type),
);

ListComponent.js

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

Обратите внимание, что ListComponent будет воспроизводить только , и только если есть какая-либо модификация в значении listElements. Примеры:

  • Теперь есть новый элемент списка типов
  • Есть один удаленный элемент типа 'list'
  • Был изменен один из текущего элемента списка

код:

import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';

import { typeElementsSelector } from './selectors';

class ListComponent extends React.Component {
  render() {
    if (!this.props.listElements.length) return (<div>Empty</div>);

    return (
      <div>
        <h2>{this.props.listElements.length} list elements</h2>
        <div>
          {this.props.listElements.map(element => (
            <ListComponentItem key={`list-${element.id}`} id={element.id} />
          ))}
        </div>
      </div>
    );
  }
}

const mapStateToProps = (reducers, props) => ({
  listElements: typeElementsSelector(reducers, props),
});

export default connect(mapStateToProps)(ListComponent);

ListComponentItem.js

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

import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';

class ListComponentItem extends React.Component {
  render() {
    return <div>{this.props.name}</div>
  }
}

const elementSelector = createSelector(
  state => state.allElements.data,
  (allElements, props) => allElements.find(element => element.id === props.id),
);

const mapStateToProps = (reducers, props) => ({
  element: elementSelector(reducers, props),
});

export default connect(mapStateToProps)(ListComponentItem);

Другой способ - создать редуктор для каждого типа и использовать ту же логику, что и выше.

{
  listElements: {
    isFetching: false,
    didInvalidate: false,
    data: [
      { id: 1, name: 'test' },
      { id: 2, name: 'test1' },
      { id: 5, name: 'test' },
      { id: 6, name: 'test1' },
      { id: 7, name: 'test' },
      { id: 8, name: 'test1' },
    ]
  },
  mapElements: {
    isFetching: false,
    didInvalidate: false,
    data: [
      { id: 3, name: 'test' },
      { id: 4, name: 'test1' },
    ]
  },
}

Помните, что ваш API может отправить вам данные, отформатированные каким-то образом, но вы можете переформатировать их по своему желанию.

Интересная статья о такой оптимизации: https://medium.com/dailyjs/react-is-slow-react-is-fast-optimizing-react-apps-in-practice-394176a11fba

Если вы хотите пойти дальше с передовыми методами нормализации данных в redux: https://github.com/paularmstrong/normalizr

Надеюсь, что это поможет.