Этот пост является продолжением поста про оптимизацию производительности списка в React приложении.

Внимание. В данном посте примеры подготовлены специально для Redux приложений. Но сам подход возможно применить и с другими библиотеками. Так же нижеприведенный совет работает в react-redux версии 5. Я не смог достичь желаемого результата в версии 4. Глубоко разбираться в причинах я не стал.

И так, стандартный способ хранить некоторое множество элементов в приложении — это хранить их в массиве:

const state = {
  targets: [{id: 'target1', radius: 10}, {id: 'target2', radius: 2}]
};

Далее, скорее всего, где-то у вас в приложении будет компонент, который отображает этот список:

const TargetsList = connect(state => ({targets: state.targets}))(
  ({ targets }) =>
    <ul>
      { targets.map((target) => <TargetView key={target.id} target={target} />) }
    </ul>
);

Если вдруг вам нужно обновить один элемент, то придётся обновить весь массив (иммутабельненько же нужно):

function appReducer(state, action) {
  if (action.type === 'UPDATE') {
    return {
      target: state.target.map((target) => {
        if (target.id === action.id) {
           return {
              ...target,
              radius: action.radius
           };
        } else {
          return target;
        }
      }) 
    }
  }
  // some other code
}

Так же обновление элемента приведёт к обновлению всей вьюшки «TargetsList» (будет вызван его render).

Я сделал небольшое демо с тестовым кодом для замера производительности.

На моей машине, обновление одного элемента в списке длиною 1000 занимает примерно 21мс. В предыдущем посте я описал способ повышения прозводительности с помощью дополнительных подписок на изменения состояния в дочернем компоненте, а так же добавления некоторой логики в «shouldComponentUpdate» для компонента, который отображает список.

Но практически такой-же результат можно достичь небольшим изменением формы данных.

Как же оптимизировать?


Если вы используете github.com/reactjs/react-redux, то вы можете увеличить прозводительность, изменив форму состояния на:

const state = {
  targetsOrder: ['id-1', 'id-2'],
  targets: {
    'id-1': { id: 'id-1', radius: 10 },
    'id-2': { id: 'id-2', radius: 20 },
  }
};

Далее нужно немного изменить компонент «TargetsList»:

const TargetsList = connect(state => ({targetsOrder: state.targetsOrder}))(
  ({ targetsOrder }) =>
    <ul>
      { targetsOrder.map((id) => <TargetView key={id} targetId={id} />) }
    </ul>
);

Обратите внимание, что, в данном случае, я передаю в дочерний компонент ID элемента, а не весь элемент. Тогда «TargetView» не может быть «тупым компонентом» и должен подписаться на изменения в состоянии:

const TargetView = connect(
  (state, ownProps) => ({target: state.targets[ownProps.targetId]})
)(({ target }) => {
  // your render logic
  return <Circle radius={target.radius} />;
});

Так как «TargetView» подписан на изменения в состоянии, он обновит сам себя, когда его данные обновятся. Важно то, что «TargetList» НЕ БУДЕТ обновлён при изменении элемента в списке потому, что «targetsOrder» остаётся тем же. В некоторых случаях такая техника может значительно повысить производительность.

> Обновлённое демо с замерами

Теперь обновление одного элемента занимает 2.2мс на моей машине. Это почти в 10 раз быстрее, чем в предыдущем примере.
Поделиться с друзьями
-->

Комментарии (4)


  1. xGromMx
    23.12.2016 01:52

    А какже reselect?


    1. lavrton
      23.12.2016 02:14

      А как он поможет?


  1. dagrotar
    23.12.2016 13:28
    +2

    Как всегда. Сначала мы радуемся «документоорентированному» варианту хранения данных.
    Вложенные объекты… Все в одном месте…
    А потом мы потихоньку приходим к мысли, что структура данных должна быть плоской.
    По этим граблям должен пройти каждый :)
    PS:
    normalizr


  1. koreec
    02.04.2017 16:30

    Однозначно, стоит.