Как вы используете `Reselect`, чтобы запоминать массив?

Предположим, у меня есть хранилище redux с такой структурой состояний:

{
  items: {
    "id1" : {
      foo: "foo1",
      bar: "bar1"
    },
    "id2": {
      foo: "foo2",
      bar: "bar2"
    } 
  }
}

Этот магазин развивается за счет получения полностью новых значений предметов:

const reduceItems = function(items = {}, action) {
  if (action.type === 'RECEIVE_ITEM') {
    return {
      ...items,
      [action.payload.id]: action.payload,
    };
  }
  return items;
};

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

function SubItem({ id, foo }) {
  return <div key={id}>{foo}</div>
}

Поскольку меня интересует только «подчасть» состояний, вот что я хочу передать «тупому» корневому представлению:

const Root = function({ subitems }) {
  // subitems[0] => { id: 'id1', foo: "foo1" }
  // subitems[1] => { id; 'id2', foo : "foo2" }
  const children = subitems.map(SubItem);
  return <div>{children}</div>;
};

Я легко могу подключить этот компонент, чтобы подписаться на изменения состояния:

function mapStatesToProps(state) {    
 return {
    subitems: xxxSelectSubItems(state)
 }
}
return connect(mapStatesToProps)(Root)

Моя основная проблема заключается в том, что происходит, когда часть состояния, которая меня не волнует (bar), изменяется. Или даже, когда я получаю новое значение элемента, в котором ни foo, ни bar не изменились:

setInterval(() => {
    store.dispatch({
      type: 'RECEIVE_ITEM',
      payload: {
        id: 'id1',
        foo: 'foo1',
        bar: 'bar1',
      },
    });
  }, 1000);

Если я использую "наивную" реализацию селектора:

// naive version
function toSubItem(id, item) {
  const foo = item.foo;
  return { id, foo };
}

function dumbSelectSubItems(state) {
  const ids = Object.keys(state.items);
  return ids.map(id => {
    const item = state.items[id];
    return toSubItem(id, item);
  });
}

Тогда список представляет собой совершенно новый объект при каждом вызове, и мой компонент каждый раз отображается бесплатно.

Конечно, если я использую «константный» селектор, который всегда возвращает один и тот же список, поскольку подключенный компонент чистый, он повторно визуализируется (но это просто для иллюстрации чистых подключенных компонентов):

// fully pure implementation
const SUBITEMS = [
  {
    id: 'id0',
    foo: 'foo0',
  },
];
function constSelectSubItems(state) {
  return SUBITEMS;
}

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

const SUBITEM = {
  id: 'id0',
  foo: 'foo0',
};
function almostConstSelectSubItems(state) {
  return [SUBITEM];
}

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

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

const reselectSelectIds = (state, props) => Object.keys(state.items);
const reselectSelectItems = (state, props) => state.items;
const reselectSelectSubItems = createSelector([reSelectIds, reSelectItems], (ids, items) => {
  return ids.map(id => toSubItem(id, items));
});

Но потом ведет себя точно так же, как наивная версия.

So:

  • бессмысленно пытаться запоминать массив?
  • может повторно выбрать справиться с этим?
  • я должен изменить организацию государства?
  • я должен просто реализовать shouldComponentUpdate в корне, используя тест "deepEqual"?
  • должен ли я отказаться от того, что Root является подключенным компонентом, и сделать каждый LeafItems самим подключенным компонентом?
  • может помочь immutable.js?
  • действительно ли это не проблема, потому что React умен и не будет ничего перерисовывать после вычисления виртуального дома?

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


person phtrivier    schedule 01.06.2017    source источник


Ответы (1)


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

Вместо того, чтобы иметь селектор, который немедленно возвращает Object.keys(state.item), вам нужно иметь дело с самим объектом:

const selectItems = state => state.items;

const selectSubItems = createSelector(
    selectItems,
    (items) => {
        const ids = Object.keys(items);
        return ids.map(id => toSubItem(id, items));
    }
);

Таким образом, массив будет пересчитан только при замене объекта state.items.

Помимо этого, да, вы также можете захотеть подключить свои отдельные компоненты элемента списка, чтобы каждый из них просматривал свои собственные данные по идентификатору. См. Мое сообщение в блоге Практический Redux , Часть 6: Связанные списки, формы и производительность для примеров. У меня также есть несколько связанных статей в Redux Techniques # Selectors and Normalization и Performance # Redux Performance разделов моего списка ссылок React / Redux .

person markerikson    schedule 01.06.2017
comment
Как обсуждалось в Twitter (twitter.com/OliverJAsh/status/1246524732469633025), повторная публикация здесь для процветания: этот селектор по-прежнему будет возвращать новые ссылки на массивы в некоторых случаях, когда вы этого не хотите, например, когда изменяется содержимое одного из элементов. К сожалению, на самом деле повторный выбор не дает решения этой проблемы прямо сейчас: - / - person Oliver Joseph Ash; 04.04.2020
comment
@OliverJosephAsh Я считаю, что причина существования Reselect-map. - person Inkling; 13.11.2020